diff --git a/.air.toml b/.air.toml index af182697fb..65872e7382 100644 --- a/.air.toml +++ b/.air.toml @@ -11,7 +11,7 @@ include_file = ["main.go"] include_dir = ["cmd", "models", "modules", "options", "routers", "services"] exclude_dir = [ "models/fixtures", - "models/migrations/fixtures", + "models/gitea_migrations/fixtures", "modules/avatar/identicon/testdata", "modules/avatar/testdata", "modules/git/tests", diff --git a/.deadcode-out b/.deadcode-out index 24facdf12e..9c7176e7b1 100644 --- a/.deadcode-out +++ b/.deadcode-out @@ -18,9 +18,11 @@ forgejo.org/models/auth forgejo.org/models/db TruncateBeans + TruncateBeansCascade InTransaction DumpTables GetTableNames + extendBeansForCascade forgejo.org/models/dbfs file.renameTo @@ -32,6 +34,9 @@ forgejo.org/models/forgejo/semver SetVersionString SetVersion +forgejo.org/models/forgejo_migrations + resetMigrations + forgejo.org/models/git RemoveDeletedBranchByID @@ -224,6 +229,12 @@ forgejo.org/services/context forgejo.org/services/federation FollowRemoteActor +forgejo.org/services/mailer + ActionCloseIssueByCommit.isActionAdditionalData + +forgejo.org/services/notify + UnregisterNotifier + forgejo.org/services/repository IsErrForkAlreadyExist diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3f250e5682..72fb06c69a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "Gitea DevContainer", - "image": "mcr.microsoft.com/devcontainers/go:1.24-bullseye", + "image": "mcr.microsoft.com/devcontainers/go:2.0-bullseye", "features": { // installs nodejs into container "ghcr.io/devcontainers/features/node:1": { diff --git a/.forgejo/workflows-composite/setup-cache-go/action.yaml b/.forgejo/workflows-composite/setup-cache-go/action.yaml index f2818a7635..fec166fc35 100644 --- a/.forgejo/workflows-composite/setup-cache-go/action.yaml +++ b/.forgejo/workflows-composite/setup-cache-go/action.yaml @@ -17,7 +17,7 @@ runs: apt-get -q install -qq -y zstd - name: "Set up Go using setup-go" - uses: https://data.forgejo.org/actions/setup-go@v5 + uses: https://data.forgejo.org/actions/setup-go@v6 id: go-version with: go-version-file: "go.mod" diff --git a/.forgejo/workflows/build-release-integration.yml b/.forgejo/workflows/build-release-integration.yml index f5aa059fcf..6cb4811016 100644 --- a/.forgejo/workflows/build-release-integration.yml +++ b/.forgejo/workflows/build-release-integration.yml @@ -1,4 +1,5 @@ name: Integration tests for the release process +enable-email-notifications: true on: push: @@ -25,7 +26,7 @@ jobs: if: vars.ROLE == 'forgejo-coding' runs-on: lxc-bookworm steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - id: forgejo uses: https://data.forgejo.org/actions/setup-forgejo@v3.0.4 diff --git a/.forgejo/workflows/build-release.yml b/.forgejo/workflows/build-release.yml index b70439f12f..042a981881 100644 --- a/.forgejo/workflows/build-release.yml +++ b/.forgejo/workflows/build-release.yml @@ -33,7 +33,7 @@ jobs: # root is used for testing, allow it if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root' steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 with: fetch-depth: 0 @@ -43,11 +43,11 @@ jobs: repository="${{ github.repository }}" echo "value=${repository##*/}" >> "$GITHUB_OUTPUT" - - uses: https://data.forgejo.org/actions/setup-node@v4 + - uses: https://data.forgejo.org/actions/setup-node@v6 with: node-version: 22 - - uses: https://data.forgejo.org/actions/setup-go@v5 + - uses: https://data.forgejo.org/actions/setup-go@v6 with: go-version-file: "go.mod" diff --git a/.forgejo/workflows/cascade-setup-end-to-end.yml b/.forgejo/workflows/cascade-setup-end-to-end.yml index e62dbd9e7d..c481c80497 100644 --- a/.forgejo/workflows/cascade-setup-end-to-end.yml +++ b/.forgejo/workflows/cascade-setup-end-to-end.yml @@ -37,7 +37,7 @@ jobs: container: image: data.forgejo.org/oci/node:22-bookworm steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 with: fetch-depth: '0' show-progress: 'false' diff --git a/.forgejo/workflows/coverage.yml b/.forgejo/workflows/coverage.yml index 194b116251..a835efd9a5 100644 --- a/.forgejo/workflows/coverage.yml +++ b/.forgejo/workflows/coverage.yml @@ -58,10 +58,10 @@ jobs: POSTGRESQL_EXTRA_FLAGS: -c full_page_writes=off options: --tmpfs /bitnami/postgresql cacher: - image: registry.redict.io/redict:7.3.5-scratch + image: registry.redict.io/redict:7.3.6-scratch options: --tmpfs /data:noatime steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 with: repository: ${{ inputs.repository }} ref: ${{ inputs.ref }} diff --git a/.forgejo/workflows/publish-release.yml b/.forgejo/workflows/publish-release.yml index 5303f902e3..a583756656 100644 --- a/.forgejo/workflows/publish-release.yml +++ b/.forgejo/workflows/publish-release.yml @@ -41,7 +41,7 @@ jobs: runs-on: lxc-bookworm if: vars.DOER != '' && vars.FORGEJO != '' && vars.TO_OWNER != '' && vars.FROM_OWNER != '' && secrets.TOKEN != '' steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - name: copy & sign uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.4.1 diff --git a/.forgejo/workflows/release-notes-assistant-milestones.yml b/.forgejo/workflows/release-notes-assistant-milestones.yml index 57c9a23f02..76799b06dc 100644 --- a/.forgejo/workflows/release-notes-assistant-milestones.yml +++ b/.forgejo/workflows/release-notes-assistant-milestones.yml @@ -15,7 +15,7 @@ jobs: container: image: 'data.forgejo.org/oci/ci:1' steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - uses: https://data.forgejo.org/actions/cache@v4 with: diff --git a/.forgejo/workflows/release-notes-assistant.yml b/.forgejo/workflows/release-notes-assistant.yml index a727e57afb..6fde710d84 100644 --- a/.forgejo/workflows/release-notes-assistant.yml +++ b/.forgejo/workflows/release-notes-assistant.yml @@ -17,7 +17,7 @@ jobs: container: image: 'data.forgejo.org/oci/node:22-bookworm' steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - name: event run: | @@ -28,7 +28,7 @@ jobs: ${{ toJSON(github.event) }} EOF - - uses: https://data.forgejo.org/actions/setup-go@v5 + - uses: https://data.forgejo.org/actions/setup-go@v6 with: go-version-file: "go.mod" cache: false diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index 6fc4380d72..0c033abf1a 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:41.122.3 + image: data.forgejo.org/renovate/renovate:41.152.9 steps: - name: Load renovate repo cache diff --git a/.forgejo/workflows/testing-integration.yml b/.forgejo/workflows/testing-integration.yml index 6822c45f39..f98f92e317 100644 --- a/.forgejo/workflows/testing-integration.yml +++ b/.forgejo/workflows/testing-integration.yml @@ -34,7 +34,7 @@ jobs: image: 'data.forgejo.org/oci/node:22-bookworm' options: --tmpfs /tmp:exec,noatime steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - uses: ./.forgejo/workflows-composite/setup-env - name: install git 2.34.1 and git-lfs 3.0.2 uses: ./.forgejo/workflows-composite/install-minimum-git-version @@ -53,7 +53,7 @@ jobs: image: 'data.forgejo.org/oci/node:22-bookworm' options: --tmpfs /tmp:exec,noatime steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - uses: ./.forgejo/workflows-composite/setup-env - name: install git 2.34.1 and git-lfs 3.0.2 uses: ./.forgejo/workflows-composite/install-minimum-git-version @@ -85,7 +85,7 @@ jobs: MARIADB_DATABASE: testgitea options: --tmpfs /var/lib/mysql:noatime steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - uses: ./.forgejo/workflows-composite/setup-env - name: install dependencies & git >= 2.42 uses: ./.forgejo/workflows-composite/apt-install-from diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml index 715ce3cb28..70b89d9ee0 100644 --- a/.forgejo/workflows/testing.yml +++ b/.forgejo/workflows/testing.yml @@ -1,4 +1,5 @@ name: testing +enable-email-notifications: true on: pull_request: @@ -21,7 +22,7 @@ jobs: cat <<'EOF' ${{ toJSON(github) }} EOF - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - uses: ./.forgejo/workflows-composite/setup-env - run: su forgejo -c 'make deps-backend deps-tools' - run: su forgejo -c 'make --always-make -j$(nproc) lint-backend tidy-check swagger-check lint-swagger fmt-check swagger-validate' # ensure the "go-licenses" make target runs @@ -33,7 +34,7 @@ jobs: image: 'data.forgejo.org/oci/node:22-bookworm' options: --tmpfs /tmp:exec,noatime steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - run: make deps-frontend - run: make lint-frontend - run: make checks-frontend @@ -77,7 +78,7 @@ jobs: MINIO_ROOT_USER: 123456 MINIO_ROOT_PASSWORD: 12345678 steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - uses: ./.forgejo/workflows-composite/setup-env - name: install git >= 2.42 uses: ./.forgejo/workflows-composite/apt-install-from @@ -104,7 +105,7 @@ jobs: image: 'data.forgejo.org/oci/playwright:latest' options: --tmpfs /tmp:exec,noatime steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 with: fetch-depth: 20 - uses: ./.forgejo/workflows-composite/setup-env @@ -126,7 +127,7 @@ jobs: echo "all=1" >> "$GITHUB_OUTPUT" - name: Get changed files id: changed-files - uses: https://data.forgejo.org/tj-actions/changed-files@v46 + uses: https://data.forgejo.org/tj-actions/changed-files@v47 with: separator: '\n' - run: | @@ -172,7 +173,7 @@ jobs: image: ${{ matrix.cacher.image }} options: ${{ matrix.cacher.options }} steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - uses: ./.forgejo/workflows-composite/setup-env - name: install git >= 2.42 uses: ./.forgejo/workflows-composite/apt-install-from @@ -205,7 +206,7 @@ jobs: MYSQL_EXTRA_FLAGS: --innodb-adaptive-flushing=OFF --innodb-buffer-pool-size=4G --innodb-log-buffer-size=128M --innodb-flush-log-at-trx-commit=0 --innodb-flush-log-at-timeout=30 --innodb-flush-method=nosync --innodb-fsync-threshold=1000000000 --disable-log-bin options: --tmpfs /bitnami/mysql/data:noatime steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - uses: ./.forgejo/workflows-composite/setup-env - name: install dependencies & git >= 2.42 uses: ./.forgejo/workflows-composite/apt-install-from @@ -233,6 +234,7 @@ jobs: options: --tmpfs /bitnami/minio/data ldap: image: data.forgejo.org/oci/forgejo-test-openldap:1 + options: --memory 500M pgsql: image: data.forgejo.org/oci/bitnami/postgresql:16 env: @@ -242,7 +244,7 @@ jobs: POSTGRESQL_EXTRA_FLAGS: -c full_page_writes=off options: --tmpfs /bitnami/postgresql steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - uses: ./.forgejo/workflows-composite/setup-env - name: install dependencies & git >= 2.42 uses: ./.forgejo/workflows-composite/apt-install-from @@ -264,7 +266,7 @@ jobs: image: 'data.forgejo.org/oci/node:22-bookworm' options: --tmpfs /tmp:exec,noatime steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - uses: ./.forgejo/workflows-composite/setup-env - name: install dependencies & git >= 2.42 uses: ./.forgejo/workflows-composite/apt-install-from @@ -292,7 +294,7 @@ jobs: image: 'data.forgejo.org/oci/node:22-bookworm' options: --tmpfs /tmp:exec,noatime steps: - - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v5 - uses: ./.forgejo/workflows-composite/setup-env - run: su forgejo -c 'make deps-backend deps-tools' - run: su forgejo -c 'make security-check' diff --git a/.golangci.yml b/.golangci.yml index b8884dd080..e41a40fe5a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -94,6 +94,7 @@ linters: - all testifylint: disable: + - error-is-as - go-require exclusions: generated: lax @@ -119,7 +120,7 @@ linters: - errcheck - gocyclo - gosec - path: models/migrations/v + path: models/gitea_migrations/v - linters: - forbidigo path: cmd diff --git a/.release-notes-assistant.yaml b/.release-notes-assistant.yaml index b3e5a8e665..f8264c0897 100644 --- a/.release-notes-assistant.yaml +++ b/.release-notes-assistant.yaml @@ -5,6 +5,7 @@ branch-find-version: 'v(?P\d+\.\d+)/forgejo' branch-to-version: '${version}.0' branch-from-version: 'v%[1]d.%[2]d/forgejo' tag-from-version: 'v%[1]d.%[2]d.%[3]d' +supported-release-count: 3 branch-known: - 'v7.0/forgejo' cleanup-line: 'sed -Ee "s/^(feat|fix):\s*//g" -e "s/^\[WIP\] //" -e "s/^WIP: //" -e "s;\[(UI|BUG|FEAT|v.*?/forgejo)\]\s*;;g"' diff --git a/CODEOWNERS b/CODEOWNERS index 25a3f698dc..fa332c6d71 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -38,6 +38,10 @@ routers/.* @gusted options/locale/.* @0ko options/locale_next/.* @0ko -# Personal interest +# lint-locale-usage build/lint-locale-usage/.* @fogti +models/unit/.* @fogti +services/migrations/lint-locale-usage/.* @fogti + +# Personal interest .*/webhook.* @oliverpool diff --git a/Makefile b/Makefile index f1bc58a258..14234288d6 100644 --- a/Makefile +++ b/Makefile @@ -37,17 +37,17 @@ endif XGO_VERSION := go-1.21.x AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go -EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.3.0 # renovate: datasource=go -GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.8.0 # renovate: datasource=go -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.3.1 # renovate: datasource=go +EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.4.1 # renovate: datasource=go +GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.1 # renovate: datasource=go +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.5.0 # renovate: datasource=go GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go -SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 # renovate: datasource=go +SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1 # renovate: datasource=go XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go -DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.36.0 # renovate: datasource=go +DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.38.0 # renovate: datasource=go GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.6.0 # renovate: datasource=go -RENOVATE_NPM_PACKAGE ?= renovate@41.122.3 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate +RENOVATE_NPM_PACKAGE ?= renovate@41.152.9 # 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: ... @@ -287,14 +287,14 @@ show-version-api: verify-version .PHONY: compute-go-test-packages compute-go-test-packages: ifeq ($(HAS_GO), yes) - $(eval GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list forgejo.org/models/migrations/...) $(shell $(GO) list forgejo.org/models/forgejo_migrations/...) forgejo.org/tests/integration/migration-test forgejo.org/tests forgejo.org/tests/integration forgejo.org/tests/e2e,$(shell $(GO) list ./...))) + $(eval GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list forgejo.org/models/gitea_migrations/...) $(shell $(GO) list forgejo.org/models/forgejo_migrations_legacy/...) $(shell $(GO) list forgejo.org/models/forgejo_migrations/...) forgejo.org/tests/integration/migration-test forgejo.org/tests forgejo.org/tests/integration forgejo.org/tests/e2e,$(shell $(GO) list ./...))) endif # Target to compute MIGRATION_PACKAGES - only runs when needed .PHONY: compute-migration-packages compute-migration-packages: ifeq ($(HAS_GO), yes) - $(eval MIGRATION_PACKAGES := $(shell $(GO) list forgejo.org/models/migrations/... forgejo.org/models/forgejo_migrations/...)) + $(eval MIGRATION_PACKAGES := $(shell $(GO) list forgejo.org/models/gitea_migrations/... forgejo.org/models/forgejo_migrations_legacy/... forgejo.org/models/forgejo_migrations/...)) endif ### @@ -432,7 +432,7 @@ lint: lint-frontend lint-backend lint-fix: lint-frontend-fix lint-backend-fix .PHONY: lint-frontend -lint-frontend: lint-js lint-css +lint-frontend: lint-js tsc lint-css .PHONY: lint-frontend-fix lint-frontend-fix: lint-js-fix lint-css-fix @@ -475,7 +475,7 @@ lint-locale: .PHONY: lint-locale-usage lint-locale-usage: - $(GO) run ./build/lint-locale-usage --allow-masked-usages-from=build/lint-locale-usage/allowed-masked-usage.txt + $(GO) run ./build/lint-locale-usage/bin --allow-masked-usages-from=build/lint-locale-usage/allowed-masked-usage.txt .PHONY: lint-md lint-md: node_modules @@ -514,7 +514,11 @@ lint-disposable-emails-fix: .PHONY: security-check security-check: - go run $(GOVULNCHECK_PACKAGE) -show color ./... + $(GO) run $(GOVULNCHECK_PACKAGE) -show color ./... + +.PHONY: tsc +tsc: node_modules + npx tsc --noEmit ### # Development and testing targets @@ -774,7 +778,7 @@ migrations.individual.mysql.test: $(GO_SOURCES) | compute-migration-packages .PHONY: migrations.individual.sqlite.test\#% migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$* + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/gitea_migrations/$* .PHONY: migrations.individual.pgsql.test migrations.individual.pgsql.test: $(GO_SOURCES) | compute-migration-packages @@ -784,7 +788,7 @@ migrations.individual.pgsql.test: $(GO_SOURCES) | compute-migration-packages .PHONY: migrations.individual.pgsql.test\#% migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$* + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/gitea_migrations/$* .PHONY: migrations.individual.sqlite.test migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite | compute-migration-packages @@ -794,7 +798,7 @@ migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite | compute-m .PHONY: migrations.individual.sqlite.test\#% migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/migrations/$* + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' forgejo.org/models/gitea_migrations/$* e2e.mysql.test: $(GO_SOURCES) $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/e2e -o e2e.mysql.test diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index c688045d1c..a0a8889352 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -130,6 +130,10 @@ A [companion blog post](https://forgejo.org/2024-07-release-v8-0/) provides addi - [PR](https://codeberg.org/forgejo/forgejo/pulls/3334): added support for the [`workflow_dispatch` trigger](https://forgejo.org/docs/v8.0/user/actions/#onworkflow_dispatch) in Forgejo Actions. - [PR](https://codeberg.org/forgejo/forgejo/pulls/3307): support [Proof Key for Code Exchange (PKCE - RFC7636)](https://www.rfc-editor.org/rfc/rfc7636) for external login using the OpenID Connect authentication source. - [PR](https://codeberg.org/forgejo/forgejo/pulls/3139): allow hiding auto generated release archives. + - [PR](https://codeberg.org/forgejo/forgejo/pulls/3952): Update of Chroma from v2.13.0: to v2.14.0: + - [`1e983e7`](https://github.com/alecthomas/chroma/commit/1e983e7) lexers/cue: support CUE attributes ([#​961](https://github.com/alecthomas/chroma/issues/961)) + - [`9347b55`](https://github.com/alecthomas/chroma/commit/9347b55) Add Gleam syntax highlighting ([#​959](https://github.com/alecthomas/chroma/issues/959)) + - [`2580aaa`](https://github.com/alecthomas/chroma/commit/2580aaa) Add Bazel bzlmod support into Python lexer ([#​947](https://github.com/alecthomas/chroma/issues/947)) - **Bug fixes** - [PR](https://codeberg.org/forgejo/forgejo/pulls/4732) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4715)): Show the AGit label on merged pull requests. - [PR](https://codeberg.org/forgejo/forgejo/pulls/4689) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4687)): Fixed: issue state change via the API is not idempotent. @@ -146,6 +150,9 @@ A [companion blog post](https://forgejo.org/2024-07-release-v8-0/) provides addi - [PR](https://codeberg.org/forgejo/forgejo/pulls/3442): Fixed: it is not possible to remove attachments from an empty comment. - [PR](https://codeberg.org/forgejo/forgejo/pulls/3430): Fixed: the `/api/v1/repos/{owner}/{repo}/wiki` API endpoints is using a hardcoded "master" branch for the wiki, rather than the branch they really use. - [PR](https://codeberg.org/forgejo/forgejo/pulls/3379): Fixed: using the API to search for users, the results are not paged by default an the default paging limits are not respected. + - [PR](https://codeberg.org/forgejo/forgejo/pulls/3952): Update of Chroma from v2.13.0: to v2.14.0: + - [`736c0ea`](https://github.com/alecthomas/chroma/commit/736c0ea) Typescript: Several fixes ([#​952](https://github.com/alecthomas/chroma/issues/952)) + - [`e5c25d0`](https://github.com/alecthomas/chroma/commit/e5c25d0) Org: Keep all newlines ([#​951](https://github.com/alecthomas/chroma/issues/951)) - **Localization** - [PR](https://codeberg.org/forgejo/forgejo/pulls/4661) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4568)): 24 July updates - [PR](https://codeberg.org/forgejo/forgejo/pulls/4565) ([backported from](https://codeberg.org/forgejo/forgejo/pulls/4451)): 19 July updates diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 608db76eb3..aa1a65db12 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -199,6 +199,16 @@ "path": "code.gitea.io/sdk/gitea/LICENSE", "licenseText": "Copyright (c) 2016 The Gitea Authors\nCopyright (c) 2014 The Gogs Authors\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\nall copies 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": "code.superseriousbusiness.org/go-jpeg-image-structure/v2", + "path": "code.superseriousbusiness.org/go-jpeg-image-structure/v2/LICENSE", + "licenseText": "MIT LICENSE\n\nCopyright 2020 Dustin Oprea\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.\n" + }, + { + "name": "code.superseriousbusiness.org/go-png-image-structure/v2", + "path": "code.superseriousbusiness.org/go-png-image-structure/v2/LICENSE", + "licenseText": "MIT LICENSE\n\nCopyright 2020 Dustin Oprea\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.\n" + }, { "name": "codeberg.org/gusted/mcaptcha", "path": "codeberg.org/gusted/mcaptcha/LICENSE", @@ -207,7 +217,7 @@ { "name": "connectrpc.com/connect", "path": "connectrpc.com/connect/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 2021-2024 The Connect Authors\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" + "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 2021-2025 The Connect Authors\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": "dario.cat/mergo", @@ -484,6 +494,31 @@ "path": "github.com/dsnet/compress/LICENSE.md", "licenseText": "Copyright © 2015, Joe Tsai and 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 met:\n\n* Redistributions of source code must retain the above copyright notice, this\nlist of conditions and the following disclaimer.\n* Redistributions in binary form must reproduce the above copyright notice,\nthis list of conditions and the following disclaimer in the documentation and/or\nother materials provided with the distribution.\n* Neither the copyright holder nor the names of its contributors may be used to\nendorse or promote products derived from this software without specific prior\nwritten permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, + { + "name": "github.com/dsoprea/go-exif/v3", + "path": "github.com/dsoprea/go-exif/v3/LICENSE", + "licenseText": "MIT LICENSE\n\nCopyright 2019 Dustin Oprea\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.\n" + }, + { + "name": "github.com/dsoprea/go-iptc", + "path": "github.com/dsoprea/go-iptc/LICENSE", + "licenseText": "MIT License\n\nCopyright (c) 2020 Dustin Oprea\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/dsoprea/go-logging", + "path": "github.com/dsoprea/go-logging/LICENSE", + "licenseText": "MIT LICENSE\n\nCopyright 2020 Dustin Oprea\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.\n" + }, + { + "name": "github.com/dsoprea/go-photoshop-info-format", + "path": "github.com/dsoprea/go-photoshop-info-format/LICENSE", + "licenseText": "MIT License\n\nCopyright (c) 2020 Dustin Oprea\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/dsoprea/go-utility/v2", + "path": "github.com/dsoprea/go-utility/v2/LICENSE", + "licenseText": "Copyright 2019 Random Ingenuity InformationWorks\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.\n" + }, { "name": "github.com/dustin/go-humanize", "path": "github.com/dustin/go-humanize/LICENSE", @@ -574,6 +609,11 @@ "path": "github.com/go-enry/go-enry/v2/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 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." }, + { + "name": "github.com/go-errors/errors", + "path": "github.com/go-errors/errors/LICENSE.MIT", + "licenseText": "Copyright (c) 2015 Conrad Irwin \u003cconrad@bugsnag.com\u003e\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.\n" + }, { "name": "github.com/go-fed/httpsig", "path": "github.com/go-fed/httpsig/LICENSE", @@ -619,6 +659,11 @@ "path": "github.com/go-webauthn/x/revoke/LICENSE", "licenseText": "Copyright (c) 2014 CloudFlare Inc.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\nRedistributions of source code must retain the above copyright notice,\nthis list of conditions and the following disclaimer.\n\nRedistributions in binary form must reproduce the above copyright notice,\nthis list of conditions and the following disclaimer in the documentation\nand/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 LIMITED\nTO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, + { + "name": "github.com/go-xmlfmt/xmlfmt", + "path": "github.com/go-xmlfmt/xmlfmt/LICENSE", + "licenseText": "MIT License\n\nCopyright (c) 2016 go-xmlfmt\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/gobwas/glob", "path": "github.com/gobwas/glob/LICENSE", @@ -649,6 +694,11 @@ "path": "github.com/golang-jwt/jwt/v5/LICENSE", "licenseText": "Copyright (c) 2012 Dave Grijalva\nCopyright (c) 2021 golang-jwt maintainers\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.\n\n" }, + { + "name": "github.com/golang/geo", + "path": "github.com/golang/geo/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 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" + }, { "name": "github.com/golang/groupcache/lru", "path": "github.com/golang/groupcache/lru/LICENSE", @@ -1194,6 +1244,11 @@ "path": "go.yaml.in/yaml/v3/LICENSE", "licenseText": "\nThis project is covered by two different licenses: MIT and Apache.\n\n#### MIT License ####\n\nThe following files were ported to Go from C files of libyaml, and thus\nare still covered by their original MIT license, with the additional\ncopyright staring in 2011 when the project was ported over:\n\n apic.go emitterc.go parserc.go readerc.go scannerc.go\n writerc.go yamlh.go yamlprivateh.go\n\nCopyright (c) 2006-2010 Kirill Simonov\nCopyright (c) 2006-2011 Kirill Simonov\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\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n### Apache License ###\n\nAll the remaining project files are covered by the Apache license:\n\nCopyright (c) 2011-2019 Canonical Ltd\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n" }, + { + "name": "go.yaml.in/yaml/v4", + "path": "go.yaml.in/yaml/v4/LICENSE", + "licenseText": "\nThis project is covered by two different licenses: MIT and Apache.\n\n#### MIT License ####\n\nThe following files were ported to Go from C files of libyaml, and thus\nare still covered by their original MIT license, with the additional\ncopyright staring in 2011 when the project was ported over:\n\n apic.go emitterc.go parserc.go readerc.go scannerc.go\n writerc.go yamlh.go yamlprivateh.go\n\nCopyright (c) 2006-2010 Kirill Simonov\nCopyright (c) 2006-2011 Kirill Simonov\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\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n### Apache License ###\n\nAll the remaining project files are covered by the Apache license:\n\nCopyright (c) 2011-2019 Canonical Ltd\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n" + }, { "name": "go4.org/readerutil", "path": "go4.org/readerutil/LICENSE", @@ -1264,6 +1319,11 @@ "path": "gopkg.in/warnings.v0/LICENSE", "licenseText": "Copyright (c) 2016 Péter Surányi.\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\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": "gopkg.in/yaml.v2", + "path": "gopkg.in/yaml.v2/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" + }, { "name": "gopkg.in/yaml.v3", "path": "gopkg.in/yaml.v3/LICENSE", diff --git a/build/code-batch-process.go b/build/code-batch-process.go index 516736b65c..ede43c6c46 100644 --- a/build/code-batch-process.go +++ b/build/code-batch-process.go @@ -74,7 +74,7 @@ func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error) co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/integration/migration-test`)) co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`)) co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/fixtures`)) - co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/migrations/fixtures`)) + co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`models/gitea_migrations/fixtures`)) co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`services/gitdiff/testdata`)) } @@ -181,7 +181,7 @@ func parseArgs() (mainOptions map[string]string, subCmd string, subArgs []string break } } - return + return mainOptions, subCmd, subArgs } func showUsage() { diff --git a/build/lint-locale-usage/allowed-masked-usage.txt b/build/lint-locale-usage/allowed-masked-usage.txt index 06675530db..cfab25a5fd 100644 --- a/build/lint-locale-usage/allowed-masked-usage.txt +++ b/build/lint-locale-usage/allowed-masked-usage.txt @@ -23,12 +23,6 @@ themes.names. # services/context/context.go relativetime. -# templates/mail/issue/default.tmpl: $.locale.Tr -mail.issue.in_tree_path - -# templates/package/metadata/arch.tmpl: $.locale.Tr -packages.details.license - # templates/repo/issue/view_content.tmpl: indirection via $closeTranslationKey repo.issues.close repo.pulls.close @@ -48,7 +42,3 @@ projects.type-3.display_name # templates/repo/settings/webhook/link_menu.tmpl, templates/webhook/new.tmpl: repo.settings.web_hook_name_ # tests/integration/repo_archive_text_test.go repo.settings. - -# services/migrations/migrate.go: messenger calls -# ToDo: give them a unique prefix -repo.migrate. diff --git a/build/lint-locale-usage/bin/handle-go.go b/build/lint-locale-usage/bin/handle-go.go new file mode 100644 index 0000000000..b1757fa0fc --- /dev/null +++ b/build/lint-locale-usage/bin/handle-go.go @@ -0,0 +1,177 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package main + +import ( + "fmt" + "go/ast" + goParser "go/parser" + "go/token" + "reflect" + "slices" + "strconv" + "strings" + + llu "forgejo.org/build/lint-locale-usage" + lluUnit "forgejo.org/models/unit/lint-locale-usage" + lluMigrate "forgejo.org/services/migrations/lint-locale-usage" +) + +// the `Handle*File` functions follow the following calling convention: +// * `fname` is the name of the input file +// * `src` is either `nil` (then the function invokes `ReadFile` to read the file) +// or the contents of the file as {`[]byte`, or a `string`} + +func HandleGoFile(handler llu.Handler, fname string, src any) error { + fset := token.NewFileSet() + node, err := goParser.ParseFile(fset, fname, src, goParser.SkipObjectResolution|goParser.ParseComments) + if err != nil { + return llu.LocatedError{ + Location: fname, + Kind: "Go parser", + Err: err, + } + } + + ast.Inspect(node, func(n ast.Node) bool { + // search for function calls of the form `anything.Tr(any-string-lit, ...)` + + switch n2 := n.(type) { + case *ast.CallExpr: + if len(n2.Args) == 0 { + return true + } + funSel, ok := n2.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + + ltf, ok := handler.LocaleTrFunctions[funSel.Sel.Name] + if !ok { + return true + } + + var gotUnexpectedInvoke *int + + for _, argNum := range ltf { + if len(n2.Args) <= int(argNum) { + argc := len(n2.Args) + gotUnexpectedInvoke = &argc + } else { + handler.HandleGoTrArgument(fset, n2.Args[int(argNum)], "") + } + } + + if gotUnexpectedInvoke != nil { + handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke) + } + + case *ast.CompositeLit: + if strings.HasSuffix(fname, "models/unit/unit.go") { + lluUnit.HandleCompositeUnit(handler, fset, n2) + } + + case *ast.FuncDecl: + matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKey") + if matchInsPrefix != nil { + results := n2.Type.Results.List + if len(results) != 1 { + handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name)) + return true + } + + ast.Inspect(n2.Body, func(n ast.Node) bool { + // search for return stmts + // TODO: what about nested functions? + if ret, ok := n.(*ast.ReturnStmt); ok { + for _, res := range ret.Results { + ast.Inspect(res, func(n ast.Node) bool { + if expr, ok := n.(ast.Expr); ok { + handler.HandleGoTrArgument(fset, expr, *matchInsPrefix) + } + return true + }) + } + return false + } + return true + }) + } + + if strings.HasSuffix(fname, "services/migrations/migrate.go") { + lluMigrate.HandleMessengerInFunc(handler, fset, n2) + } + return true + case *ast.GenDecl: + switch n2.Tok { + case token.CONST, token.VAR: + matchInsPrefix := handler.HandleGoCommentGroup(fset, n2.Doc, " llu:TrKeys") + if matchInsPrefix == nil { + return true + } + for _, spec := range n2.Specs { + // interpret all contained strings as message IDs + ast.Inspect(spec, func(n ast.Node) bool { + if argLit, ok := n.(*ast.BasicLit); ok { + handler.HandleGoTrBasicLit(fset, argLit, *matchInsPrefix) + return false + } + return true + }) + } + + case token.TYPE: + // modules/web/middleware/binding.go:Validate uses the convention that structs + // entries can have tags. + // In particular, `locale:$msgid` should be handled; any fields with `form:-` shouldn't. + // Problem: we don't know which structs are forms, actually. + + for _, spec := range n2.Specs { + tspec := spec.(*ast.TypeSpec) + structNode, ok := tspec.Type.(*ast.StructType) + if !ok || !(strings.HasSuffix(tspec.Name.Name, "Form") || + (tspec.Doc != nil && + slices.ContainsFunc(tspec.Doc.List, func(c *ast.Comment) bool { + return c.Text == "// swagger:model" + }))) { + continue + } + for _, field := range structNode.Fields.List { + if field.Names == nil { + continue + } + if len(field.Names) != 1 { + handler.OnWarning(fset, field.Type.Pos(), "unsupported multiple field names") + continue + } + msgidPos := field.Names[0].NamePos + msgid := "form." + field.Names[0].Name + if field.Tag != nil && field.Tag.Kind == token.STRING { + rawTag, err := strconv.Unquote(field.Tag.Value) + if err != nil { + handler.OnWarning(fset, field.Tag.ValuePos, "invalid tag value encountered") + continue + } + tag := reflect.StructTag(rawTag) + if tag.Get("form") == "-" { + continue + } + tmp := tag.Get("locale") + if len(tmp) != 0 { + msgidPos = field.Tag.ValuePos + msgid = tmp + } + } + handler.OnMsgid(fset, msgidPos, msgid, true) + } + } + } + } + + return true + }) + + return nil +} diff --git a/build/lint-locale-usage/lint-locale-usage.go b/build/lint-locale-usage/bin/lint-locale-usage.go similarity index 87% rename from build/lint-locale-usage/lint-locale-usage.go rename to build/lint-locale-usage/bin/lint-locale-usage.go index 8c67a781e9..d9cdf9f321 100644 --- a/build/lint-locale-usage/lint-locale-usage.go +++ b/build/lint-locale-usage/bin/lint-locale-usage.go @@ -16,6 +16,7 @@ import ( "sort" "strings" + llu "forgejo.org/build/lint-locale-usage" "forgejo.org/modules/container" "forgejo.org/modules/translation/localeiter" ) @@ -23,27 +24,6 @@ import ( // this works by first gathering all valid source string IDs from `en-US` reference files // and then checking if all used source strings are actually defined -type LocatedError struct { - Location string - Kind string - Err error -} - -func (e LocatedError) Error() string { - var sb strings.Builder - - sb.WriteString(e.Location) - sb.WriteString(":\t") - if e.Kind != "" { - sb.WriteString(e.Kind) - sb.WriteString(": ") - } - sb.WriteString("ERROR: ") - sb.WriteString(e.Err.Error()) - - return sb.String() -} - func InitLocaleTrFunctions() map[string][]uint { ret := make(map[string][]uint) @@ -58,14 +38,6 @@ func InitLocaleTrFunctions() map[string][]uint { return ret } -type Handler struct { - OnMsgid func(fset *token.FileSet, pos token.Pos, msgid string) - OnMsgidPrefix func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool) - OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int) - OnWarning func(fset *token.FileSet, pos token.Pos, msg string) - LocaleTrFunctions map[string][]uint -} - type StringTrie interface { Matches(key []string) bool } @@ -113,7 +85,7 @@ func (m StringTrieMap) Insert(key []string) { func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], allowedMaskedPrefixes StringTrieMap, chkMsgid func(msgid string) bool) error { file, err := os.Open(fname) if err != nil { - return LocatedError{ + return llu.LocatedError{ Location: fname, Kind: "Open", Err: err, @@ -133,7 +105,7 @@ func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], al allowedMaskedPrefixes.Insert(strings.Split(linePrefix, ".")) } else { if !chkMsgid(line) { - return LocatedError{ + return llu.LocatedError{ Location: fmt.Sprintf("%s: line %d", fname, lno), Kind: "undefined msgid", Err: errors.New(line), @@ -143,7 +115,7 @@ func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], al } } if err := scanner.Err(); err != nil { - return LocatedError{ + return llu.LocatedError{ Location: fname, Kind: "Scanner", Err: err, @@ -152,15 +124,6 @@ func ParseAllowedMaskedUsages(fname string, usedMsgids container.Set[string], al return nil } -// Truncating a message id prefix to the last dot -func PrepareMsgidPrefix(s string) (string, bool) { - index := strings.LastIndexByte(s, 0x2e) - if index == -1 { - return "", true - } - return s[:index], index != len(s)-1 -} - func Usage() { outp := flag.CommandLine.Output() fmt.Fprintf(outp, "Usage of %s:\n", os.Args[0]) @@ -210,6 +173,7 @@ func Usage() { func main() { allowMissingMsgids := false allowUnusedMsgids := false + allowWeakMissingMsgids := true usedMsgids := make(container.Set[string]) allowedMaskedPrefixes := make(StringTrieMap) @@ -228,6 +192,12 @@ func main() { false, "don't return an error code if missing message IDs are found", ) + flag.BoolVar( + &allowWeakMissingMsgids, + "allow-weak-missing-msgids", + true, + "Don't return an error code if missing 'weak' (e.g. \"form.$msgid\") message IDs are found", + ) flag.BoolVar( &allowUnusedMsgids, "allow-unused-msgids", @@ -289,7 +259,7 @@ func main() { os.Exit(3) } - handler := Handler{ + handler := llu.Handler{ OnMsgidPrefix: func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool) { msgidPrefixSplit := strings.Split(msgidPrefix, ".") if !truncated { @@ -299,8 +269,11 @@ func main() { fmt.Printf("%s:\tmissing msgid prefix: %s\n", fset.Position(pos).String(), msgidPrefix) } }, - OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) { + OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string, weak bool) { if !msgids.Contains(msgid) { + if weak && allowWeakMissingMsgids { + return + } gotAnyMsgidError = true fmt.Printf("%s:\tmissing msgid: %s\n", fset.Position(pos).String(), msgid) } else { @@ -314,7 +287,7 @@ func main() { OnWarning: func(fset *token.FileSet, pos token.Pos, msg string) { fmt.Printf("%s:\tWARNING: %s\n", fset.Position(pos).String(), msg) }, - LocaleTrFunctions: InitLocaleTrFunctions(), + LocaleTrFunctions: llu.InitLocaleTrFunctions(), } if err := filepath.WalkDir(".", func(fpath string, d fs.DirEntry, err error) error { @@ -332,7 +305,7 @@ func main() { } else if name == "bindata.go" || fpath == "modules/translation/i18n/i18n_test.go" { // skip false positives } else if strings.HasSuffix(name, ".go") { - onError(handler.HandleGoFile(fpath, nil)) + onError(HandleGoFile(handler, fpath, nil)) } else if strings.HasSuffix(name, ".tmpl") { if strings.HasPrefix(fpath, "tests") && strings.HasSuffix(name, ".ini.tmpl") { // skip false positives diff --git a/build/lint-locale-usage/lint-locale-usage_test.go b/build/lint-locale-usage/bin/lint-locale-usage_test.go similarity index 83% rename from build/lint-locale-usage/lint-locale-usage_test.go rename to build/lint-locale-usage/bin/lint-locale-usage_test.go index 81ca12c6db..69fc5accc6 100644 --- a/build/lint-locale-usage/lint-locale-usage_test.go +++ b/build/lint-locale-usage/bin/lint-locale-usage_test.go @@ -7,24 +7,26 @@ import ( "go/token" "testing" + llu "forgejo.org/build/lint-locale-usage" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func buildHandler(ret *[]string) Handler { - return Handler{ - OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) { +func buildHandler(ret *[]string) llu.Handler { + return llu.Handler{ + OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string, weak bool) { *ret = append(*ret, msgid) }, OnUnexpectedInvoke: func(fset *token.FileSet, pos token.Pos, funcname string, argc int) {}, - LocaleTrFunctions: InitLocaleTrFunctions(), + LocaleTrFunctions: llu.InitLocaleTrFunctions(), } } func HandleGoFileWrapped(t *testing.T, fname, src string) []string { var ret []string handler := buildHandler(&ret) - require.NoError(t, handler.HandleGoFile(fname, src)) + require.NoError(t, HandleGoFile(handler, fname, src)) return ret } diff --git a/build/lint-locale-usage/handle-go.go b/build/lint-locale-usage/handle-go.go index a91a210313..44229e52f7 100644 --- a/build/lint-locale-usage/handle-go.go +++ b/build/lint-locale-usage/handle-go.go @@ -2,18 +2,17 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package main +package lintLocaleUsage import ( "fmt" "go/ast" - goParser "go/parser" "go/token" "strconv" "strings" ) -func (handler Handler) handleGoTrBasicLit(fset *token.FileSet, argLit *ast.BasicLit, prefix string) { +func (handler Handler) HandleGoTrBasicLit(fset *token.FileSet, argLit *ast.BasicLit, prefix string) { if argLit.Kind == token.STRING { // extract string content arg, err := strconv.Unquote(argLit.Value) @@ -29,14 +28,14 @@ func (handler Handler) handleGoTrBasicLit(fset *token.FileSet, argLit *ast.Basic } handler.OnMsgidPrefix(fset, argLit.ValuePos, prep, trunc) } else { - handler.OnMsgid(fset, argLit.ValuePos, arg) + handler.OnMsgid(fset, argLit.ValuePos, arg, false) } } } -func (handler Handler) handleGoTrArgument(fset *token.FileSet, n ast.Expr, prefix string) { +func (handler Handler) HandleGoTrArgument(fset *token.FileSet, n ast.Expr, prefix string) { if argLit, ok := n.(*ast.BasicLit); ok { - handler.handleGoTrBasicLit(fset, argLit, prefix) + handler.HandleGoTrBasicLit(fset, argLit, prefix) } else if argBinExpr, ok := n.(*ast.BinaryExpr); ok { if argBinExpr.Op != token.ADD { // pass @@ -57,7 +56,7 @@ func (handler Handler) handleGoTrArgument(fset *token.FileSet, n ast.Expr, prefi } } -func (handler Handler) handleGoCommentGroup(fset *token.FileSet, cg *ast.CommentGroup, commentPrefix string) *string { +func (handler Handler) HandleGoCommentGroup(fset *token.FileSet, cg *ast.CommentGroup, commentPrefix string) *string { if cg == nil { return nil } @@ -87,131 +86,3 @@ func (handler Handler) handleGoCommentGroup(fset *token.FileSet, cg *ast.Comment return &matchInsPrefix } } - -// the `Handle*File` functions follow the following calling convention: -// * `fname` is the name of the input file -// * `src` is either `nil` (then the function invokes `ReadFile` to read the file) -// or the contents of the file as {`[]byte`, or a `string`} - -func (handler Handler) HandleGoFile(fname string, src any) error { - fset := token.NewFileSet() - node, err := goParser.ParseFile(fset, fname, src, goParser.SkipObjectResolution|goParser.ParseComments) - if err != nil { - return LocatedError{ - Location: fname, - Kind: "Go parser", - Err: err, - } - } - - ast.Inspect(node, func(n ast.Node) bool { - // search for function calls of the form `anything.Tr(any-string-lit, ...)` - - switch n2 := n.(type) { - case *ast.CallExpr: - if len(n2.Args) == 0 { - return true - } - funSel, ok := n2.Fun.(*ast.SelectorExpr) - if !ok { - return true - } - - ltf, ok := handler.LocaleTrFunctions[funSel.Sel.Name] - if !ok { - return true - } - - var gotUnexpectedInvoke *int - - for _, argNum := range ltf { - if len(n2.Args) <= int(argNum) { - argc := len(n2.Args) - gotUnexpectedInvoke = &argc - } else { - handler.handleGoTrArgument(fset, n2.Args[int(argNum)], "") - } - } - - if gotUnexpectedInvoke != nil { - handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke) - } - case *ast.CompositeLit: - ident, ok := n2.Type.(*ast.Ident) - if !ok { - return true - } - - // special case: models/unit/unit.go - if strings.HasSuffix(fname, "unit.go") && ident.Name == "Unit" { - if len(n2.Elts) != 6 { - handler.OnWarning(fset, n2.Pos(), "unexpected initialization of 'Unit' (unexpected number of arguments)") - } - // NameKey has index 2 - // invoked like '{{ctx.Locale.Tr $unit.NameKey}}' - nameKey, ok := n2.Elts[2].(*ast.BasicLit) - if !ok || nameKey.Kind != token.STRING { - handler.OnWarning(fset, n2.Elts[2].Pos(), "unexpected initialization of 'Unit' (expected string literal as NameKey)") - return true - } - - // extract string content - arg, err := strconv.Unquote(nameKey.Value) - if err == nil { - // found interesting strings - handler.OnMsgid(fset, nameKey.ValuePos, arg) - } - } - case *ast.FuncDecl: - matchInsPrefix := handler.handleGoCommentGroup(fset, n2.Doc, "llu:returnsTrKey") - if matchInsPrefix == nil { - return true - } - results := n2.Type.Results.List - if len(results) != 1 { - handler.OnWarning(fset, n2.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", n2.Name.Name)) - return true - } - - ast.Inspect(n2.Body, func(n ast.Node) bool { - // search for return stmts - // TODO: what about nested functions? - if ret, ok := n.(*ast.ReturnStmt); ok { - for _, res := range ret.Results { - ast.Inspect(res, func(n ast.Node) bool { - if expr, ok := n.(ast.Expr); ok { - handler.handleGoTrArgument(fset, expr, *matchInsPrefix) - } - return true - }) - } - return false - } - return true - }) - return true - case *ast.GenDecl: - if !(n2.Tok == token.CONST || n2.Tok == token.VAR) { - return true - } - matchInsPrefix := handler.handleGoCommentGroup(fset, n2.Doc, " llu:TrKeys") - if matchInsPrefix == nil { - return true - } - for _, spec := range n2.Specs { - // interpret all contained strings as message IDs - ast.Inspect(spec, func(n ast.Node) bool { - if argLit, ok := n.(*ast.BasicLit); ok { - handler.handleGoTrBasicLit(fset, argLit, *matchInsPrefix) - return false - } - return true - }) - } - } - - return true - }) - - return nil -} diff --git a/build/lint-locale-usage/handle-tmpl.go b/build/lint-locale-usage/handle-tmpl.go index a71d7d47e3..8d03291205 100644 --- a/build/lint-locale-usage/handle-tmpl.go +++ b/build/lint-locale-usage/handle-tmpl.go @@ -2,7 +2,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package main +package lintLocaleUsage import ( "fmt" @@ -48,18 +48,29 @@ func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.N } funcname := "" - if nodeChain, ok := nodeCommand.Args[0].(*tmplParser.ChainNode); ok { + switch nodeCommand.Args[0].Type() { + case tmplParser.NodeChain: + nodeChain := nodeCommand.Args[0].(*tmplParser.ChainNode) if nodeIdent, ok := nodeChain.Node.(*tmplParser.IdentifierNode); ok { if nodeIdent.Ident != "ctx" || len(nodeChain.Field) != 2 || nodeChain.Field[0] != "Locale" { return } funcname = nodeChain.Field[1] } - } else if nodeField, ok := nodeCommand.Args[0].(*tmplParser.FieldNode); ok { + + case tmplParser.NodeField: + nodeField := nodeCommand.Args[0].(*tmplParser.FieldNode) if len(nodeField.Ident) != 2 || !(nodeField.Ident[0] == "locale" || nodeField.Ident[0] == "Locale") { return } funcname = nodeField.Ident[1] + + case tmplParser.NodeVariable: + nodeVar := nodeCommand.Args[0].(*tmplParser.VariableNode) + if len(nodeVar.Ident) != 3 || !(nodeVar.Ident[0] == "$" && nodeVar.Ident[1] == "locale") { + return + } + funcname = nodeVar.Ident[2] } var gotUnexpectedInvoke *int @@ -93,7 +104,7 @@ func (handler Handler) handleTemplateMsgid(fset *token.FileSet, node tmplParser. case tmplParser.NodeString: nodeString := node.(*tmplParser.StringNode) // found interesting strings - handler.OnMsgid(fset, pos, nodeString.Text) + handler.OnMsgid(fset, pos, nodeString.Text, false) case tmplParser.NodePipe: nodePipe := node.(*tmplParser.PipeNode) @@ -132,7 +143,7 @@ func (handler Handler) handleTemplateMsgid(fset *token.FileSet, node tmplParser. if len(nodeCommand.Args) == 2 { // found interesting strings - handler.OnMsgid(fset, stringPos, msgidPrefix) + handler.OnMsgid(fset, stringPos, msgidPrefix, false) } else { if nodeIdent.Ident == "printf" { parts := strings.SplitN(msgidPrefix, "%", 2) diff --git a/build/lint-locale-usage/handler.go b/build/lint-locale-usage/handler.go new file mode 100644 index 0000000000..6673ac3a4d --- /dev/null +++ b/build/lint-locale-usage/handler.go @@ -0,0 +1,62 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package lintLocaleUsage + +import ( + "go/token" + "strings" +) + +type LocatedError struct { + Location string + Kind string + Err error +} + +func (e LocatedError) Error() string { + var sb strings.Builder + + sb.WriteString(e.Location) + sb.WriteString(":\t") + if e.Kind != "" { + sb.WriteString(e.Kind) + sb.WriteString(": ") + } + sb.WriteString("ERROR: ") + sb.WriteString(e.Err.Error()) + + return sb.String() +} + +func InitLocaleTrFunctions() map[string][]uint { + ret := make(map[string][]uint) + + f0 := []uint{0} + ret["Tr"] = f0 + ret["TrString"] = f0 + ret["TrHTML"] = f0 + + ret["TrPluralString"] = []uint{1} + ret["TrN"] = []uint{1, 2} + + return ret +} + +type Handler struct { + OnMsgid func(fset *token.FileSet, pos token.Pos, msgid string, weak bool) + OnMsgidPrefix func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool) + OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int) + OnWarning func(fset *token.FileSet, pos token.Pos, msg string) + LocaleTrFunctions map[string][]uint +} + +// Truncating a message id prefix to the last dot +func PrepareMsgidPrefix(s string) (string, bool) { + index := strings.LastIndexByte(s, 0x2e) + if index == -1 { + return "", true + } + return s[:index], index != len(s)-1 +} diff --git a/cmd/actions.go b/cmd/actions.go index 12af2c8e86..7a440d0290 100644 --- a/cmd/actions.go +++ b/cmd/actions.go @@ -28,6 +28,7 @@ func subcmdActionsGenRunnerToken() *cli.Command { return &cli.Command{ Name: "generate-runner-token", Usage: "Generate a new token for a runner to use to register with the server", + Before: noDanglingArgs, Action: runGenerateActionsRunnerToken, Aliases: []string{"grt"}, Flags: []cli.Flag{ diff --git a/cmd/admin.go b/cmd/admin.go index 7e06a99cda..90157e2d5a 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -37,6 +37,7 @@ func subcmdRepoSyncReleases() *cli.Command { return &cli.Command{ Name: "repo-sync-releases", Usage: "Synchronize repository releases with tags", + Before: noDanglingArgs, Action: runRepoSyncReleases, } } @@ -75,6 +76,7 @@ func subcmdSendMail() *cli.Command { return &cli.Command{ Name: "sendmail", Usage: "Send a message to all users", + Before: noDanglingArgs, Action: runSendMail, Flags: []cli.Flag{ &cli.StringFlag{ @@ -103,7 +105,7 @@ func idFlag() *cli.Int64Flag { } } -func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error { +func runRepoSyncReleases(ctx context.Context, c *cli.Command) error { ctx, cancel := installSignals(ctx) defer cancel() diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go index 91b344b1e9..0cfe7da12e 100644 --- a/cmd/admin_auth.go +++ b/cmd/admin_auth.go @@ -31,6 +31,7 @@ func microcmdAuthDelete() *cli.Command { Name: "delete", Usage: "Delete specific auth source", Flags: []cli.Flag{idFlag()}, + Before: noDanglingArgs, Action: runDeleteAuth, } } @@ -39,6 +40,7 @@ func microcmdAuthList() *cli.Command { return &cli.Command{ Name: "list", Usage: "List auth sources", + Before: noDanglingArgs, Action: runListAuth, Flags: []cli.Flag{ &cli.IntFlag{ diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 9af6c331d3..0ed7613a7e 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -133,8 +133,9 @@ func ldapSimpleAuthCLIFlags() []cli.Flag { func microcmdAuthAddLdapBindDn() *cli.Command { return &cli.Command{ - Name: "add-ldap", - Usage: "Add new LDAP (via Bind DN) authentication source", + Name: "add-ldap", + Usage: "Add new LDAP (via Bind DN) authentication source", + Before: noDanglingArgs, Action: func(ctx context.Context, cli *cli.Command) error { return newAuthService().addLdapBindDn(ctx, cli) }, @@ -144,8 +145,9 @@ func microcmdAuthAddLdapBindDn() *cli.Command { func microcmdAuthUpdateLdapBindDn() *cli.Command { return &cli.Command{ - Name: "update-ldap", - Usage: "Update existing LDAP (via Bind DN) authentication source", + Name: "update-ldap", + Usage: "Update existing LDAP (via Bind DN) authentication source", + Before: noDanglingArgs, Action: func(ctx context.Context, cli *cli.Command) error { return newAuthService().updateLdapBindDn(ctx, cli) }, @@ -155,8 +157,9 @@ func microcmdAuthUpdateLdapBindDn() *cli.Command { func microcmdAuthAddLdapSimpleAuth() *cli.Command { return &cli.Command{ - Name: "add-ldap-simple", - Usage: "Add new LDAP (simple auth) authentication source", + Name: "add-ldap-simple", + Usage: "Add new LDAP (simple auth) authentication source", + Before: noDanglingArgs, Action: func(ctx context.Context, cli *cli.Command) error { return newAuthService().addLdapSimpleAuth(ctx, cli) }, @@ -166,8 +169,9 @@ func microcmdAuthAddLdapSimpleAuth() *cli.Command { func microcmdAuthUpdateLdapSimpleAuth() *cli.Command { return &cli.Command{ - Name: "update-ldap-simple", - Usage: "Update existing LDAP (simple auth) authentication source", + Name: "update-ldap-simple", + Usage: "Update existing LDAP (simple auth) authentication source", + Before: noDanglingArgs, Action: func(ctx context.Context, cli *cli.Command) error { return newAuthService().updateLdapSimpleAuth(ctx, cli) }, diff --git a/cmd/admin_auth_oauth.go b/cmd/admin_auth_oauth.go index ef5d9116e3..0fd8650f37 100644 --- a/cmd/admin_auth_oauth.go +++ b/cmd/admin_auth_oauth.go @@ -136,6 +136,7 @@ func microcmdAuthAddOauth() *cli.Command { return &cli.Command{ Name: "add-oauth", Usage: "Add new Oauth authentication source", + Before: noDanglingArgs, Action: newAuthService().addOauth, Flags: oauthCLIFlags(), } @@ -145,6 +146,7 @@ func microcmdAuthUpdateOauth() *cli.Command { return &cli.Command{ Name: "update-oauth", Usage: "Update existing Oauth authentication source", + Before: noDanglingArgs, Action: newAuthService().updateOauth, Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{idFlag()}, oauthCLIFlags()[1:]...)...), } diff --git a/cmd/admin_auth_stmp.go b/cmd/admin_auth_stmp.go index 48b3adaac3..970b613638 100644 --- a/cmd/admin_auth_stmp.go +++ b/cmd/admin_auth_stmp.go @@ -78,6 +78,7 @@ func microcmdAuthAddSMTP() *cli.Command { return &cli.Command{ Name: "add-smtp", Usage: "Add new SMTP authentication source", + Before: noDanglingArgs, Action: runAddSMTP, Flags: smtpCLIFlags(), } @@ -87,6 +88,7 @@ func microcmdAuthUpdateSMTP() *cli.Command { return &cli.Command{ Name: "update-smtp", Usage: "Update existing SMTP authentication source", + Before: noDanglingArgs, Action: runUpdateSMTP, Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{idFlag()}, smtpCLIFlags()[1:]...)...), } diff --git a/cmd/admin_regenerate.go b/cmd/admin_regenerate.go index 7bfd12f8f4..4d14df317d 100644 --- a/cmd/admin_regenerate.go +++ b/cmd/admin_regenerate.go @@ -17,17 +17,19 @@ var ( microcmdRegenHooks = &cli.Command{ Name: "hooks", Usage: "Regenerate git-hooks", + Before: noDanglingArgs, Action: runRegenerateHooks, } microcmdRegenKeys = &cli.Command{ Name: "keys", Usage: "Regenerate authorized_keys file", + Before: noDanglingArgs, Action: runRegenerateKeys, } ) -func runRegenerateHooks(ctx context.Context, _ *cli.Command) error { +func runRegenerateHooks(ctx context.Context, c *cli.Command) error { ctx, cancel := installSignals(ctx) defer cancel() @@ -37,7 +39,7 @@ func runRegenerateHooks(ctx context.Context, _ *cli.Command) error { return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) } -func runRegenerateKeys(ctx context.Context, _ *cli.Command) error { +func runRegenerateKeys(ctx context.Context, c *cli.Command) error { ctx, cancel := installSignals(ctx) defer cancel() diff --git a/cmd/admin_user_change_password.go b/cmd/admin_user_change_password.go index dd8c9d378a..cd52a8378c 100644 --- a/cmd/admin_user_change_password.go +++ b/cmd/admin_user_change_password.go @@ -21,6 +21,7 @@ func microcmdUserChangePassword() *cli.Command { return &cli.Command{ Name: "change-password", Usage: "Change a user's password", + Before: noDanglingArgs, Action: runChangePassword, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 96431412f6..e3800bdb59 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -23,6 +23,7 @@ func microcmdUserCreate() *cli.Command { return &cli.Command{ Name: "create", Usage: "Create a new user in database", + Before: noDanglingArgs, Action: runCreateUser, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/cmd/admin_user_delete.go b/cmd/admin_user_delete.go index 3382c53e5f..affd76247f 100644 --- a/cmd/admin_user_delete.go +++ b/cmd/admin_user_delete.go @@ -40,6 +40,7 @@ func microcmdUserDelete() *cli.Command { Usage: "Purge user, all their repositories, organizations and comments", }, }, + Before: noDanglingArgs, Action: runDeleteUser, } } diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index d0f2878297..0a3a7fa89d 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -40,6 +40,7 @@ func microcmdUserGenerateAccessToken() *cli.Command { Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`, }, }, + Before: noDanglingArgs, Action: runGenerateAccessToken, } } diff --git a/cmd/admin_user_list.go b/cmd/admin_user_list.go index ccc4b8c917..8e1323bcf8 100644 --- a/cmd/admin_user_list.go +++ b/cmd/admin_user_list.go @@ -18,6 +18,7 @@ func microcmdUserList() *cli.Command { return &cli.Command{ Name: "list", Usage: "List users", + Before: noDanglingArgs, Action: runListUsers, Flags: []cli.Flag{ &cli.BoolFlag{ diff --git a/cmd/admin_user_reset_mfa.go b/cmd/admin_user_reset_mfa.go index 8107fd97bf..504331100c 100644 --- a/cmd/admin_user_reset_mfa.go +++ b/cmd/admin_user_reset_mfa.go @@ -17,6 +17,7 @@ func microcmdUserResetMFA() *cli.Command { return &cli.Command{ Name: "reset-mfa", Usage: "Remove all two-factor authentication configurations for a user", + Before: noDanglingArgs, Action: runResetMFA, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/cmd/cert.go b/cmd/cert.go index f9e3a16f3e..baadcbda85 100644 --- a/cmd/cert.go +++ b/cmd/cert.go @@ -31,6 +31,7 @@ func cmdCert() *cli.Command { Usage: "Generate self-signed certificate", Description: `Generate a self-signed X.509 certificate for a TLS server. Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`, + Before: noDanglingArgs, Action: runCert, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/cmd/cmd.go b/cmd/cmd.go index 85a482b78c..379609d294 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -12,6 +12,7 @@ import ( "io" "os" "os/signal" + "slices" "strings" "syscall" @@ -40,6 +41,19 @@ func argsSet(c *cli.Command, args ...string) error { return nil } +// When a CLI command is intended to be used only with flags and no other arbitrary args, noDanglingArgs will validate +// the end-user's usage. +func noDanglingArgs(ctx context.Context, c *cli.Command) (context.Context, error) { + if c.Args().Len() != 0 { + args := c.Args().Slice() + if slices.Contains(args, "false") { + println("Hint: boolean false must be specified as a single arg, eg. '--restricted=false', not '--restricted false'") + } + return nil, fmt.Errorf("unexpected arguments: %s", strings.Join(c.Args().Slice(), ", ")) + } + return nil, nil +} + // confirm waits for user input which confirms an action func confirm() (bool, error) { var response string @@ -135,3 +149,18 @@ func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(ctx context.Context, return ctx, nil } } + +func multipleBefore(beforeFuncs ...cli.BeforeFunc) cli.BeforeFunc { + return func(ctx context.Context, cli *cli.Command) (context.Context, error) { + for _, beforeFunc := range beforeFuncs { + bctx, err := beforeFunc(ctx, cli) + if err != nil { + return bctx, err + } + if bctx != nil { + ctx = bctx + } + } + return ctx, nil + } +} diff --git a/cmd/doctor.go b/cmd/doctor.go index 681794f094..f557481110 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -6,6 +6,8 @@ package cmd import ( "context" "fmt" + "image" + "io" golog "log" "os" "path/filepath" @@ -13,13 +15,17 @@ import ( "text/tabwriter" "forgejo.org/models/db" - "forgejo.org/models/migrations" - migrate_base "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations" + migrate_base "forgejo.org/models/gitea_migrations/base" + repo_model "forgejo.org/models/repo" + user_model "forgejo.org/models/user" "forgejo.org/modules/container" "forgejo.org/modules/log" "forgejo.org/modules/setting" + "forgejo.org/modules/storage" "forgejo.org/services/doctor" + exif_terminator "code.superseriousbusiness.org/exif-terminator" "github.com/urfave/cli/v3" ) @@ -34,6 +40,7 @@ func cmdDoctor() *cli.Command { cmdDoctorCheck(), cmdRecreateTable(), cmdDoctorConvert(), + cmdAvatarStripExif(), }, } } @@ -43,6 +50,7 @@ func cmdDoctorCheck() *cli.Command { Name: "check", Usage: "Diagnose and optionally fix problems", Description: "A command to diagnose problems with the current Forgejo instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", + Before: noDanglingArgs, Action: runDoctorCheck, Flags: []cli.Flag{ &cli.BoolFlag{ @@ -98,6 +106,15 @@ You should back-up your database before doing this and ensure that your database } } +func cmdAvatarStripExif() *cli.Command { + return &cli.Command{ + Name: "avatar-strip-exif", + Usage: "Strip EXIF metadata from all images in the avatar storage", + Before: noDanglingArgs, + Action: runAvatarStripExif, + } +} + func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error { stdCtx, cancel := installSignals(stdCtx) defer cancel() @@ -142,7 +159,7 @@ func runRecreateTable(stdCtx context.Context, ctx *cli.Command) error { return err } - if err := migrations.EnsureUpToDate(engine); err != nil { + if err := gitea_migrations.EnsureUpToDate(engine); err != nil { return err } @@ -230,3 +247,78 @@ func runDoctorCheck(stdCtx context.Context, ctx *cli.Command) error { } return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks) } + +func runAvatarStripExif(ctx context.Context, c *cli.Command) error { + ctx, cancel := installSignals(ctx) + defer cancel() + + if err := initDB(ctx); err != nil { + return err + } + if err := storage.Init(); err != nil { + return err + } + + type HasCustomAvatarRelativePath interface { + CustomAvatarRelativePath() string + } + + doExifStrip := func(obj HasCustomAvatarRelativePath, name string, target_storage storage.ObjectStorage) error { + if obj.CustomAvatarRelativePath() == "" { + return nil + } + + log.Info("Stripping avatar for %s...", name) + + avatarFile, err := target_storage.Open(obj.CustomAvatarRelativePath()) + if err != nil { + return fmt.Errorf("storage.Avatars.Open: %w", err) + } + _, imgType, err := image.DecodeConfig(avatarFile) + if err != nil { + return fmt.Errorf("image.DecodeConfig: %w", err) + } + + // reset io.Reader for exif termination scan + _, err = avatarFile.Seek(0, io.SeekStart) + if err != nil { + return fmt.Errorf("avatarFile.Seek: %w", err) + } + + cleanedData, err := exif_terminator.Terminate(avatarFile, imgType) + if err != nil && strings.Contains(err.Error(), "cannot be processed") { + // expected error for an image type that isn't supported by exif_terminator + log.Info("... image type %s is not supported by exif_terminator, skipping.", imgType) + return nil + } else if err != nil { + return fmt.Errorf("error cleaning exif data: %w", err) + } + + if err := storage.SaveFrom(target_storage, obj.CustomAvatarRelativePath(), func(w io.Writer) error { + _, err := io.Copy(w, cleanedData) + return err + }); err != nil { + return fmt.Errorf("Failed to create dir %s: %w", obj.CustomAvatarRelativePath(), err) + } + + log.Info("... completed %s.", name) + + return nil + } + + err := db.Iterate(ctx, nil, func(ctx context.Context, user *user_model.User) error { + return doExifStrip(user, fmt.Sprintf("user %s", user.Name), storage.Avatars) + }) + if err != nil { + return err + } + + err = db.Iterate(ctx, nil, func(ctx context.Context, repo *repo_model.Repository) error { + return doExifStrip(repo, fmt.Sprintf("repo %s", repo.Name), storage.RepoAvatars) + }) + if err != nil { + return err + } + + return nil +} diff --git a/cmd/doctor_convert.go b/cmd/doctor_convert.go index 44bebae154..7bc70163dd 100644 --- a/cmd/doctor_convert.go +++ b/cmd/doctor_convert.go @@ -20,6 +20,7 @@ func cmdDoctorConvert() *cli.Command { Name: "convert", Usage: "Convert the database", Description: "A command to convert an existing MySQL database from utf8 to utf8mb4", + Before: noDanglingArgs, Action: runDoctorConvert, } } diff --git a/cmd/dump.go b/cmd/dump.go index ea141291f5..b94277e529 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -164,6 +164,7 @@ func cmdDump() *cli.Command { Usage: "Dump Forgejo files and database", Description: `Dump compresses all related files and database into zip file. It can be used for backup and capture Forgejo server image to send to maintainer`, + Before: noDanglingArgs, Action: runDump, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go index 7159d55e99..8e0ef0311f 100644 --- a/cmd/dump_repo.go +++ b/cmd/dump_repo.go @@ -28,6 +28,7 @@ func cmdDumpRepository() *cli.Command { Name: "dump-repo", Usage: "Dump the repository from git/github/gitea/gitlab", Description: "This is a command for dumping the repository data.", + Before: noDanglingArgs, Action: runDumpRepository, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/cmd/generate.go b/cmd/generate.go index 7076ae541f..a37d689dc2 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -42,6 +42,7 @@ func microcmdGenerateInternalToken() *cli.Command { return &cli.Command{ Name: "INTERNAL_TOKEN", Usage: "Generate a new INTERNAL_TOKEN", + Before: noDanglingArgs, Action: runGenerateInternalToken, } } @@ -51,6 +52,7 @@ func microcmdGenerateLfsJwtSecret() *cli.Command { Name: "JWT_SECRET", Aliases: []string{"LFS_JWT_SECRET"}, Usage: "Generate a new JWT_SECRET", + Before: noDanglingArgs, Action: runGenerateLfsJwtSecret, } } @@ -59,6 +61,7 @@ func microcmdGenerateSecretKey() *cli.Command { return &cli.Command{ Name: "SECRET_KEY", Usage: "Generate a new SECRET_KEY", + Before: noDanglingArgs, Action: runGenerateSecretKey, } } diff --git a/cmd/keys.go b/cmd/keys.go index 00901903f4..31be86d99b 100644 --- a/cmd/keys.go +++ b/cmd/keys.go @@ -21,7 +21,7 @@ func cmdKeys() *cli.Command { Name: "keys", Usage: "(internal) Should only be called by SSH server", Description: "Queries the Forgejo database to get the authorized command for a given ssh key fingerprint", - Before: PrepareConsoleLoggerLevel(log.FATAL), + Before: multipleBefore(noDanglingArgs, PrepareConsoleLoggerLevel(log.FATAL)), Action: runKeys, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/cmd/manager.go b/cmd/manager.go index 029329b44e..87634705ee 100644 --- a/cmd/manager.go +++ b/cmd/manager.go @@ -39,6 +39,7 @@ func subcmdShutdown() *cli.Command { Name: "debug", }, }, + Before: noDanglingArgs, Action: runShutdown, } } @@ -52,6 +53,7 @@ func subcmdRestart() *cli.Command { Name: "debug", }, }, + Before: noDanglingArgs, Action: runRestart, } } @@ -65,6 +67,7 @@ func subcmdReloadTemplates() *cli.Command { Name: "debug", }, }, + Before: noDanglingArgs, Action: runReloadTemplates, } } @@ -73,6 +76,7 @@ func subcmdFlushQueues() *cli.Command { return &cli.Command{ Name: "flush-queues", Usage: "Flush queues in the running process", + Before: noDanglingArgs, Action: runFlushQueues, Flags: []cli.Flag{ &cli.DurationFlag{ @@ -95,6 +99,7 @@ func subCmdProcesses() *cli.Command { return &cli.Command{ Name: "processes", Usage: "Display running processes within the current process", + Before: noDanglingArgs, Action: runProcesses, Flags: []cli.Flag{ &cli.BoolFlag{ diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go index c18bfa919b..02c924f4c5 100644 --- a/cmd/manager_logging.go +++ b/cmd/manager_logging.go @@ -77,6 +77,7 @@ func subcmdLogging() *cli.Command { Name: "debug", }, }, + Before: noDanglingArgs, Action: runPauseLogging, }, { Name: "resume", @@ -86,6 +87,7 @@ func subcmdLogging() *cli.Command { Name: "debug", }, }, + Before: noDanglingArgs, Action: runResumeLogging, }, { Name: "release-and-reopen", @@ -95,6 +97,7 @@ func subcmdLogging() *cli.Command { Name: "debug", }, }, + Before: noDanglingArgs, Action: runReleaseReopenLogging, }, { Name: "remove", @@ -156,6 +159,7 @@ func subcmdLogging() *cli.Command { Usage: "Compression level to use", }, }...), + Before: noDanglingArgs, Action: runAddFileLogger, }, { Name: "conn", @@ -182,6 +186,7 @@ func subcmdLogging() *cli.Command { Usage: "Host address and port to connect to (defaults to :7020)", }, }...), + Before: noDanglingArgs, Action: runAddConnLogger, }, }, @@ -197,6 +202,7 @@ func subcmdLogging() *cli.Command { Usage: "Switch off SQL logging", }, }, + Before: noDanglingArgs, Action: runSetLogSQL, }, }, diff --git a/cmd/migrate.go b/cmd/migrate.go index 5a485d17f9..af450d6139 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -7,7 +7,7 @@ import ( "context" "forgejo.org/models/db" - "forgejo.org/models/migrations" + "forgejo.org/models/gitea_migrations" "forgejo.org/modules/log" "forgejo.org/modules/setting" @@ -20,6 +20,7 @@ func cmdMigrate() *cli.Command { Name: "migrate", Usage: "Migrate the database", Description: "This is a command for migrating the database, so that you can run 'forgejo admin user create' before starting the server.", + Before: noDanglingArgs, Action: runMigrate, } } @@ -43,7 +44,7 @@ func runMigrate(stdCtx context.Context, ctx *cli.Command) error { if err != nil { return err } - return migrations.Migrate(masterEngine) + return gitea_migrations.Migrate(masterEngine) }); err != nil { log.Fatal("Failed to initialize ORM engine: %v", err) return err diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go index d741a883e3..14ce222c9b 100644 --- a/cmd/migrate_storage.go +++ b/cmd/migrate_storage.go @@ -13,7 +13,7 @@ import ( actions_model "forgejo.org/models/actions" "forgejo.org/models/db" git_model "forgejo.org/models/git" - "forgejo.org/models/migrations" + "forgejo.org/models/gitea_migrations" packages_model "forgejo.org/models/packages" repo_model "forgejo.org/models/repo" user_model "forgejo.org/models/user" @@ -32,6 +32,7 @@ func cmdMigrateStorage() *cli.Command { Name: "migrate-storage", Usage: "Migrate the storage", Description: "Copies stored files from storage configured in app.ini to parameter-configured storage", + Before: noDanglingArgs, Action: runMigrateStorage, Flags: []cli.Flag{ &cli.StringFlag{ @@ -199,7 +200,7 @@ func runMigrateStorage(stdCtx context.Context, ctx *cli.Command) error { log.Info("Configuration file: %s", setting.CustomConf) if err := db.InitEngineWithMigration(context.Background(), func(e db.Engine) error { - return migrations.Migrate(e.(*xorm.Engine)) + return gitea_migrations.Migrate(e.(*xorm.Engine)) }); err != nil { log.Fatal("Failed to initialize ORM engine: %v", err) return err diff --git a/cmd/restore_repo.go b/cmd/restore_repo.go index 0e9f0bb50a..6c9bb218d4 100644 --- a/cmd/restore_repo.go +++ b/cmd/restore_repo.go @@ -19,6 +19,7 @@ func cmdRestoreRepository() *cli.Command { Name: "restore-repo", Usage: "Restore the repository from disk", Description: "This is a command for restoring the repository data.", + Before: noDanglingArgs, Action: runRestoreRepository, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/cmd/web.go b/cmd/web.go index 87965a7c1e..12a8cac797 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -39,7 +39,7 @@ func cmdWeb() *cli.Command { Usage: "Start the Forgejo web server", Description: `The Forgejo web server is the only thing you need to run, and it takes care of all the other things for you`, - Before: PrepareConsoleLoggerLevel(log.INFO), + Before: multipleBefore(noDanglingArgs, PrepareConsoleLoggerLevel(log.INFO)), Action: runWeb, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/contrib/coverage-helper.sh b/contrib/coverage-helper.sh index ec9f5469ea..3c37e03e05 100755 --- a/contrib/coverage-helper.sh +++ b/contrib/coverage-helper.sh @@ -8,8 +8,8 @@ PS4='${BASH_SOURCE[0]}:$LINENO: ${FUNCNAME[0]}: ' # Those must be explicitly required and are excluded from the full list of packages because they # would interfere with the testing fixtures. # -excluded+='forgejo.org/models/migrations|' # must be run before database specific tests -excluded+='forgejo.org/models/forgejo_migrations|' # must be run before database specific tests +excluded+='forgejo.org/models/gitea_migrations|' # must be run before database specific tests +excluded+='forgejo.org/models/forgejo_migrations_legacy|' # must be run before database specific tests excluded+='forgejo.org/tests/integration/migration-test|' # must be run before database specific tests excluded+='forgejo.org/tests|' # only tests, no coverage to get there excluded+='forgejo.org/tests/e2e|' # JavaScript is not in scope here and if it adds coverage it should not be counted diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index b065247d91..c68d2ebcc4 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -12,10 +12,10 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; These values are environment-dependent but form the basis of a lot of values. They will be -;; reported as part of the default configuration when running `gitea help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up. +;; reported as part of the default configuration when running `forgejo help` or on start-up. The order they are emitted there is slightly different but we will list them here in the order they are set-up. ;; -;; - _`AppPath`_: This is the absolute path of the running gitea binary. -;; - _`AppWorkPath`_: This refers to "working path" of the `gitea` binary. It is determined by using the first set thing in the following hierarchy: +;; - _`AppPath`_: This is the absolute path of the running Forgejo binary. +;; - _`AppWorkPath`_: This refers to "working path" of the `forgejo` binary. It is determined by using the first set thing in the following hierarchy: ;; - The "WORK_PATH" option in "app.ini" file ;; - The `--work-path` flag passed to the binary ;; - The environment variable `$GITEA_WORK_DIR` @@ -54,7 +54,7 @@ APP_NAME = ; Forgejo: Beyond coding. We Forge. RUN_USER = ; git ;; ;; Application run mode, affects performance and debugging: "dev" or "prod", default is "prod" -;; Mode "dev" makes Gitea easier to develop and debug, values other than "dev" are treated as "prod" which is for production use. +;; Mode "dev" makes Forgejo easier to develop and debug, values other than "dev" are treated as "prod" which is for production use. ;RUN_MODE = prod ;; ;; The working directory, see the comment of AppWorkPath above @@ -127,7 +127,7 @@ RUN_USER = ; git ;; Permission for unix socket ;UNIX_SOCKET_PERMISSION = 666 ;; -;; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service. In +;; Local (DMZ) URL for Forgejo workers (such as SSH update) accessing web service. In ;; most cases you do not need to change the default value. Alter it only if ;; your SSH server node is not the same as HTTP node. For different protocol, the default ;; values are different. If `PROTOCOL` is `http+unix`, the default value is `http://unix/`. @@ -169,11 +169,11 @@ RUN_USER = ; git ;; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'. ;SSH_ROOT_PATH = ;; -;; Gitea will create a authorized_keys file by default when it is not using the internal ssh server +;; Forgejo will create a authorized_keys file by default when it is not using the internal ssh server ;; If you intend to use the AuthorizedKeysCommand functionality then you should turn this off. ;SSH_CREATE_AUTHORIZED_KEYS_FILE = true ;; -;; Gitea will create a authorized_principals file by default when it is not using the internal ssh server +;; Forgejo will create a authorized_principals file by default when it is not using the internal ssh server ;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off. ;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true ;; @@ -198,7 +198,7 @@ RUN_USER = ; git ;; default is the system temporary directory. ;SSH_KEY_TEST_PATH = ;; -;; Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Gitea does the parsing itself. +;; Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Forgejo does the parsing itself. ;SSH_KEYGEN_PATH = ;; ;; Enable SSH Authorized Key Backup when rewriting all keys, default is false @@ -220,9 +220,9 @@ RUN_USER = ; git ;; E.g."ssh- ". or "ssh- , ssh- ". ;; For more information see "TrustedUserCAKeys" in the sshd config manpages. ;SSH_TRUSTED_USER_CA_KEYS = -;; Absolute path of the `TrustedUserCaKeys` file gitea will manage. +;; Absolute path of the `TrustedUserCaKeys` file Forgejo will manage. ;; Default this `RUN_USER`/.ssh/gitea-trusted-user-ca-keys.pem -;; If you're running your own ssh server and you want to use the gitea managed file you'll also need to modify your +;; If you're running your own ssh server and you want to use the Forgejo managed file you'll also need to modify your ;; sshd_config to point to this file. The official docker image will automatically work without further configuration. ;SSH_TRUSTED_USER_CA_KEYS_FILENAME = ;; @@ -277,7 +277,7 @@ RUN_USER = ; git ;; Manual TLS settings: (Only applicable if ENABLE_ACME=false) ;; ;; Generate steps: -;; $ ./gitea cert -ca=true -duration=8760h0m0s -host=myhost.example.com +;; $ ./forgejo cert -ca=true -duration=8760h0m0s -host=myhost.example.com ;; ;; Or from a .pfx file exported from the Windows certificate store (do ;; not forget to export the private key): @@ -288,7 +288,7 @@ RUN_USER = ; git ;KEY_FILE = https/key.pem ;; ;; Root directory containing templates and static files. -;; default is the path where Gitea is executed +;; default is the path where Forgejo is executed ;STATIC_ROOT_PATH = ; Will default to the built-in value _`StaticRootPath`_ ;; ;; Default path for App data @@ -302,7 +302,7 @@ RUN_USER = ; git ;; For "serve" command it dumps to disk at PPROF_DATA_PATH as (cpuprofile|memprofile)__ ;ENABLE_PPROF = false ;; -;; PPROF_DATA_PATH, use an absolute path when you start gitea as service +;; PPROF_DATA_PATH, use an absolute path when you start Forgejo as service ;PPROF_DATA_PATH = data/tmp/pprof ; Path is relative to _`AppWorkPath`_ ;; ;; Landing page, can be "home", "explore", "organizations", "login", or any URL such as "/org/repo" or even "https://anotherwebsite.com" @@ -374,7 +374,7 @@ DB_TYPE = sqlite3 ;USER = root ;PASSWD = ;Use PASSWD = `your password` for quoting if you use special characters in the password. ;SSL_MODE = false ; either "false" (default), "true", or "skip-verify" -;CHARSET_COLLATION = ; Empty as default, Gitea will try to find a case-sensitive collation. Don't change it unless you clearly know what you need. +;CHARSET_COLLATION = ; Empty as default, Forgejo will try to find a case-sensitive collation. Don't change it unless you clearly know what you need. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; @@ -440,7 +440,7 @@ SECRET_KEY = ;; This key is VERY IMPORTANT. If you lose it, the data encrypted by it (like 2FA secret) can't be decrypted anymore. ;SECRET_KEY_URI = file:/etc/gitea/secret_key ;; -;; Secret used to validate communication within Gitea binary. +;; Secret used to validate communication within Forgejo binary. INTERNAL_TOKEN = ;; ;; Alternative location to specify internal token, instead of this file; you cannot specify both this and INTERNAL_TOKEN, and must pick one @@ -474,9 +474,9 @@ INTERNAL_TOKEN = ;; ;; Set to false to allow users with git hook privileges to create custom git hooks. ;; Custom git hooks can be used to perform arbitrary code execution on the host operating system. -;; This enables the users to access and modify this config file and the Gitea database and interrupt the Gitea service. -;; By modifying the Gitea database, users can gain Gitea administrator privileges. -;; It also enables them to access other resources available to the user on the operating system that is running the Gitea instance and perform arbitrary actions in the name of the Gitea OS user. +;; This enables the users to access and modify this config file and the Forgejo database and interrupt the Forgejo service. +;; By modifying the Forgejo database, users can gain Forgejo administrator privileges. +;; It also enables them to access other resources available to the user on the operating system that is running the Forgejo instance and perform arbitrary actions in the name of the Forgejo OS user. ;; WARNING: This maybe harmful to you website or your operating system. ;; WARNING: Setting this to true does not change existing hooks in git repos; adjust it before if necessary. ;DISABLE_GIT_HOOKS = true @@ -484,7 +484,7 @@ INTERNAL_TOKEN = ;; Set to true to disable webhooks feature. ;DISABLE_WEBHOOKS = false ;; -;; Set to false to allow pushes to gitea repositories despite having an incomplete environment - NOT RECOMMENDED +;; Set to false to allow pushes to Forgejo repositories despite having an incomplete environment - NOT RECOMMENDED ;ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET = true ;; ;;Comma separated list of character classes required to pass minimum complexity. @@ -545,7 +545,7 @@ ENABLED = true ;; The file must contain a RSA or ECDSA private key in the PKCS8 format. If no key exists a 4096 bit key will be created for you. ;JWT_SIGNING_PRIVATE_KEY_FILE = jwt/private.pem ;; -;; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://docs.gitea.io/en-us/command-line/#generate +;; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://forgejo.org/docs/latest/admin/command-line/#generate-secret ;; This setting is only needed if JWT_SIGNING_ALGORITHM is set to HS256, HS384 or HS512. ;JWT_SECRET = ;; @@ -670,7 +670,7 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; The path of git executable. If empty, Gitea searches through the PATH environment. +;; The path of git executable. If empty, Forgejo searches through the PATH environment. ;PATH = ;; ;; The HOME directory for Git @@ -755,17 +755,17 @@ LEVEL = Info ;; Whether a new user needs to be confirmed manually after registration. (Requires `REGISTER_EMAIL_CONFIRM` to be disabled.) ;REGISTER_MANUAL_CONFIRM = false ;; -;; List of domain names that are allowed to be used to register on a Gitea instance, wildcard is supported -;; eg: gitea.io,example.com,*.mydomain.com +;; List of domain names that are allowed to be used to register on a Forgejo instance, wildcard is supported +;; eg: forgejo.org,example.com,*.mydomain.com ;EMAIL_DOMAIN_ALLOWLIST = ;; -;; Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance, wildcard is supported +;; Comma-separated list of domain names that are not allowed to be used to register on a Forgejo instance, wildcard is supported ;EMAIL_DOMAIN_BLOCKLIST = ;; ;; Disallow registration, only allow admins to create accounts. ;DISABLE_REGISTRATION = false ;; -;; Allow registration only using gitea itself, it works only when DISABLE_REGISTRATION is false +;; Allow registration only using Forgejo itself, it works only when DISABLE_REGISTRATION is false ;ALLOW_ONLY_INTERNAL_REGISTRATION = false ;; ;; Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false @@ -777,7 +777,7 @@ LEVEL = Info ;; Mail notification ;ENABLE_NOTIFY_MAIL = false ;; -;; This setting enables gitea to be signed in with HTTP BASIC Authentication using the user's password +;; This setting enables Forgejo to be signed in with HTTP BASIC Authentication using the user's password ;; If you set this to false you will not be able to access the tokens endpoints on the API with your password ;; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token ;ENABLE_BASIC_AUTHENTICATION = true @@ -1012,7 +1012,7 @@ LEVEL = Info ;; Close issues as long as a commit on any branch marks it as fixed ;DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false ;; -;; Allow users to push local repositories to Gitea and have them automatically created for a user or an org +;; Allow users to push local repositories to Forgejo and have them automatically created for a user or an org ;ENABLE_PUSH_CREATE_USER = false ;ENABLE_PUSH_CREATE_ORG = false ;; @@ -1076,7 +1076,7 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; Path for local repository copy. Defaults to `tmp/local-repo` (content gets deleted on gitea restart) +;; Path for local repository copy. Defaults to `tmp/local-repo` (content gets deleted on Forgejo restart) ;LOCAL_COPY_PATH = tmp/local-repo ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1088,7 +1088,7 @@ LEVEL = Info ;; Whether repository file uploads are enabled. Defaults to `true` ;ENABLED = true ;; -;; Path for uploads. Defaults to `data/tmp/uploads` (content gets deleted on gitea restart) +;; Path for uploads. Defaults to `data/tmp/uploads` (content gets deleted on Forgejo restart) ;TEMP_PATH = data/tmp/uploads ;; ;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. @@ -1187,7 +1187,7 @@ LEVEL = Info ;; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter ;DEFAULT_TRUST_MODEL = collaborator ;; -;; Determines when gitea should sign the initial commit when creating a repository +;; Determines when Forgejo should sign the initial commit when creating a repository ;; Either: ;; - never ;; - pubkey: only sign if the user has a pubkey @@ -1301,7 +1301,7 @@ LEVEL = Info ;; Whether the email of the user should be shown in the Explore Users page ;SHOW_USER_EMAIL = true ;; -;; Set the default theme for the Gitea install +;; Set the default theme for the Forgejo install ;DEFAULT_THEME = forgejo-auto ;; ;; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`. @@ -1338,10 +1338,6 @@ LEVEL = Info ;; Change the sort type of the explore pages. ;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest". ;EXPLORE_PAGING_DEFAULT_SORT = recentupdate -;; -;; The tense all timestamps should be rendered in. Possible values are `absolute` time (i.e. 1970-01-01, 11:59) and `mixed`. -;; `mixed` means most timestamps are rendered in relative time (i.e. 2 days ago). -;PREFERRED_TIMESTAMP_TENSE = mixed ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1710,10 +1706,10 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; NOTICE: this section is for Gitea 1.18 and later. If you are using Gitea 1.17 or older, +;; NOTICE: this section is for Forgejo 1.18 and later. If you are using Forgejo 1.17 or older, ;; please refer to -;; https://github.com/go-gitea/gitea/blob/release/v1.17/custom/conf/app.example.ini -;; https://github.com/go-gitea/gitea/blob/release/v1.17/docs/content/doc/advanced/config-cheat-sheet.en-us.md +;; https://codeberg.org/forgejo/forgejo/src/commit/8769df117d6cc2f4ab00d6e1d54ef4241d063f11/custom/conf/app.example.ini +;; https://codeberg.org/forgejo/forgejo/src/commit/8769df117d6cc2f4ab00d6e1d54ef4241d063f11/docs/content/doc/advanced/config-cheat-sheet.en-us.md ;; ;ENABLED = false ;; @@ -1766,7 +1762,7 @@ LEVEL = Info ;; Sometimes it is helpful to use a different address on the envelope. Set this to use ENVELOPE_FROM as the from on the envelope. Set to `<>` to send an empty address. ;ENVELOPE_FROM = ;; -;; If gitea sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `Mister X (by CodeIt) `, +;; If Forgejo sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `John Doe (by AppName) `, ;; set it to `{{ .DisplayName }} (by {{ .AppName }})`. Available Variables: `.DisplayName`, `.AppName` and `.Domain`. ;FROM_DISPLAY_NAME_FORMAT = {{ .DisplayName }} ;; @@ -1927,7 +1923,7 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; How Gitea deals with missing repository avatars +;; How Forgejo deals with missing repository avatars ;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used ;REPOSITORY_AVATAR_FALLBACK = none ;REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png @@ -2045,7 +2041,7 @@ LEVEL = Info ;; ;; Setting this to true will enable all cron tasks periodically with default settings. ;ENABLED = false -;; Setting this to true will run all enabled cron tasks when Gitea starts. +;; Setting this to true will run all enabled cron tasks when Forgejo starts. ;RUN_AT_START = false ;; ;; Note: ``SCHEDULE`` accept formats @@ -2085,7 +2081,7 @@ LEVEL = Info ;SCHEDULE = @every 10m ;; Enable running Update mirrors task periodically. ;ENABLED = true -;; Run Update mirrors task when Gitea starts. +;; Run Update mirrors task when Forgejo starts. ;RUN_AT_START = false ;; Notice if not success ;NOTICE_ON_SUCCESS = false @@ -2106,7 +2102,7 @@ LEVEL = Info ;SCHEDULE = @midnight ;; Enable running Repository health check task periodically. ;ENABLED = true -;; Run Repository health check task when Gitea starts. +;; Run Repository health check task when Forgejo starts. ;RUN_AT_START = false ;; Notice if not success ;NOTICE_ON_SUCCESS = false @@ -2124,7 +2120,7 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Enable running check repository statistics task periodically. ;ENABLED = true -;; Run check repository statistics task when Gitea starts. +;; Run check repository statistics task when Forgejo starts. ;RUN_AT_START = true ;; Notice if not success ;NOTICE_ON_SUCCESS = false @@ -2279,7 +2275,7 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Update the '.ssh/authorized_keys' file with Gitea SSH keys +;; Update the '.ssh/authorized_keys' file with Forgejo SSH keys ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[cron.resync_all_sshkeys] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -2352,7 +2348,7 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Check for new Gitea versions +;; Check for new Forgejo versions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[cron.update_checker] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -2453,7 +2449,7 @@ LEVEL = Info ;[other] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Show version information about Gitea and Go in the footer +;; Show version information about Forgejo and Go in the footer ;SHOW_FOOTER_VERSION = true ;; Show template execution time in the footer ;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true @@ -2700,7 +2696,7 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; settings for Gitea's LFS client (eg: mirroring an upstream lfs endpoint) +;; settings for Forgejo's LFS client (eg: mirroring an upstream lfs endpoint) ;; ;[lfs_client] ;; Limit the number of pointers in each batch request to this number @@ -2786,6 +2782,10 @@ LEVEL = Info ;SKIP_WORKFLOW_STRINGS = [skip ci],[ci skip],[no ci],[skip actions],[actions skip] ;; Limit on inputs for manual / workflow_dispatch triggers, default is 10 ;LIMIT_DISPATCH_INPUTS = 10 +;; Support queuing workflow jobs, by setting `concurrency.group` & `concurrency.cancel-in-progress: false`, can increase +;; server and database workload due to more complex database queries and more frequent server task querying; this +;; feature can be disabled to reduce performance impact +;CONCURRENCY_GROUP_QUEUE_ENABLED = true ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/eslint.config.mjs b/eslint.config.mjs index 83a6b6bee1..61987aef14 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -58,7 +58,14 @@ export default tseslint.config( sourceType: 'module', }, rules: { - '@typescript-eslint/no-unused-vars': 'off', // TODO: enable this rule again + '@typescript-eslint/no-unused-vars': [2, { + args: 'all', + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + ignoreRestSiblings: false, + }], '@eslint-community/eslint-comments/disable-enable-pair': [2], '@eslint-community/eslint-comments/no-aggregating-enable': [2], @@ -539,14 +546,7 @@ export default tseslint.config( 'no-unused-labels': [2], 'no-unused-private-class-members': [2], - 'no-unused-vars': [2, { - args: 'all', - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - caughtErrorsIgnorePattern: '^_', - destructuredArrayIgnorePattern: '^_', - ignoreRestSiblings: false, - }], + 'no-unused-vars': [0], 'no-use-before-define': [2, { functions: false, @@ -703,7 +703,6 @@ export default tseslint.config( 'sonarjs/no-inverted-boolean-check': [2], 'sonarjs/no-nested-switch': [0], 'sonarjs/no-nested-template-literals': [0], - 'sonarjs/no-one-iteration-loop': [2], 'sonarjs/no-redundant-boolean': [2], 'sonarjs/no-redundant-jump': [2], 'sonarjs/no-same-line-conditional': [2], @@ -1083,6 +1082,7 @@ export default tseslint.config( '@vitest/no-standalone-expect': [0], '@vitest/no-test-prefixes': [0], '@vitest/no-test-return-statement': [0], + '@vitest/prefer-called-exactly-once-with': [2], '@vitest/prefer-called-with': [0], '@vitest/prefer-comparison-matcher': [0], '@vitest/prefer-each': [0], @@ -1090,6 +1090,7 @@ export default tseslint.config( '@vitest/prefer-expect-resolves': [0], '@vitest/prefer-hooks-in-order': [0], '@vitest/prefer-hooks-on-top': [2], + '@vitest/prefer-import-in-mock': [0], '@vitest/prefer-lowercase-title': [0], '@vitest/prefer-mock-promise-shorthand': [0], '@vitest/prefer-snapshot-hint': [0], diff --git a/go.mod b/go.mod index e49ec8055f..5f07864b39 100644 --- a/go.mod +++ b/go.mod @@ -2,23 +2,25 @@ module forgejo.org go 1.24.0 -toolchain go1.24.7 +toolchain go1.25.3 require ( - code.forgejo.org/f3/gof3/v3 v3.11.0 + code.forgejo.org/f3/gof3/v3 v3.11.1 code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 code.forgejo.org/forgejo/go-rpmutils v1.0.0 code.forgejo.org/forgejo/levelqueue v1.0.0 code.forgejo.org/forgejo/reply v1.0.2 - code.forgejo.org/forgejo/runner/v11 v11.1.1 + code.forgejo.org/forgejo/runner/v11 v11.2.0 code.forgejo.org/go-chi/binding v1.0.1 code.forgejo.org/go-chi/cache v1.0.1 code.forgejo.org/go-chi/captcha v1.0.2 code.forgejo.org/go-chi/session v1.0.2 code.gitea.io/actions-proto-go v0.4.0 code.gitea.io/sdk/gitea v0.21.0 + code.superseriousbusiness.org/exif-terminator v0.11.0 + code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 - connectrpc.com/connect v1.18.1 + connectrpc.com/connect v1.19.1 github.com/42wim/httpsig v1.2.3 github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920 github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 @@ -34,6 +36,7 @@ require ( github.com/djherbis/buffer v1.2.0 github.com/djherbis/nio/v3 v3.0.1 github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 + github.com/dsoprea/go-exif/v3 v3.0.1 github.com/dustin/go-humanize v1.0.1 github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 github.com/emersion/go-imap v1.2.1 @@ -47,7 +50,7 @@ require ( github.com/go-co-op/gocron v1.37.0 github.com/go-enry/go-enry/v2 v2.9.2 github.com/go-ldap/ldap/v3 v3.4.6 - github.com/go-openapi/spec v0.21.0 + github.com/go-openapi/spec v0.22.0 github.com/go-sql-driver/mysql v1.9.3 github.com/go-webauthn/webauthn v0.14.0 github.com/gobwas/glob v0.2.3 @@ -56,7 +59,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/google/go-github/v64 v64.0.0 - github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 + github.com/google/pprof v0.0.0-20250923004556-9e5a51aed1e8 github.com/google/uuid v1.6.0 github.com/gorilla/feeds v1.2.0 github.com/gorilla/sessions v1.4.0 @@ -67,14 +70,14 @@ require ( github.com/jhillyerd/enmime/v2 v2.2.0 github.com/json-iterator/go v1.1.12 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/klauspost/compress v1.18.0 + github.com/klauspost/compress v1.18.1 github.com/klauspost/cpuid/v2 v2.2.11 github.com/lib/pq v1.10.9 github.com/markbates/goth v1.80.0 github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-sqlite3 v1.14.32 github.com/meilisearch/meilisearch-go v0.34.0 - github.com/mholt/archives v0.1.4 + github.com/mholt/archives v0.1.5 github.com/microcosm-cc/bluemonday v1.0.27 github.com/minio/minio-go/v7 v7.0.95 github.com/msteinert/pam/v2 v2.1.0 @@ -96,17 +99,17 @@ require ( github.com/yohcop/openid-go v1.0.1 github.com/yuin/goldmark v1.7.13 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc - gitlab.com/gitlab-org/api/client-go v0.130.1 + gitlab.com/gitlab-org/api/client-go v0.143.2 go.uber.org/mock v0.6.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.42.0 - golang.org/x/image v0.31.0 - golang.org/x/net v0.44.0 - golang.org/x/oauth2 v0.31.0 + golang.org/x/crypto v0.43.0 + golang.org/x/image v0.32.0 + golang.org/x/net v0.46.0 + golang.org/x/oauth2 v0.32.0 golang.org/x/sync v0.17.0 - golang.org/x/sys v0.36.0 - golang.org/x/text v0.29.0 - google.golang.org/protobuf v1.36.9 + golang.org/x/sys v0.37.0 + golang.org/x/text v0.30.0 + google.golang.org/protobuf v1.36.10 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/ini.v1 v1.67.0 mvdan.cc/xurls/v2 v2.5.0 @@ -116,6 +119,7 @@ require ( require ( cloud.google.com/go/compute/metadata v0.6.0 // indirect + code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 // indirect dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.1.0 // indirect git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect @@ -145,7 +149,7 @@ require ( github.com/blevesearch/zapx/v14 v14.4.2 // indirect github.com/blevesearch/zapx/v15 v15.4.2 // indirect github.com/blevesearch/zapx/v16 v16.2.4 // indirect - github.com/bmatcuk/doublestar/v4 v4.8.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/sevenzip v1.6.1 // indirect github.com/bodgit/windows v1.0.1 // indirect @@ -160,6 +164,10 @@ require ( github.com/davidmz/go-pageant v1.0.2 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb // indirect + github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect + github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c // indirect + github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.18.0 // indirect @@ -167,17 +175,26 @@ require ( github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-enry/go-oniguruma v1.2.1 // indirect + github.com/go-errors/errors v1.4.2 // indirect github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect - github.com/go-git/go-git/v5 v5.16.2 // indirect + github.com/go-git/go-git/v5 v5.16.3 // indirect github.com/go-ini/ini v1.67.0 // indirect - github.com/go-openapi/jsonpointer v0.21.1 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/swag/conv v0.25.1 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/swag/jsonutils v0.25.1 // indirect + github.com/go-openapi/swag/loading v0.25.1 // indirect + github.com/go-openapi/swag/stringutils v0.25.1 // indirect + github.com/go-openapi/swag/typeutils v0.25.1 // indirect + github.com/go-openapi/swag/yamlutils v0.25.1 // indirect github.com/go-webauthn/x v0.1.25 // indirect + github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect @@ -189,7 +206,7 @@ require ( github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect @@ -198,7 +215,7 @@ require ( github.com/mailru/easyjson v0.9.0 // indirect github.com/markbates/going v1.0.3 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.17 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mholt/acmez/v3 v3.1.2 // indirect github.com/miekg/dns v1.1.63 // indirect @@ -212,7 +229,7 @@ require ( github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nwaples/rardecode/v2 v2.1.1 // indirect + github.com/nwaples/rardecode/v2 v2.2.0 // indirect github.com/olekukonko/errors v1.1.0 // indirect github.com/olekukonko/ll v0.0.9 // indirect github.com/olekukonko/tablewriter v1.0.7 // indirect @@ -225,13 +242,13 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/rhysd/actionlint v1.7.7 // indirect + github.com/rhysd/actionlint v1.7.8 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/sorairolake/lzip-go v0.3.8 // indirect - github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/afero v1.15.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 @@ -243,12 +260,14 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/time v0.13.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.38.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -260,4 +279,4 @@ replace github.com/gliderlabs/ssh => code.forgejo.org/forgejo/ssh v0.0.0-2024121 replace git.sr.ht/~mariusor/go-xsd-duration => code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078 -replace xorm.io/xorm v1.3.9 => code.forgejo.org/xorm/xorm v1.3.9-forgejo.1 +replace xorm.io/xorm v1.3.9 => code.forgejo.org/xorm/xorm v1.3.9-forgejo.3 diff --git a/go.sum b/go.sum index e88102cb52..e3f94068cb 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -code.forgejo.org/f3/gof3/v3 v3.11.0 h1:f/xToKwqTgxG6PYxvewywjDQyCcyHEEJ6sZqUitFsAE= -code.forgejo.org/f3/gof3/v3 v3.11.0/go.mod h1:4FaRUNSQGBiD1M0DuB0yNv+Z2wMtlOeckgygHSSq4KQ= +code.forgejo.org/f3/gof3/v3 v3.11.1 h1:c0vE8XvqpbXuSv8gzttn96k5T2FQi0u9bYnux46qSAs= +code.forgejo.org/f3/gof3/v3 v3.11.1/go.mod h1:1p2UKrqZiwxKneQF2DKrMnc403YIgR/lfcfvadZtmDs= 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/go-rpmutils v1.0.0 h1:RZGGeKt70p/WaIEL97pyT6uiiEIoN8/aLmS5Z6WmX0M= @@ -28,8 +28,8 @@ code.forgejo.org/forgejo/levelqueue v1.0.0 h1:9krYpU6BM+j/1Ntj6m+VCAIu0UNnne1/Uf code.forgejo.org/forgejo/levelqueue v1.0.0/go.mod h1:fmG6zhVuqim2rxSFOoasgXO8V2W/k9U31VVYqLIRLhQ= code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ= code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U= -code.forgejo.org/forgejo/runner/v11 v11.1.1 h1:CoSfxBOLKLMJZq5VhKd5ZIUc3tCf04iyFx926s+zaMA= -code.forgejo.org/forgejo/runner/v11 v11.1.1/go.mod h1:9f0D2EG7uabL+cv71SWHKrGgo2vmLpvko0QLmtn3RDE= +code.forgejo.org/forgejo/runner/v11 v11.2.0 h1:juvrGBxQG3bNDKDc+3SorYY0sx9sgPcta122K/l+Ids= +code.forgejo.org/forgejo/runner/v11 v11.2.0/go.mod h1:hCiUsUtsvV7wICou9Gk2aV0kGn87NhhirCcx7WRqfZw= code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616 h1:kEZL84+02jY9RxXM4zHBWZ3Fml0B09cmP1LGkDsCfIA= code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= code.forgejo.org/go-chi/binding v1.0.1 h1:coKNI+X1NzRN7X85LlrpvBRqk0TXpJ+ja28vusQWEuY= @@ -40,16 +40,22 @@ code.forgejo.org/go-chi/captcha v1.0.2 h1:vyHDPXkpjDv8bLO9NqtWzZayzstD/WpJ5xwEkA code.forgejo.org/go-chi/captcha v1.0.2/go.mod h1:lxiPLcJ76UCZHoH31/Wbum4GUi2NgjfFZLrJkKv1lLE= code.forgejo.org/go-chi/session v1.0.2 h1:pG+AXre9L9VXJmTaADXkmeEPuRalhmBXyv6tG2Rvjcc= code.forgejo.org/go-chi/session v1.0.2/go.mod h1:HnEGyBny7WPzCiVLP2vzL5ssma+3gCSl/vLpuVNYrqc= -code.forgejo.org/xorm/xorm v1.3.9-forgejo.1 h1:jU5CICzbkpqol6qTa4yctLuS0BI3D2mVCx7q6ogK/gE= -code.forgejo.org/xorm/xorm v1.3.9-forgejo.1/go.mod h1:0q+miQUSpTlsgMw2blcO9a87GB5WyatiX3GuwBca31w= +code.forgejo.org/xorm/xorm v1.3.9-forgejo.3 h1:7KW1+3cr2YIZCS85TM+imdsUe8OdNsfV3F8iP2t36rM= +code.forgejo.org/xorm/xorm v1.3.9-forgejo.3/go.mod h1:5ouTxqMcalQUvlBpQynRpzu/44GwaMpkA1nU+encsDE= code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU= code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas= code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4= code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA= +code.superseriousbusiness.org/exif-terminator v0.11.0 h1:Hof0MCcsa+1fS17gf86fTTZ8AQnMY9h9kzcc+2C6mVg= +code.superseriousbusiness.org/exif-terminator v0.11.0/go.mod h1:9sutT1axa/kSdlPLlRFjCNKmyo/KNx8eX3XZvWBlAEY= +code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0 h1:r9uq8StaSHYKJ8DklR9Xy+E9c40G1Z8yj5TRGi8L6+4= +code.superseriousbusiness.org/go-jpeg-image-structure/v2 v2.3.0/go.mod h1:IK1OlR6APjVB3E9tuYGvf0qXMrwP+TrzcHS5rf4wffQ= +code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0 h1:I512jiIeXDC4//2BeSPrRM2ZS4wpBKUaPeTPxakMNGA= +code.superseriousbusiness.org/go-png-image-structure/v2 v2.3.0/go.mod h1:SNHomXNW88o1pFfLHpD4KsCZLfcr4z5dm+xcX5SV10A= codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY= codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM= -connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw= -connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= +connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= +connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -143,8 +149,8 @@ github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFx github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw= github.com/blevesearch/zapx/v16 v16.2.4 h1:tGgfvleXTAkwsD5mEzgM3zCS/7pgocTCnO1oyAUjlww= github.com/blevesearch/zapx/v16 v16.2.4/go.mod h1:Rti/REtuuMmzwsI8/C/qIzRaEoSK/wiFYw5e5ctUKKs= -github.com/bmatcuk/doublestar/v4 v4.8.0 h1:DSXtrypQddoug1459viM9X9D3dp1Z7993fw36I2kNcQ= -github.com/bmatcuk/doublestar/v4 v4.8.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE= +github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4= @@ -209,6 +215,28 @@ github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cn github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E= +github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8= +github.com/dsoprea/go-exif/v3 v3.0.0-20210428042052-dca55bf8ca15/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= +github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= +github.com/dsoprea/go-exif/v3 v3.0.0-20221003160559-cf5cd88aa559/go.mod h1:rW6DMEv25U9zCtE5ukC7ttBRllXj7g7TAHl7tQrT5No= +github.com/dsoprea/go-exif/v3 v3.0.0-20221003171958-de6cb6e380a8/go.mod h1:akyZEJZ/k5bmbC9gA612ZLQkcED8enS9vuTiuAkENr0= +github.com/dsoprea/go-exif/v3 v3.0.1 h1:/IE4iW7gvY7BablV1XY0unqhMv26EYpOquVMwoBo/wc= +github.com/dsoprea/go-exif/v3 v3.0.1/go.mod h1:10HkA1Wz3h398cDP66L+Is9kKDmlqlIJGPv8pk4EWvc= +github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb h1:gwjJjUr6FY7zAWVEueFPrcRHhd9+IK81TcItbqw2du4= +github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb/go.mod h1:kYIdx9N9NaOyD7U6D+YtExN7QhRm+5kq7//yOsRXQtM= +github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= +github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= +github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg= +github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= +github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c h1:7j5aWACOzROpr+dvMtu8GnI97g9ShLWD72XIELMgn+c= +github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c/go.mod h1:pqKB+ijp27cEcrHxhXVgUUMlSDRuGJJp1E+20Lj5H0E= +github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8= +github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU= +github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LVjRU0RNUuMDqkPTxcALio0LWPFPXxxFCvVGVAwEpFc= +github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU= +github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw= +github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/editorconfig/editorconfig-core-go/v2 v2.6.3 h1:XVUp6qW3BIkmM3/1EkrHpa6bL56APOynfXcZEmIgOhs= @@ -258,6 +286,11 @@ github.com/go-enry/go-enry/v2 v2.9.2 h1:giOQAtCgBX08kosrX818DCQJTCNtKwoPBGu0qb6n github.com/go-enry/go-enry/v2 v2.9.2/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8= github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo= github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= +github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -266,22 +299,36 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM= -github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8= +github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= -github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= -github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= -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-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/spec v0.22.0 h1:xT/EsX4frL3U09QviRIZXvkh80yibxQmtoEvyqug0Tw= +github.com/go-openapi/spec v0.22.0/go.mod h1:K0FhKxkez8YNS94XzF8YKEMULbFrRw4m15i2YUht4L0= +github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0= +github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8= +github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg= +github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw= +github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc= +github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw= +github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg= +github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA= +github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= +github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= +github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= 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= @@ -291,6 +338,8 @@ github.com/go-webauthn/webauthn v0.14.0 h1:ZLNPUgPcDlAeoxe+5umWG/tEeCoQIDr7gE2Zx github.com/go-webauthn/webauthn v0.14.0/go.mod h1:QZzPFH3LJ48u5uEPAu+8/nWJImoLBWM7iAH/kSVSo6k= github.com/go-webauthn/x v0.1.25 h1:g/0noooIGcz/yCVqebcFgNnGIgBlJIccS+LYAa+0Z88= github.com/go-webauthn/x v0.1.25/go.mod h1:ieblaPY1/BVCV0oQTsA/VAo08/TWayQuJuo5Q+XxmTY= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= @@ -308,6 +357,10 @@ github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9v github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= +github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -360,8 +413,8 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ= -github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/pprof v0.0.0-20250923004556-9e5a51aed1e8 h1:ZI8gCoCjGzPsum4L21jHdQs8shFBIQih1TM9Rd/c+EQ= +github.com/google/pprof v0.0.0-20250923004556-9e5a51aed1e8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -387,8 +440,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -404,6 +457,8 @@ github.com/inbucket/html2text v0.9.0 h1:ULJmVcBEMAcmLE+/rN815KG1Fx6+a4HhbUxiDiN+ github.com/inbucket/html2text v0.9.0/go.mod h1:QDaumzl+/OzlSVbNohhmg+yAy5pKjUjzCKW2BMvztKE= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jhillyerd/enmime/v2 v2.2.0 h1:Pe35MB96eZK5Q0XjlvPftOgWypQpd1gcbfJKAt7rsB8= github.com/jhillyerd/enmime/v2 v2.2.0/go.mod h1:SOBXlCemjhiV2DvHhAKnJiWrtJGS/Ffuw4Iy7NjBTaI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -418,8 +473,8 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= +github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU= @@ -453,8 +508,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ= +github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= @@ -463,8 +518,8 @@ github.com/meilisearch/meilisearch-go v0.34.0 h1:P+Ohdx4/PCxXaoI5wNi0LMwPkuiNrF/ github.com/meilisearch/meilisearch-go v0.34.0/go.mod h1:cUVJZ2zMqTvvwIMEEAdsWH+zrHsrLpAw6gm8Lt1MXK0= github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= -github.com/mholt/archives v0.1.4 h1:sU+/lLNgafUontWFv3AVwO8VUWye3rrtN6hgC2dU11c= -github.com/mholt/archives v0.1.4/go.mod h1:I2ia+SQTtQHej9w1GZM/mz7qfdgQv+BHr3hEKqDcGuk= +github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ= +github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= @@ -498,8 +553,8 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0= github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48= -github.com/nwaples/rardecode/v2 v2.1.1 h1:OJaYalXdliBUXPmC8CZGQ7oZDxzX1/5mQmgn0/GASew= -github.com/nwaples/rardecode/v2 v2.1.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= +github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A= +github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -553,8 +608,8 @@ github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhi github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rhysd/actionlint v1.7.7 h1:0KgkoNTrYY7vmOCs9BW2AHxLvvpoY9nEUzgBHiPUr0k= -github.com/rhysd/actionlint v1.7.7/go.mod h1:AE6I6vJEkNaIfWqC2GNE5spIJNhxf8NCtLEKU4NnUXg= +github.com/rhysd/actionlint v1.7.8 h1:3d+N9ourgAxVYG4z2IFxFIk/YiT6V+VnKASfXGwT60E= +github.com/rhysd/actionlint v1.7.8/go.mod h1:3kiS6egcbXG+vQsJIhFxTz+UKaF1JprsE0SKrpCZKvU= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -580,8 +635,8 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik= github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -632,8 +687,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.130.1 h1:1xF5C5Zq3sFeNg3PzS2z63oqrxifne3n/OnbI7nptRc= -gitlab.com/gitlab-org/api/client-go v0.130.1/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM= +gitlab.com/gitlab-org/api/client-go v0.143.2 h1:tfmUW8u+G/DGKOB/FDR0c06f0RVUAEe0ym8WpLoiHXI= +gitlab.com/gitlab-org/api/client-go v0.143.2/go.mod h1:gJn5yLx9vYGXr73Yv0ueHWCVl+fL8iUOgJFxC7qV+iM= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -655,6 +710,8 @@ go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s= +go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -669,8 +726,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -683,8 +740,8 @@ golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/y golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA= -golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA= +golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ= +golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -706,8 +763,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -722,11 +779,16 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= @@ -734,15 +796,15 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= -golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -780,12 +842,15 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -793,8 +858,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -804,8 +869,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -819,12 +884,12 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= -golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -854,8 +919,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -899,8 +964,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -921,8 +986,10 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -933,14 +1000,14 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= -modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A= +modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY= -modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= +modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4= +modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/models/actions/run.go b/models/actions/run.go index b5f79a0cb3..2d83806b98 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -25,11 +25,23 @@ import ( "xorm.io/builder" ) +type ConcurrencyMode int + +const ( + // Don't enforce concurrency control. Note that you won't find `UnlimitedConcurrency` implemented directly in the + // code; setting it on an `ActionRun` prevents the other limiting behaviors. + UnlimitedConcurrency ConcurrencyMode = iota + // Queue behind other jobs with the same concurrency group + QueueBehind + // Cancel other jobs with the same concurrency group + CancelInProgress +) + // ActionRun represents a run of a workflow file type ActionRun struct { ID int64 Title string - RepoID int64 `xorm:"index unique(repo_index)"` + RepoID int64 `xorm:"index unique(repo_index) index(concurrency)"` Repo *repo_model.Repository `xorm:"-"` OwnerID int64 `xorm:"index"` WorkflowID string `xorm:"index"` // the name of workflow file @@ -56,6 +68,11 @@ type ActionRun struct { Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` NotifyEmail bool + + ConcurrencyGroup string `xorm:"'concurrency_group' index(concurrency)"` + ConcurrencyType ConcurrencyMode + + PreExecutionError string `xorm:"LONGTEXT"` // used to report errors that blocked execution of a workflow } func init() { @@ -163,6 +180,24 @@ func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, err return nil, fmt.Errorf("event %s is not a pull request event", run.Event) } +func (run *ActionRun) SetConcurrencyGroup(concurrencyGroup string) { + // Concurrency groups are case insensitive identifiers, implemented by collapsing case here. Unfortunately the + // `ConcurrencyGroup` field can't be made a private field because xorm doesn't map those fields -- using + // `SetConcurrencyGroup` is required for consistency but not enforced at compile-time. + run.ConcurrencyGroup = strings.ToLower(concurrencyGroup) +} + +func (run *ActionRun) SetDefaultConcurrencyGroup() { + // Before ConcurrencyGroups were supported, Forgejo would automatically cancel runs with matching git refs, workflow + // IDs, and trigger events. For backwards compatibility we emulate that behavior: + run.SetConcurrencyGroup(fmt.Sprintf( + "%s_%s_%s__auto", + run.Ref, + run.WorkflowID, + run.TriggerEvent, + )) +} + func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error { _, err := db.GetEngine(ctx).ID(repo.ID). SetExpr("num_action_runs", @@ -183,6 +218,7 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err ), ), ). + Cols("num_action_runs", "num_closed_action_runs"). Update(repo) return err } diff --git a/models/actions/run_list.go b/models/actions/run_list.go index 92be510569..174d2aa70c 100644 --- a/models/actions/run_list.go +++ b/models/actions/run_list.go @@ -5,6 +5,7 @@ package actions import ( "context" + "strings" "forgejo.org/models/db" repo_model "forgejo.org/models/repo" @@ -64,14 +65,15 @@ func (runs RunList) LoadRepos(ctx context.Context) error { type FindRunOptions struct { db.ListOptions - RepoID int64 - OwnerID int64 - WorkflowID string - Ref string // the commit/tag/… that caused this workflow - TriggerUserID int64 - TriggerEvent webhook_module.HookEventType - Approved bool // not util.OptionalBool, it works only when it's true - Status []Status + RepoID int64 + OwnerID int64 + WorkflowID string + Ref string // the commit/tag/… that caused this workflow + TriggerUserID int64 + TriggerEvent webhook_module.HookEventType + Approved bool // not util.OptionalBool, it works only when it's true + Status []Status + ConcurrencyGroup string } func (opts FindRunOptions) ToConds() builder.Cond { @@ -100,6 +102,9 @@ func (opts FindRunOptions) ToConds() builder.Cond { if opts.TriggerEvent != "" { cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent}) } + if opts.ConcurrencyGroup != "" { + cond = cond.And(builder.Eq{"concurrency_group": strings.ToLower(opts.ConcurrencyGroup)}) + } return cond } diff --git a/models/actions/run_test.go b/models/actions/run_test.go index c9a552a2b2..834d1595ae 100644 --- a/models/actions/run_test.go +++ b/models/actions/run_test.go @@ -5,7 +5,84 @@ package actions import ( "testing" + + repo_model "forgejo.org/models/repo" + "forgejo.org/models/unittest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestGetRunBefore(t *testing.T) { } + +func TestSetConcurrencyGroup(t *testing.T) { + run := ActionRun{} + run.SetConcurrencyGroup("abc123") + assert.Equal(t, "abc123", run.ConcurrencyGroup) + run.SetConcurrencyGroup("ABC123") // case should collapse in SetConcurrencyGroup + assert.Equal(t, "abc123", run.ConcurrencyGroup) +} + +func TestSetDefaultConcurrencyGroup(t *testing.T) { + run := ActionRun{ + Ref: "refs/heads/main", + WorkflowID: "testing", + TriggerEvent: "pull_request", + } + run.SetDefaultConcurrencyGroup() + assert.Equal(t, "refs/heads/main_testing_pull_request__auto", run.ConcurrencyGroup) + run = ActionRun{ + Ref: "refs/heads/main", + WorkflowID: "TESTING", // case should collapse in SetDefaultConcurrencyGroup + TriggerEvent: "pull_request", + } + run.SetDefaultConcurrencyGroup() + assert.Equal(t, "refs/heads/main_testing_pull_request__auto", run.ConcurrencyGroup) +} + +func TestUpdateRepoRunsNumbers(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + t.Run("Normal", func(t *testing.T) { + t.Run("Repo 1", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + require.NoError(t, updateRepoRunsNumbers(t.Context(), repo)) + + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + assert.Equal(t, 1, repo.NumActionRuns) + assert.Equal(t, 1, repo.NumClosedActionRuns) + }) + + t.Run("Repo 4", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + + require.NoError(t, updateRepoRunsNumbers(t.Context(), repo)) + + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + assert.Equal(t, 4, repo.NumActionRuns) + assert.Equal(t, 4, repo.NumClosedActionRuns) + }) + + t.Run("Repo 63", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 63}) + + require.NoError(t, updateRepoRunsNumbers(t.Context(), repo)) + + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 63}) + assert.Equal(t, 3, repo.NumActionRuns) + assert.Equal(t, 2, repo.NumClosedActionRuns) + }) + }) + + t.Run("Columns specifc", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repo.Name = "ishouldnotbeupdated" + + require.NoError(t, updateRepoRunsNumbers(t.Context(), repo)) + + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + assert.Equal(t, "repo1", repo.Name) + }) +} diff --git a/models/actions/task.go b/models/actions/task.go index 6e77db1b18..569fc2bb33 100644 --- a/models/actions/task.go +++ b/models/actions/task.go @@ -239,6 +239,88 @@ func GetRunningTaskByToken(ctx context.Context, token string) (*ActionTask, erro return nil, errNotExist } +func getConcurrencyCondition() builder.Cond { + concurrencyCond := builder.NewCond() + + // OK to pick if there's no concurrency_group on the run + concurrencyCond = concurrencyCond.Or(builder.Eq{"concurrency_group": ""}) + concurrencyCond = concurrencyCond.Or(builder.IsNull{"concurrency_group"}) + + // OK to pick if it's not a "QueueBehind" concurrency type + concurrencyCond = concurrencyCond.Or(builder.Neq{"concurrency_type": QueueBehind}) + + // subQuery ends up representing all the runs that would block a run from executing: + subQuery := builder.Select("id").From("action_run", "inner_run"). + // A run can't block itself, so exclude it from this search + Where(builder.Neq{"inner_run.id": builder.Expr("outer_run.id")}). + // Blocking runs must be from the same repo & concurrency group + And(builder.Eq{"inner_run.repo_id": builder.Expr("outer_run.repo_id")}). + And(builder.Eq{"inner_run.concurrency_group": builder.Expr("outer_run.concurrency_group")}). + And( + // Ideally the logic here would be that a blocking run is "not done", and "younger", which allows each run + // to be blocked on the previous runs in the concurrency group and therefore execute in order from oldest to + // newest. + // + // But it's possible for runs to be required to run out-of-order -- for example, if a younger run has + // already completed but then it is re-run. If we only used "not done" and "younger" as logic, then the + // re-run would not be blocked, and therefore would violate the concurrency group's single-run goal. + // + // So we use two conditions to meet both needs: + // + // Blocking runs have a running status... + builder.Eq{"inner_run.status": StatusRunning}.Or( + // Blocking runs don't have a IsDone status & are younger than the outer_run + builder.NotIn("inner_run.status", []Status{StatusSuccess, StatusFailure, StatusCancelled, StatusSkipped}). + And(builder.Lt{"inner_run.`index`": builder.Expr("outer_run.`index`")}))) + + // OK to pick if there are no blocking runs + concurrencyCond = concurrencyCond.Or(builder.NotExists(subQuery)) + + return concurrencyCond +} + +// Returns all the available jobs that could be executed on `runner`, before label filtering is applied. Note that +// only a single job can actually be run from this result for any given invocation, as multiple runs (in order) from any +// single concurrency group could be returned. +func GetAvailableJobsForRunner(e db.Engine, runner *ActionRunner) ([]*ActionRunJob, error) { + jobCond := builder.NewCond() + if runner.RepoID != 0 { + jobCond = builder.Eq{"repo_id": runner.RepoID} + } else if runner.OwnerID != 0 { + jobCond = builder.In("repo_id", builder.Select("`repository`.id").From("repository"). + Join("INNER", "repo_unit", "`repository`.id = `repo_unit`.repo_id"). + Where(builder.Eq{"`repository`.owner_id": runner.OwnerID, "`repo_unit`.type": unit.TypeActions})) + } + // Concurrency group checks for queuing one run behind the last run in the concurrency group are more + // computationally expensive on the database. To manage the risk that this might have on large-scale deployments + // When this feature is initially released, it can be disabled in the ini file by setting + // `CONCURRENCY_GROUP_QUEUE_ENABLED = false` in the `[actions]` section. If disabled, then actions with a + // concurrency group and `cancel-in-progress: false` will run simultaneously rather than being queued. + if setting.Actions.ConcurrencyGroupQueueEnabled { + jobCond = jobCond.And(getConcurrencyCondition()) + } + if jobCond.IsValid() { + // It is *likely* more efficient to use an EXISTS query here rather than an IN clause, as that allows the + // database's query optimizer to perform partial computation of the subquery rather than complete computation. + // However, database engines can be fickle and difficult to predict. We'll retain the original IN clause + // implementation when ConcurrencyGroupQueueEnabled is disabled, which should maintain the same performance + // characteristics. When ConcurrencyGroupQueueEnabled is enabled, it will switch to the EXISTS clause. + if setting.Actions.ConcurrencyGroupQueueEnabled { + jobCond = builder.Exists(builder.Select("id").From("action_run", "outer_run"). + Where(builder.Eq{"outer_run.id": builder.Expr("action_run_job.run_id")}). + And(jobCond)) + } else { + jobCond = builder.In("run_id", builder.Select("id").From("action_run", "outer_run").Where(jobCond)) + } + } + + var jobs []*ActionRunJob + if err := e.Where("task_id=? AND status=?", 0, StatusWaiting).And(jobCond).Asc("updated", "id").Find(&jobs); err != nil { + return nil, err + } + return jobs, nil +} + func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask, bool, error) { ctx, commiter, err := db.TxContext(ctx) if err != nil { @@ -248,20 +330,8 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask e := db.GetEngine(ctx) - jobCond := builder.NewCond() - if runner.RepoID != 0 { - jobCond = builder.Eq{"repo_id": runner.RepoID} - } else if runner.OwnerID != 0 { - jobCond = builder.In("repo_id", builder.Select("`repository`.id").From("repository"). - Join("INNER", "repo_unit", "`repository`.id = `repo_unit`.repo_id"). - Where(builder.Eq{"`repository`.owner_id": runner.OwnerID, "`repo_unit`.type": unit.TypeActions})) - } - if jobCond.IsValid() { - jobCond = builder.In("run_id", builder.Select("id").From("action_run").Where(jobCond)) - } - - var jobs []*ActionRunJob - if err := e.Where("task_id=? AND status=?", 0, StatusWaiting).And(jobCond).Asc("updated", "id").Find(&jobs); err != nil { + jobs, err := GetAvailableJobsForRunner(e, runner) + if err != nil { return nil, false, err } diff --git a/models/auth/access_token.go b/models/auth/access_token.go index 695702b7a0..2bdb5357c5 100644 --- a/models/auth/access_token.go +++ b/models/auth/access_token.go @@ -127,6 +127,13 @@ func (t *AccessToken) DisplayPublicOnly() bool { return publicOnly } +// UpdateLastUsed updates the time this token was last used to now. +func (t *AccessToken) UpdateLastUsed(ctx context.Context) error { + t.UpdatedUnix = timeutil.TimeStampNow() + _, err := db.GetEngine(ctx).ID(t.ID).Cols("updated_unix").NoAutoTime().Update(t) + return err +} + func getAccessTokenIDFromCache(token string) int64 { if successfulAccessTokenCache == nil { return 0 @@ -220,12 +227,6 @@ func (opts ListAccessTokensOptions) ToOrders() string { return "created_unix DESC" } -// UpdateAccessToken updates information of access token. -func UpdateAccessToken(ctx context.Context, t *AccessToken) error { - _, err := db.GetEngine(ctx).ID(t.ID).AllCols().Update(t) - return err -} - // DeleteAccessTokenByID deletes access token by given ID. func DeleteAccessTokenByID(ctx context.Context, id, userID int64) error { cnt, err := db.GetEngine(ctx).ID(id).Delete(&AccessToken{ @@ -258,5 +259,6 @@ func RegenerateAccessTokenByID(ctx context.Context, id, userID int64) (*AccessTo // Reset the creation time, token is unused t.UpdatedUnix = timeutil.TimeStampNow() - return t, UpdateAccessToken(ctx, t) + _, err = db.GetEngine(ctx).ID(t.ID).Cols("token_salt", "token", "token_hash", "token_last_eight", "updated_unix").NoAutoTime().Update(t) + return t, err } diff --git a/models/auth/access_token_test.go b/models/auth/access_token_test.go index 913118433c..18e0fd3342 100644 --- a/models/auth/access_token_test.go +++ b/models/auth/access_token_test.go @@ -5,10 +5,12 @@ package auth_test import ( "testing" + "time" auth_model "forgejo.org/models/auth" "forgejo.org/models/db" "forgejo.org/models/unittest" + "forgejo.org/modules/timeutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -107,14 +109,17 @@ func TestListAccessTokens(t *testing.T) { assert.Empty(t, tokens) } -func TestUpdateAccessToken(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - token, err := auth_model.GetAccessTokenBySHA(db.DefaultContext, "4c6f36e6cf498e2a448662f915d932c09c5a146c") - require.NoError(t, err) - token.Name = "Token Z" +func TestUpdateLastUsed(t *testing.T) { + timeutil.MockSet(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)) + defer timeutil.MockUnset() - require.NoError(t, auth_model.UpdateAccessToken(db.DefaultContext, token)) - unittest.AssertExistsAndLoadBean(t, token) + require.NoError(t, unittest.PrepareTestDatabase()) + token := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{ID: 2}) + + require.NoError(t, token.UpdateLastUsed(t.Context())) + + token = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{ID: 2}) + assert.Equal(t, timeutil.TimeStampNow(), token.UpdatedUnix) } func TestDeleteAccessTokenByID(t *testing.T) { diff --git a/models/db/context.go b/models/db/context.go index 3e035cd733..34736ddbef 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -298,6 +298,31 @@ func TruncateBeans(ctx context.Context, beans ...any) (err error) { return nil } +// TruncateBeansCascade deletes all given beans. Beans MUST NOT contain delete conditions, as tables related by foreign +// keys will also be truncated. +func TruncateBeansCascade(ctx context.Context, beans ...any) (err error) { + // Expand the list of beans to any other table with a foreign key reference to the beans + cascadeTables, err := extendBeansForCascade(beans) + if err != nil { + return err + } + + // Sort the beans in inverse foreign key delete order + cascadeSorted, err := sortBeans(cascadeTables, foreignKeySortDelete) + if err != nil { + return err + } + + // Execute the truncate + e := GetEngine(ctx) + for i := range cascadeSorted { + if _, err = e.Truncate(cascadeSorted[i]); err != nil { + return err + } + } + return nil +} + // CountByBean counts the number of database records according non-empty fields of the bean as conditions. func CountByBean(ctx context.Context, bean any) (int64, error) { return GetEngine(ctx).Count(bean) diff --git a/models/db/engine.go b/models/db/engine.go index 42b9150696..71197153e5 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -161,9 +161,13 @@ func (w engineGroupWrapper) AddHook(hook contexts.Hook) bool { // SyncAllTables sync the schemas of all tables func SyncAllTables() error { - _, err := x.StoreEngine("InnoDB").SyncWithOptions(xorm.SyncOptions{ + sortedTables, err := sortBeans(tables, foreignKeySortInsert) + if err != nil { + return err + } + _, err = x.StoreEngine("InnoDB").SyncWithOptions(xorm.SyncOptions{ WarnIfDatabaseColumnMissed: true, - }, tables...) + }, sortedTables...) return err } diff --git a/models/db/foreign_keys.go b/models/db/foreign_keys.go new file mode 100644 index 0000000000..7af0b92f73 --- /dev/null +++ b/models/db/foreign_keys.go @@ -0,0 +1,275 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package db + +import ( + "cmp" + "fmt" + "slices" + "sync" + + "forgejo.org/modules/container" + + "xorm.io/xorm/schemas" +) + +type schemaWithDefaultBean struct { + schema *schemas.Table + bean any +} + +var ( + cachedForeignKeyOrderedTables = sync.OnceValues(foreignKeyOrderedTables) + cachedTableNameLookupOrder = sync.OnceValues(tableNameLookupOrder) + // Slice of all registered tables, including their bean from `RegisterModel()` and their schemas.Table reference. + cachedSchemaTables = sync.OnceValues(func() ([]schemaWithDefaultBean, error) { + schemaTables := make([]schemaWithDefaultBean, 0, len(tables)) + for _, bean := range tables { + table, err := TableInfo(bean) + if err != nil { + return nil, fmt.Errorf("cachedSchemaTables: failure to fetch schema table for bean %#v: %w", bean, err) + } + schemaTables = append(schemaTables, schemaWithDefaultBean{ + schema: table, + bean: bean, + }) + } + return schemaTables, nil + }) + // Lookup map from table name -> {schemas.Table, bean}. The bean is the empty bean from `RegisterModel()`. + cachedTableMap = sync.OnceValues(func() (map[string]schemaWithDefaultBean, error) { + schemaTables, err := cachedSchemaTables() + if err != nil { + return nil, err + } + retval := make(map[string]schemaWithDefaultBean, len(schemaTables)) + for _, table := range schemaTables { + retval[table.schema.Name] = table + } + return retval, nil + }) + // Table A has foreign keys to [B, C], this is a map of A -> {B, C}. + cachedReferencingTables = sync.OnceValues(func() (map[string][]string, error) { + schemaTables, err := cachedSchemaTables() + if err != nil { + return nil, err + } + return calculateReferencingTables(schemaTables), nil + }) + // Table A has foreign keys to [B, C], this is a map of B -> {A}, C -> {A}. + cachedReferencedTables = sync.OnceValues(func() (map[string][]string, error) { + referencingTables, err := cachedReferencingTables() + if err != nil { + return nil, err + } + referencedTables := make(map[string][]string) + for referencingTable, targetTables := range referencingTables { + for _, targetTable := range targetTables { + referencedTables[targetTable] = append(referencedTables[targetTable], referencingTable) + } + } + return referencedTables, nil + }) +) + +// Create a map for each schema table which contains a slice of all the tables that reference it (with a foreign key). +func calculateReferencingTables(tables []schemaWithDefaultBean) map[string][]string { + referencingTables := make(map[string][]string, len(tables)) + for _, table := range tables { + tableName := table.schema.Name + for _, fk := range table.schema.ForeignKeys { + referencingTables[tableName] = append(referencingTables[tableName], fk.TargetTableName) + } + } + return referencingTables +} + +// Create a list of database tables in their "foreign key order". This order specifies the safe insertion order for +// records into tables, where earlier tables in the list are referenced by foreign keys that exist in tables later in +// the list. This order can be used in reverse as a safe deletion order as well. +// +// An ordered list of tables is incompatible with tables that have self-referencing foreign keys and circular referenced +// foreign keys; however neither of those cases are in-use in Forgejo. +func calculateTableForeignKeyOrder(tables []schemaWithDefaultBean) ([]schemaWithDefaultBean, error) { + remainingTables := slices.Clone(tables) + + referencingTables := calculateReferencingTables(remainingTables) + orderedTables := make([]schemaWithDefaultBean, 0, len(remainingTables)) + + for len(remainingTables) > 0 { + nextGroup := make([]schemaWithDefaultBean, 0, len(remainingTables)) + + for _, targetTable := range remainingTables { + // Skip if this targetTable has foreign keys and the target table hasn't been created. + slice, ok := referencingTables[targetTable.schema.Name] + if ok && len(slice) > 0 { // This table is still referencing an uncreated table + continue + } + // This table's references are satisfied or it had none + nextGroup = append(nextGroup, targetTable) + } + + if len(nextGroup) == 0 { + return nil, fmt.Errorf("calculateTableForeignKeyOrder: unable to figure out next table from remainingTables = %#v", remainingTables) + } + + orderedTables = append(orderedTables, nextGroup...) + + // Cleanup between loops: remove each table in nextGroup from remainingTables, and remove their table names from + // referencingTables as well. + for _, doneTable := range nextGroup { + remainingTables = slices.DeleteFunc(remainingTables, func(remainingTable schemaWithDefaultBean) bool { + return remainingTable.schema.Name == doneTable.schema.Name + }) + for referencingTable, referencedTables := range referencingTables { + referencingTables[referencingTable] = slices.DeleteFunc(referencedTables, func(tableName string) bool { + return tableName == doneTable.schema.Name + }) + } + } + } + + return orderedTables, nil +} + +// Create a list of registered database tables in their "foreign key order", per calculateTableForeignKeyOrder. +func foreignKeyOrderedTables() ([]schemaWithDefaultBean, error) { + schemaTables, err := cachedSchemaTables() + if err != nil { + return nil, err + } + + orderedTables, err := calculateTableForeignKeyOrder(schemaTables) + if err != nil { + return nil, err + } + + return orderedTables, nil +} + +// Create a map from each registered database table's name to its order in "foreign key order", per +// calculateTableForeignKeyOrder. +func tableNameLookupOrder() (map[string]int, error) { + tables, err := cachedForeignKeyOrderedTables() + if err != nil { + return nil, err + } + + lookupMap := make(map[string]int, len(tables)) + for i, table := range tables { + lookupMap[table.schema.Name] = i + } + + return lookupMap, nil +} + +// When used as a comparator function in `slices.SortFunc`, can sort a slice into the safe insertion order for records +// in tables, where earlier tables in the list are referenced by foreign keys that exist in tables later in the list. +func TableNameInsertionOrderSortFunc(table1, table2 string) int { + lookupMap, err := cachedTableNameLookupOrder() + if err != nil { + panic(fmt.Sprintf("cachedTableNameLookupOrder failed: %#v", err)) + } + + // Since this is typically used by `slices.SortFunc` it can't return an error. If a table is referenced that isn't + // a registered model then it will be sorted at the beginning -- this case is used in models/gitea_migrations/test. + val1, ok := lookupMap[table1] + if !ok { + val1 = -1 + } + val2, ok := lookupMap[table2] + if !ok { + val2 = -1 + } + + return cmp.Compare(val1, val2) +} + +// In "Insert" order, tables that have a foreign key will be sorted after the tables that the foreign key points to, so +// that records can be safely inserted in this order. "Delete" order is the opposite, and allows records to be safely +// deleted in this order. +type foreignKeySortOrder int8 + +const ( + foreignKeySortInsert foreignKeySortOrder = iota + foreignKeySortDelete +) + +// Sort the provided beans in the provided foreign-key sort order. +func sortBeans(beans []any, sortOrder foreignKeySortOrder) ([]any, error) { + type beanWithTableName struct { + bean any + tableName string + } + + beansWithTableNames := make([]beanWithTableName, 0, len(beans)) + for _, bean := range beans { + table, err := TableInfo(bean) + if err != nil { + return nil, fmt.Errorf("sortBeans: failure to fetch schema table for bean %#v: %w", bean, err) + } + beansWithTableNames = append(beansWithTableNames, beanWithTableName{bean: bean, tableName: table.Name}) + } + + slices.SortFunc(beansWithTableNames, func(a, b beanWithTableName) int { + if sortOrder == foreignKeySortInsert { + return TableNameInsertionOrderSortFunc(a.tableName, b.tableName) + } + return TableNameInsertionOrderSortFunc(b.tableName, a.tableName) + }) + + beanRetval := make([]any, len(beans)) + for i, beanWithTableName := range beansWithTableNames { + beanRetval[i] = beanWithTableName.bean + } + return beanRetval, nil +} + +// A database operation on `beans` may need to affect additional tables based upon foreign keys to those beans. +// extendBeansForCascade returns a new list of beans which includes all the referencing tables that link to `beans`' +// tables. For example, provided a `&User{}`, it will return `[&User{}, &Stopwatch{}, ...]` where `Stopwatch` is a table +// that references `User`. The additional beans returned will be default structs that were provided to +// `db.RegisterModel`. +func extendBeansForCascade(beans []any) ([]any, error) { + referencedTables, err := cachedReferencedTables() + if err != nil { + return nil, err + } + tableMap, err := cachedTableMap() + if err != nil { + return nil, err + } + + deduplicateTables := make(container.Set[string], len(beans)) + + finalBeans := slices.Clone(beans) + newBeans := beans + nextBeanSet := make([]any, 0) + + for { + for _, bean := range newBeans { + schema, err := TableInfo(bean) + if err != nil { + return nil, fmt.Errorf("cascadeDeleteTables: failure to fetch schema table for bean %#v: %w", bean, err) + } + if deduplicateTables.Contains(schema.Name) { + continue + } + deduplicateTables.Add(schema.Name) + for _, referencingTable := range referencedTables[schema.Name] { + table := tableMap[referencingTable] + finalBeans = append(finalBeans, table.bean) + nextBeanSet = append(nextBeanSet, table.bean) + } + } + + if len(nextBeanSet) == 0 { + break + } + newBeans = nextBeanSet + nextBeanSet = nextBeanSet[:0] // set len 0, keep allocation for reuse + } + + return finalBeans, nil +} diff --git a/models/db/iterate.go b/models/db/iterate.go index 450c7d3389..5e30b5e8bc 100644 --- a/models/db/iterate.go +++ b/models/db/iterate.go @@ -5,39 +5,82 @@ package db import ( "context" + "fmt" + "reflect" "forgejo.org/modules/setting" "xorm.io/builder" ) -// Iterate iterate all the Bean object +// Iterate iterate all the Bean object. The table being iterated must have a single-column primary key. func Iterate[Bean any](ctx context.Context, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error { - var start int + var dummy Bean batchSize := setting.Database.IterateBufferSize - sess := GetEngine(ctx) + + table, err := TableInfo(&dummy) + if err != nil { + return fmt.Errorf("unable to fetch table info for bean %v: %w", dummy, err) + } + if len(table.PrimaryKeys) != 1 { + return fmt.Errorf("iterate only supported on a table with 1 primary key field, but table %s had %d", table.Name, len(table.PrimaryKeys)) + } + + pkDbName := table.PrimaryKeys[0] + var pkStructFieldName string + + for _, c := range table.Columns() { + if c.Name == pkDbName { + pkStructFieldName = c.FieldName + break + } + } + if pkStructFieldName == "" { + return fmt.Errorf("iterate unable to identify struct field for primary key %s", pkDbName) + } + + var lastPK any + for { select { case <-ctx.Done(): return ctx.Err() default: beans := make([]*Bean, 0, batchSize) + + sess := GetEngine(ctx) + sess = sess.OrderBy(pkDbName) if cond != nil { sess = sess.Where(cond) } - if err := sess.Limit(batchSize, start).Find(&beans); err != nil { + if lastPK != nil { + sess = sess.Where(builder.Gt{pkDbName: lastPK}) + } + + if err := sess.Limit(batchSize).Find(&beans); err != nil { return err } if len(beans) == 0 { return nil } - start += len(beans) for _, bean := range beans { if err := f(ctx, bean); err != nil { return err } } + + lastBean := beans[len(beans)-1] + lastPK = extractFieldValue(lastBean, pkStructFieldName) } } } + +func extractFieldValue(bean any, fieldName string) any { + v := reflect.ValueOf(bean) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + field := v.FieldByName(fieldName) + return field.Interface() +} diff --git a/models/db/iterate_test.go b/models/db/iterate_test.go index bdeaa876d5..405db84866 100644 --- a/models/db/iterate_test.go +++ b/models/db/iterate_test.go @@ -5,42 +5,113 @@ package db_test import ( "context" + "fmt" + "slices" "testing" "forgejo.org/models/db" repo_model "forgejo.org/models/repo" "forgejo.org/models/unittest" + "forgejo.org/modules/setting" + "forgejo.org/modules/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "xorm.io/builder" ) func TestIterate(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - xe, err := unittest.GetXORMEngine() - require.NoError(t, err) - require.NoError(t, xe.Sync(&repo_model.RepoUnit{})) + db.SetLogSQL(t.Context(), true) + defer test.MockVariableValue(&setting.Database.IterateBufferSize, 50)() - cnt, err := db.GetEngine(db.DefaultContext).Count(&repo_model.RepoUnit{}) - require.NoError(t, err) + t.Run("No Modifications", func(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + xe, err := unittest.GetXORMEngine() + require.NoError(t, err) + require.NoError(t, xe.Sync(&repo_model.RepoUnit{})) - var repoUnitCnt int - err = db.Iterate(db.DefaultContext, nil, func(ctx context.Context, repo *repo_model.RepoUnit) error { - repoUnitCnt++ - return nil + // Fetch all the repo unit IDs... + var remainingRepoIDs []int64 + db.GetEngine(t.Context()).Table(&repo_model.RepoUnit{}).Cols("id").Find(&remainingRepoIDs) + + // Ensure that every repo unit ID is found when doing iterate: + err = db.Iterate(t.Context(), nil, func(ctx context.Context, repo *repo_model.RepoUnit) error { + remainingRepoIDs = slices.DeleteFunc(remainingRepoIDs, func(n int64) bool { + return repo.ID == n + }) + return nil + }) + require.NoError(t, err) + assert.Empty(t, remainingRepoIDs) }) - require.NoError(t, err) - assert.EqualValues(t, cnt, repoUnitCnt) - err = db.Iterate(db.DefaultContext, nil, func(ctx context.Context, repoUnit *repo_model.RepoUnit) error { - has, err := db.ExistByID[repo_model.RepoUnit](ctx, repoUnit.ID) - if err != nil { - return err - } - if !has { - return db.ErrNotExist{Resource: "repo_unit", ID: repoUnit.ID} - } - return nil + t.Run("Concurrent Delete", func(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + xe, err := unittest.GetXORMEngine() + require.NoError(t, err) + require.NoError(t, xe.Sync(&repo_model.RepoUnit{})) + + // Fetch all the repo unit IDs... + var remainingRepoIDs []int64 + db.GetEngine(t.Context()).Table(&repo_model.RepoUnit{}).Cols("id").Find(&remainingRepoIDs) + + // Ensure that every repo unit ID is found, even if someone else performs a DELETE on the table while we're + // iterating. In real-world usage the deleted record may or may not be returned, but the important + // subject-under-test is that no *other* record is skipped. + didDelete := false + err = db.Iterate(t.Context(), nil, func(ctx context.Context, repo *repo_model.RepoUnit) error { + // While on page 2 (assuming ID ordering, 50 record buffer size)... + if repo.ID == 51 { + // Delete a record that would have been on page 1. + affected, err := db.GetEngine(t.Context()).ID(25).Delete(&repo_model.RepoUnit{}) + if err != nil { + return err + } else if affected != 1 { + return fmt.Errorf("expected to delete 1 record, but affected %d records", affected) + } + didDelete = true + } + remainingRepoIDs = slices.DeleteFunc(remainingRepoIDs, func(n int64) bool { + return repo.ID == n + }) + return nil + }) + require.NoError(t, err) + assert.True(t, didDelete, "didDelete") + assert.Empty(t, remainingRepoIDs) + }) + + t.Run("Verify cond applied", func(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + xe, err := unittest.GetXORMEngine() + require.NoError(t, err) + require.NoError(t, xe.Sync(&repo_model.RepoUnit{})) + + // Fetch all the repo unit IDs... + var remainingRepoIDs []int64 + db.GetEngine(t.Context()).Table(&repo_model.RepoUnit{}).Cols("id").Find(&remainingRepoIDs) + + // Remove those that we're not expecting to find based upon `Iterate`'s condition. We'll trim the front few + // records and last few records, which will confirm that cond is applied on all pages. + remainingRepoIDs = slices.DeleteFunc(remainingRepoIDs, func(n int64) bool { + return n <= 15 || n > 1000 + }) + err = db.Iterate(t.Context(), builder.Gt{"id": 15}.And(builder.Lt{"id": 1000}), func(ctx context.Context, repo *repo_model.RepoUnit) error { + removedRecord := false + // Remove the record from remainingRepoIDs, but track to make sure we did actually remove a record + remainingRepoIDs = slices.DeleteFunc(remainingRepoIDs, func(n int64) bool { + if repo.ID == n { + removedRecord = true + return true + } + return false + }) + if !removedRecord { + return fmt.Errorf("unable to find record in remainingRepoIDs for repo %d, indicating a cond application failure", repo.ID) + } + return nil + }) + require.NoError(t, err) + assert.Empty(t, remainingRepoIDs) }) - require.NoError(t, err) } diff --git a/models/fixtures/action_run.yml b/models/fixtures/action_run.yml index 5b6f89ae0e..4e5af28166 100644 --- a/models/fixtures/action_run.yml +++ b/models/fixtures/action_run.yml @@ -525,6 +525,7 @@ ref: "refs/heads/main" commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee" event: "workflow_dispatch" + trigger_event: "workflow_dispatch" is_fork_pull_request: 0 status: 6 # running started: 1683636528 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index 2f104eed65..f537ee02d5 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -349,7 +349,7 @@ is_archived: false is_mirror: false status: 0 - is_fork: false + is_fork: true fork_id: 10 is_template: false template_id: 0 diff --git a/models/fixtures/tracked_time.yml b/models/fixtures/tracked_time.yml index 768af38d9e..f006576c45 100644 --- a/models/fixtures/tracked_time.yml +++ b/models/fixtures/tracked_time.yml @@ -24,7 +24,6 @@ - id: 4 - user_id: -1 issue_id: 4 time: 1 created_unix: 946684803 diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index 00aa182540..9e9cb2c1a6 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -1562,3 +1562,41 @@ theme: "" keep_activity_private: false created_unix: 1672578400 + +- + id: 42 + lower_name: federated-example.net + name: federated-example.net + full_name: federated + email: f73240e82-c061-41ef-b7d6-4376cb6f2e1c@example.com + keep_email_private: false + email_notifications_preference: enabled + passwd: ZogKvWdyEx:password + passwd_hash_algo: dummy + must_change_password: false + login_source: 0 + login_name: federated-example.net + type: 5 + salt: ZogKvWdyEx + max_repo_creation: -1 + is_active: false + is_admin: false + is_restricted: false + allow_git_hook: false + allow_import_local: false + allow_create_organization: false + prohibit_login: false + avatar: "" + avatar_email: f73240e82-c061-41ef-b7d6-4376cb6f2e1c@example.com + use_custom_avatar: false + num_followers: 0 + num_following: 0 + num_stars: 0 + num_repos: 0 + num_teams: 0 + num_members: 0 + visibility: 0 + repo_admin_change_team_access: false + theme: "" + keep_activity_private: false + created_unix: 1759086716 diff --git a/models/forgejo_migrations/README.md b/models/forgejo_migrations/README.md new file mode 100644 index 0000000000..578b75c8a0 --- /dev/null +++ b/models/forgejo_migrations/README.md @@ -0,0 +1,105 @@ +# forgejo_migrations + +Forgejo has three database migration mechanisms: + +- `models/gitea_migrations` + - Original database schema migrations from Forgejo's legacy as a fork of Gitea. + - A linear set of migrations referenced in `models/gitea_migrations/migrations.go`, each represented by a number (eg. migration 304). + - The current version is recorded in the database in the table `version`. +- `models/forgejo_migrations_legacy` + - The next 50-ish database schema migrations reflecting change in Forgejo's database structure between Forgejo v7.0 and v14.0 + - A linear set of migrations referenced in `models/forgejo_migrations_legacy/migrate.go`, each represented by a number (eg. migration 43). + - The current version is recorded in the database in the table `forgejo_version`. +- `models/forgejo_migrations` + - The most recent database schema migrations, reflecting change in the v14.0 release cycle and onwards into the future. + - Each migration is identified by the filename it is stored in. + - The applied migrations are recorded in the database in the table `forgejo_migration`. + +`forgejo_migrations` is designed to reduce code conflicts when multiple developers may be making schema migrations in close succession, which it does by avoiding having one code file with a long array of migrations. Instead, each file in `models/forgejo_migrations` registers itself as a migration, and its filename indicates the order that migration will be applied. + +Files in `forgejo_migrations` must: +- Define an `init` function which registers a function to be invoked for the migration. +- Follow the naming convention: + - The letter `v` + - A number, representing the development cycle that the migration was created in + - A letter, indicating any required migration ordering + - The character `_` (underscore) + - A short descriptive identifier for the migration + +For example, valid migration file names would look like this: +- `v14a_add-threaded-comments.go` +- `v14a_add-federated-emojis.go` +- `v14b_fix-threaded-comments-index.go` + + +## Migration Ordering + +Forgejo executes registered migrations in `forgejo_migrations` in the `strings.Compare()` ordering of their filename. + +There are edge cases where migrations may not be executed in this exact order: +- If a schema change is backported to an earlier Forgejo release. For example, if a bugfix during the v15 development cycle was backported into a v14 patch release, then a migration labeled `v15a_fix-unusual-data-corruption.go` could be applied during a v14 software upgrade. In the future when a v15 software release occurs, that migration will be identified as already applied and will be skipped. +- If a developer working on Forgejo switches between different branches with different schema migrations. +- If the contents of the `forgejo_migrations` database table are changed outside of Forgejo modifying it. + + +## Creating a new Migration + +First, determine the filename for your migration. In general, you create a new migration by starting a file with the same prefix as the most recent migration present. If `v14a_add-forgejo-migrations-table.go` was the last file, most of the time you can create your migration with the same `v14a_...` prefix. + +There are two exceptions: +- After the release branch is cut for a release, increment the version in the migration. If v14 was cut, you would start `v15a_...` as the next migration. +- If your migration requires that an earlier migration is complete first, you would increment the letter in the prefix. If you were modifying the table created by `v14a_add-forgejo-migrations-table.go`, then you would name your migration `v14b_...`. + +Once you've determined the migration filename, then you can copy this template into the file: + +```go +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations + +import ( + "forgejo.org/modules/timeutil" + + "xorm.io/xorm" +) + +func init() { + registerMigration(&Migration{ + Description: "short sentence describing this migration", + Upgrade: myMigrationFunction, // rename + }) +} + +func myMigrationFunction(x *xorm.Engine) error { + // add migration logic here + // + // to prevent `make watch` from recording this migration as done when it + // isn't authored yet, returh an error until the implementation is done + return errors.New("not implemented yet") +} +``` + +And now it's up to you to write the contents of your migration function. + + +## Development Notes + +Once migrations are executed, a record of their execution is stored in the database table `forgejo_migration`. + +```sql +=> SELECT * FROM forgejo_migration; + id | created_unix +-----------------------------------+-------------- + v14a_add-forgejo-migrations-table | 1760402451 + v14a_example-other-migration | 1760402453 + v14b_another-example | 1760402455 + v15a_add-something-cool | 1760402456 + v15a_another-example-again | 1760402457 +``` + +If your migration successfully executes once, it will be recorded in this table and it will never execute again, even if you change the migration code. It is common during development to need to re-run a migration, in which case you can delete the record that you're working on developing. The migration will be re-run as soon as the Forgejo server is restarted: + +```sql +=> DELETE FROM forgejo_migration WHERE id = 'v15a_another-example-again'; +``` diff --git a/models/forgejo_migrations/foreign_key_utils.go b/models/forgejo_migrations/foreign_key_utils.go new file mode 100644 index 0000000000..fc2680e43e --- /dev/null +++ b/models/forgejo_migrations/foreign_key_utils.go @@ -0,0 +1,31 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations + +import ( + "errors" + + "forgejo.org/modules/log" + + "xorm.io/xorm" +) + +func syncDoctorForeignKey(x *xorm.Engine, beans []any) error { + for _, bean := range beans { + // Sync() drops indexes by default, which will cause unnecessary rebuilding of indexes when syncDoctorForeignKey + // is used with partial bean definitions; so we disable that option + _, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, bean) + if err != nil { + if errors.Is(err, xorm.ErrForeignKeyViolation) { + tableName := x.TableName(bean) + log.Error( + "Foreign key creation on table %s failed. Run `forgejo doctor check --all` to identify the orphaned records preventing this foreign key from being created. Error was: %v", + tableName, err) + return err + } + return err + } + } + return nil +} diff --git a/models/forgejo_migrations/main_test.go b/models/forgejo_migrations/main_test.go index 2246e327f0..a38afed10d 100644 --- a/models/forgejo_migrations/main_test.go +++ b/models/forgejo_migrations/main_test.go @@ -1,12 +1,12 @@ -// Copyright 2023 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later package forgejo_migrations import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" ) func TestMain(m *testing.M) { diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go index 71fcf16e7a..53ab95d216 100644 --- a/models/forgejo_migrations/migrate.go +++ b/models/forgejo_migrations/migrate.go @@ -1,229 +1,222 @@ -// Copyright 2023 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later package forgejo_migrations import ( - "context" - "errors" "fmt" "os" + "regexp" + "runtime" + "slices" + "strings" - "forgejo.org/models/forgejo/semver" - forgejo_v1_20 "forgejo.org/models/forgejo_migrations/v1_20" - forgejo_v1_22 "forgejo.org/models/forgejo_migrations/v1_22" - "forgejo.org/modules/git" + "forgejo.org/modules/container" "forgejo.org/modules/log" "forgejo.org/modules/setting" + "forgejo.org/modules/timeutil" "xorm.io/xorm" "xorm.io/xorm/names" ) -// ForgejoVersion describes the Forgejo version table. Should have only one row with id = 1. -type ForgejoVersion struct { - ID int64 `xorm:"pk autoincr"` - Version int64 +// ForgejoMigration table contains a record of migrations applied to the database. (Note that there are older +// migrations in the forgejo_version table from before this table was introduced, and the `version` table from Gitea +// migrations). Each record in this table represents one successfully completed migration which was completed at the +// `CreatedUnix` time. +type ForgejoMigration struct { + ID string `xorm:"pk"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` } type Migration struct { - description string - migrate func(*xorm.Engine) error + Description string // short plaintext explanation of the migration + Upgrade func(*xorm.Engine) error // perform the migration + + id string // unique migration identifier } -// NewMigration creates a new migration. -func NewMigration(desc string, fn func(*xorm.Engine) error) *Migration { - return &Migration{desc, fn} +var ( + rawMigrations []*Migration + migrationFilenameRegex = regexp.MustCompile(`/(?Pv[0-9]+[a-z])_(?P[^/]+)\.go$`) +) + +var getMigrationFilename = func() string { + _, migrationFilename, _, _ := runtime.Caller(2) + return migrationFilename } -// This is a sequence of additional Forgejo migrations. -// Add new migrations to the bottom of the list. -var migrations = []*Migration{ - // v0 -> v1 - NewMigration("Create the `forgejo_blocked_user` table", forgejo_v1_20.AddForgejoBlockedUser), - // v1 -> v2 - NewMigration("Create the `forgejo_sem_ver` table", forgejo_v1_20.CreateSemVerTable), - // v2 -> v3 - NewMigration("Create the `forgejo_auth_token` table", forgejo_v1_20.CreateAuthorizationTokenTable), - // v3 -> v4 - NewMigration("Add the `default_permissions` column to the `repo_unit` table", forgejo_v1_22.AddDefaultPermissionsToRepoUnit), - // v4 -> v5 - NewMigration("Create the `forgejo_repo_flag` table", forgejo_v1_22.CreateRepoFlagTable), - // v5 -> v6 - NewMigration("Add the `wiki_branch` column to the `repository` table", forgejo_v1_22.AddWikiBranchToRepository), - // v6 -> v7 - NewMigration("Add the `enable_repo_unit_hints` column to the `user` table", forgejo_v1_22.AddUserRepoUnitHintsSetting), - // v7 -> v8 - NewMigration("Modify the `release`.`note` content to remove SSH signatures", forgejo_v1_22.RemoveSSHSignaturesFromReleaseNotes), - // v8 -> v9 - NewMigration("Add the `apply_to_admins` column to the `protected_branch` table", forgejo_v1_22.AddApplyToAdminsSetting), - // v9 -> v10 - NewMigration("Add pronouns to user", forgejo_v1_22.AddPronounsToUser), - // v10 -> v11 - NewMigration("Add the `created` column to the `issue` table", forgejo_v1_22.AddCreatedToIssue), - // v11 -> v12 - NewMigration("Add repo_archive_download_count table", forgejo_v1_22.AddRepoArchiveDownloadCount), - // v12 -> v13 - NewMigration("Add `hide_archive_links` column to `release` table", AddHideArchiveLinksToRelease), - // v13 -> v14 - NewMigration("Remove Gitea-specific columns from the repository and badge tables", RemoveGiteaSpecificColumnsFromRepositoryAndBadge), - // v14 -> v15 - NewMigration("Create the `federation_host` table", CreateFederationHostTable), - // v15 -> v16 - NewMigration("Create the `federated_user` table", CreateFederatedUserTable), - // v16 -> v17 - NewMigration("Add `normalized_federated_uri` column to `user` table", AddNormalizedFederatedURIToUser), - // v17 -> v18 - NewMigration("Create the `following_repo` table", CreateFollowingRepoTable), - // v18 -> v19 - NewMigration("Add external_url to attachment table", AddExternalURLColumnToAttachmentTable), - // v19 -> v20 - NewMigration("Creating Quota-related tables", CreateQuotaTables), - // v20 -> v21 - NewMigration("Add SSH keypair to `pull_mirror` table", AddSSHKeypairToPushMirror), - // v21 -> v22 - NewMigration("Add `legacy` to `web_authn_credential` table", AddLegacyToWebAuthnCredential), - // v22 -> v23 - NewMigration("Add `delete_branch_after_merge` to `auto_merge` table", AddDeleteBranchAfterMergeToAutoMerge), - // v23 -> v24 - NewMigration("Add `purpose` column to `forgejo_auth_token` table", AddPurposeToForgejoAuthToken), - // v24 -> v25 - NewMigration("Migrate `secret` column to store keying material", MigrateTwoFactorToKeying), - // v25 -> v26 - NewMigration("Add `hash_blake2b` column to `package_blob` table", AddHashBlake2bToPackageBlob), - // v26 -> v27 - NewMigration("Add `created_unix` column to `user_redirect` table", AddCreatedUnixToRedirect), - // v27 -> v28 - NewMigration("Add pronoun privacy settings to user", AddHidePronounsOptionToUser), - // v28 -> v29 - NewMigration("Add public key information to `FederatedUser` and `FederationHost`", AddPublicKeyInformationForFederation), - // v29 -> v30 - NewMigration("Migrate `User.NormalizedFederatedURI` column to extract port & schema into FederatedHost", MigrateNormalizedFederatedURI), - // v30 -> v31 - 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("Noop because of https://codeberg.org/forgejo/forgejo/issues/8373", NoopAddIndexToActionRunStopped), - // v35 -> v36 - NewMigration("Fix wiki unit default permission", FixWikiUnitDefaultPermission), - // v36 -> v37 - NewMigration("Add `branch_filter` to `push_mirror` table", AddPushMirrorBranchFilter), - // v37 -> v38 - NewMigration("Add `resolved_unix` column to `abuse_report` table", AddResolvedUnixToAbuseReport), - // v38 -> v39 - NewMigration("Migrate `data` column of `secret` table to store keying material", MigrateActionSecretsToKeying), - // v39 -> v40 - NewMigration("Add index for release sha1", AddIndexForReleaseSha1), -} +func registerMigration(migration *Migration) { + migrationFilename := getMigrationFilename() -// GetCurrentDBVersion returns the current Forgejo database version. -func GetCurrentDBVersion(x *xorm.Engine) (int64, error) { - if err := x.Sync(new(ForgejoVersion)); err != nil { - return -1, fmt.Errorf("sync: %w", err) + if migrationResolutionComplete { + panic(fmt.Sprintf("attempted to register migration from %s after migration resolution is already complete", migrationFilename)) } - currentVersion := &ForgejoVersion{ID: 1} - has, err := x.Get(currentVersion) + matches := migrationFilenameRegex.FindStringSubmatch(migrationFilename) + if len(matches) == 0 { + panic(fmt.Sprintf("registerMigration must be invoked from a file matching migrationFilenameRegex, but was invoked from %q", migrationFilename)) + } + migration.id = fmt.Sprintf("%s_%s", matches[1], matches[2]) // this just rebuilds the filename, but guarantees that the regex applied for consistent naming + + rawMigrations = append(rawMigrations, migration) +} + +// For testing only +func resetMigrations() { + rawMigrations = nil + orderedMigrations = nil + migrationResolutionComplete = false + inMemoryMigrationIDs = nil +} + +var ( + migrationResolutionComplete = false + inMemoryMigrationIDs container.Set[string] + orderedMigrations []*Migration +) + +func resolveMigrations() { + if migrationResolutionComplete { + return + } + + inMemoryMigrationIDs = make(container.Set[string]) + for _, m := range rawMigrations { + if inMemoryMigrationIDs.Contains(m.id) { + // With the filename-based migration ID this shouldn't be possible, but a bit of a sanity check.. + panic(fmt.Sprintf("migration id is duplicated: %q", m.id)) + } + inMemoryMigrationIDs.Add(m.id) + } + + orderedMigrations = slices.Clone(rawMigrations) + slices.SortFunc(orderedMigrations, func(m1, m2 *Migration) int { + return strings.Compare(m1.id, m2.id) + }) + + migrationResolutionComplete = true +} + +func getInDBMigrationIDs(x *xorm.Engine) (container.Set[string], error) { + var inDBMigrations []ForgejoMigration + err := x.Find(&inDBMigrations) if err != nil { - return -1, fmt.Errorf("get: %w", err) + return nil, err } - if !has { - return -1, nil - } - return currentVersion.Version, nil -} -// ExpectedVersion returns the expected Forgejo database version. -func ExpectedVersion() int64 { - return int64(len(migrations)) + inDBMigrationIDs := make(container.Set[string], len(inDBMigrations)) + for _, inDB := range inDBMigrations { + inDBMigrationIDs.Add(inDB.ID) + } + + return inDBMigrationIDs, nil } // EnsureUpToDate will check if the Forgejo database is at the correct version. func EnsureUpToDate(x *xorm.Engine) error { - currentDB, err := GetCurrentDBVersion(x) + resolveMigrations() + + inDBMigrationIDs, err := getInDBMigrationIDs(x) if err != nil { return err } - if currentDB < 0 { - return errors.New("database has not been initialized") + // invalidMigrations are those that are in the database, but aren't registered. + invalidMigrations := inDBMigrationIDs.Difference(inMemoryMigrationIDs) + if len(invalidMigrations) > 0 { + return fmt.Errorf("current Forgejo database has migration(s) %s applied, which are not registered migrations", strings.Join(invalidMigrations.Slice(), ", ")) } - expected := ExpectedVersion() - - if currentDB != expected { - return fmt.Errorf(`current Forgejo database version %d is not equal to the expected version %d. Please run "forgejo [--config /path/to/app.ini] migrate" to update the database version`, currentDB, expected) + // unappliedMigrations are those that haven't yet been applied, but seem valid + unappliedMigrations := inMemoryMigrationIDs.Difference(inDBMigrationIDs) + if len(unappliedMigrations) > 0 { + return fmt.Errorf(`current Forgejo database is missing migration(s) %s. Please run "forgejo [--config /path/to/app.ini] migrate" to update the database version`, strings.Join(unappliedMigrations.Slice(), ", ")) } return nil } +func recordMigrationComplete(x *xorm.Engine, migration *Migration) error { + affected, err := x.Insert(&ForgejoMigration{ID: migration.id}) + if err != nil { + return err + } else if affected != 1 { + return fmt.Errorf("migration[%s]: failed to insert into DB, %d records affected", migration.id, affected) + } + return nil +} + // Migrate Forgejo database to current version. -func Migrate(x *xorm.Engine) error { +func Migrate(x *xorm.Engine, freshDB bool) error { + resolveMigrations() + // Set a new clean the default mapper to GonicMapper as that is the default for . x.SetMapper(names.GonicMapper{}) - if err := x.Sync(new(ForgejoVersion)); err != nil { + if err := x.Sync(new(ForgejoMigration)); err != nil { return fmt.Errorf("sync: %w", err) } - currentVersion := &ForgejoVersion{ID: 1} - has, err := x.Get(currentVersion) + inDBMigrationIDs, err := getInDBMigrationIDs(x) if err != nil { - return fmt.Errorf("get: %w", err) - } else if !has { - // If the version record does not exist we think - // it is a fresh installation and we can skip all migrations. - currentVersion.ID = 0 - currentVersion.Version = ExpectedVersion() - - if _, err = x.InsertOne(currentVersion); err != nil { - return fmt.Errorf("insert: %w", err) + return err + } else if len(inDBMigrationIDs) == 0 && freshDB { + // During startup on a new, empty database, and during integration tests, we rely only on `SyncAllTables` to + // create the DB schema. No migrations can be applied because `SyncAllTables` occurs later in the + // initialization cycle. We mark all migrations as complete up to this point and only run future migrations. + for _, migration := range orderedMigrations { + err := recordMigrationComplete(x, migration) + if err != nil { + return err + } } + inDBMigrationIDs, err = getInDBMigrationIDs(x) + if err != nil { + return err + } + } else if freshDB { + return fmt.Errorf("unexpected state: migrator called with freshDB=true, but existing migrations in DB %#v", inDBMigrationIDs) } - v := currentVersion.Version - - // Downgrading Forgejo's database version not supported - if v > ExpectedVersion() { - msg := fmt.Sprintf("Your Forgejo database (migration version: %d) is for a newer version of Forgejo, you cannot use the newer database for this old Forgejo release (%d).", v, ExpectedVersion()) + // invalidMigrations are those that are in the database, but aren't registered. + invalidMigrations := inDBMigrationIDs.Difference(inMemoryMigrationIDs) + if len(invalidMigrations) > 0 { + // Downgrading Forgejo's database version not supported + msg := fmt.Sprintf("Your Forgejo database has %d migration(s) (%s) for a newer version of Forgejo, you cannot use the newer database for this old Forgejo release.", len(invalidMigrations), strings.Join(invalidMigrations.Slice(), ", ")) msg += "\nForgejo will exit to keep your database safe and unchanged. Please use the correct Forgejo release, do not change the migration version manually (incorrect manual operation may cause data loss)." if !setting.IsProd { - msg += fmt.Sprintf("\nIf you are in development and really know what you're doing, you can force changing the migration version by executing: UPDATE forgejo_version SET version=%d WHERE id=1;", ExpectedVersion()) + msg += "\nIf you are in development and know what you're doing, you can remove the migration records from the forgejo_migration table. The affect of those migrations will still be present." + quoted := slices.Clone(invalidMigrations.Slice()) + for i, s := range quoted { + quoted[i] = "'" + s + "'" + } + msg += fmt.Sprintf("\n DELETE FROM forgejo_migration WHERE id IN (%s)", strings.Join(quoted, ", ")) } _, _ = fmt.Fprintln(os.Stderr, msg) log.Fatal(msg) return nil } - // Some migration tasks depend on the git command - if git.DefaultContext == nil { - if err = git.InitSimple(context.Background()); err != nil { - return err + // unappliedMigrations are those that are registered but haven't been applied. + unappliedMigrations := inMemoryMigrationIDs.Difference(inDBMigrationIDs) + for _, migration := range orderedMigrations { + if !unappliedMigrations.Contains(migration.id) { + continue } - } - // Migrate - for i, m := range migrations[v:] { - log.Info("Migration[%d]: %s", v+int64(i), m.description) + log.Info("Migration[%s]: %s", migration.id, migration.Description) + // Reset the mapper between each migration - migrations are not supposed to depend on each other x.SetMapper(names.GonicMapper{}) - if err = m.migrate(x); err != nil { - return fmt.Errorf("migration[%d]: %s failed: %w", v+int64(i), m.description, err) + if err = migration.Upgrade(x); err != nil { + return fmt.Errorf("migration[%s]: %s failed: %w", migration.id, migration.Description, err) } - currentVersion.Version = v + int64(i) + 1 - if _, err = x.ID(1).Update(currentVersion); err != nil { + + err := recordMigrationComplete(x, migration) + if err != nil { return err } } - if err := x.Sync(new(semver.ForgejoSemVer)); err != nil { - return fmt.Errorf("sync: %w", err) - } - - return semver.SetVersionStringWithEngine(x, setting.ForgejoVersion) + return nil } diff --git a/models/forgejo_migrations/migrate_test.go b/models/forgejo_migrations/migrate_test.go index 9d16c9fe1c..360f6a16d5 100644 --- a/models/forgejo_migrations/migrate_test.go +++ b/models/forgejo_migrations/migrate_test.go @@ -1,39 +1,314 @@ -// Copyright 2023 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later package forgejo_migrations import ( + "fmt" "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" + "forgejo.org/modules/test" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "xorm.io/xorm" ) -// TestEnsureUpToDate tests the behavior of EnsureUpToDate. -func TestEnsureUpToDate(t *testing.T) { - x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoVersion)) - defer deferable() - if x == nil || t.Failed() { - return +func noOpMigration(x *xorm.Engine) error { + return nil +} + +func nilMigration() *Migration { + return &Migration{ + Description: "nothing", + Upgrade: noOpMigration, + } +} + +func TestRegisterMigration(t *testing.T) { + resetMigrations() + + defer test.MockVariableValue(&getMigrationFilename, func() string { + return "some-path/v99b_neat_migration.go" + })() + + t.Run("migrationResolutionComplete", func(t *testing.T) { + defer test.MockVariableValue(&migrationResolutionComplete, true)() + assert.PanicsWithValue(t, "attempted to register migration from some-path/v99b_neat_migration.go after migration resolution is already complete", func() { + registerMigration(nilMigration()) + }) + }) + + for _, fn := range []string{ + "v99b_neat_migration.go", // no leading path + "vb_neat_migration.go", // no version number + "v12_neat_migration.go", // no migration group letter + "v12a-neat-migration.go", // no undescore + "v12a.go", // no descriptive identifier + } { + t.Run(fmt.Sprintf("bad name - %s", fn), func(t *testing.T) { + defer test.MockVariableValue(&getMigrationFilename, func() string { + return fn + })() + assert.PanicsWithValue(t, fmt.Sprintf("registerMigration must be invoked from a file matching migrationFilenameRegex, but was invoked from %q", fn), func() { + registerMigration(nilMigration()) + }) + }) } - // Ensure error if there's no row in Forgejo Version. - err := EnsureUpToDate(x) - require.Error(t, err) - - // Insert 'good' Forgejo Version row. - _, err = x.InsertOne(&ForgejoVersion{ID: 1, Version: ExpectedVersion()}) - require.NoError(t, err) - - err = EnsureUpToDate(x) - require.NoError(t, err) - - // Modify forgejo version to have a lower version. - _, err = x.Exec("UPDATE `forgejo_version` SET version = ? WHERE id = 1", ExpectedVersion()-1) - require.NoError(t, err) - - err = EnsureUpToDate(x) - require.Error(t, err) + registerMigration(nilMigration()) + found := false + for _, m := range rawMigrations { + if m.id == "v99b_neat_migration" { + found = true + } + } + require.True(t, found, "found registered migration") +} + +func TestResolveMigrations(t *testing.T) { + t.Run("duplicate migration IDs", func(t *testing.T) { + resetMigrations() + defer test.MockVariableValue(&getMigrationFilename, func() string { + return "some-path/v99b_neat_migration.go" + })() + registerMigration(nilMigration()) + registerMigration(nilMigration()) + + assert.PanicsWithValue(t, "migration id is duplicated: \"v99b_neat_migration\"", func() { + resolveMigrations() + }) + }) + + t.Run("success", func(t *testing.T) { + resetMigrations() + defer test.MockVariableValue(&getMigrationFilename, func() string { + return "some-path/v99b_neat_migration.go" + })() + registerMigration(nilMigration()) + + defer test.MockVariableValue(&getMigrationFilename, func() string { + return "some-path/v77a_neat_migration.go" + })() + registerMigration(nilMigration()) + + resolveMigrations() + + assert.True(t, migrationResolutionComplete, "migration resolution complete") + assert.Contains(t, inMemoryMigrationIDs, "v77a_neat_migration") + assert.Contains(t, inMemoryMigrationIDs, "v99b_neat_migration") + require.Len(t, orderedMigrations, 2) + assert.Equal(t, "v77a_neat_migration", orderedMigrations[0].id) + assert.Equal(t, "v99b_neat_migration", orderedMigrations[1].id) + }) +} + +func TestGetInDBMigrationIDs(t *testing.T) { + x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoMigration)) + defer deferable() + require.NotNil(t, x) + + migrationIDs, err := getInDBMigrationIDs(x) + require.NoError(t, err) + require.NotNil(t, migrationIDs) + assert.Empty(t, migrationIDs) + + _, err = x.Insert(&ForgejoMigration{ID: "v77a_neat_migration"}) + require.NoError(t, err) + _, err = x.Insert(&ForgejoMigration{ID: "v99b_neat_migration"}) + require.NoError(t, err) + + migrationIDs, err = getInDBMigrationIDs(x) + require.NoError(t, err) + require.NotNil(t, migrationIDs) + assert.Len(t, migrationIDs, 2) + assert.Contains(t, migrationIDs, "v77a_neat_migration") + assert.Contains(t, migrationIDs, "v99b_neat_migration") +} + +func TestEnsureUpToDate(t *testing.T) { + tests := []struct { + desc string + inMemory []string + inDB []string + err string + }{ + { + desc: "up-to-date", + inMemory: []string{"v77a_neat_migration"}, + inDB: []string{"v77a_neat_migration"}, + }, + { + desc: "invalid-migration", + inMemory: []string{}, + inDB: []string{"v77a_neat_migration"}, + err: "current Forgejo database has migration(s) v77a_neat_migration applied, which are not registered migrations", + }, + { + desc: "missing-migration", + inMemory: []string{"v77a_neat_migration"}, + inDB: []string{}, + err: "current Forgejo database is missing migration(s) v77a_neat_migration", + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + resetMigrations() + x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoMigration)) + defer deferable() + require.NotNil(t, x) + + for _, inMemory := range tc.inMemory { + defer test.MockVariableValue(&getMigrationFilename, func() string { + return fmt.Sprintf("some-path/%s.go", inMemory) + })() + registerMigration(nilMigration()) + } + for _, inDB := range tc.inDB { + x.Insert(&ForgejoMigration{ID: inDB}) + } + + err := EnsureUpToDate(x) + if tc.err == "" { + assert.NoError(t, err) + } else { + assert.ErrorContains(t, err, tc.err) + } + }) + } +} + +func TestMigrate(t *testing.T) { + resetMigrations() + x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoMigration)) + defer deferable() + require.NotNil(t, x) + + v77aRun := false + defer test.MockVariableValue(&getMigrationFilename, func() string { + return "some-path/v77a_neat_migration.go" + })() + registerMigration(&Migration{ + Description: "nothing", + Upgrade: func(x *xorm.Engine) error { + v77aRun = true + return nil + }, + }) + // v77a_neat_migration will already be marked as already run + _, err := x.Insert(&ForgejoMigration{ID: "v77a_neat_migration"}) + require.NoError(t, err) + + v99bRun := false + defer test.MockVariableValue(&getMigrationFilename, func() string { + return "some-path/v99b_neat_migration.go" + })() + registerMigration(&Migration{ + Description: "nothing", + Upgrade: func(x *xorm.Engine) error { + v99bRun = true + type ForgejoMagicFunctionality struct { + ID int64 `xorm:"pk autoincr"` + Name string + } + return x.Sync(new(ForgejoMagicFunctionality)) + }, + }) + + v99cRun := false + defer test.MockVariableValue(&getMigrationFilename, func() string { + return "some-path/v99c_neat_migration.go" + })() + registerMigration(&Migration{ + Description: "nothing", + Upgrade: func(x *xorm.Engine) error { + v99cRun = true + type ForgejoMagicFunctionality struct { + NewField string + } + return x.Sync(new(ForgejoMagicFunctionality)) + }, + }) + + err = Migrate(x, false) + require.NoError(t, err) + + assert.False(t, v77aRun, "v77aRun") // was already marked as run in the DB so shouldn't have run again + assert.True(t, v99bRun, "v99bRun") + assert.True(t, v99cRun, "v99cRun") + migrationIDs, err := getInDBMigrationIDs(x) + require.NoError(t, err) + assert.Contains(t, migrationIDs, "v77a_neat_migration") + assert.Contains(t, migrationIDs, "v99b_neat_migration") + assert.Contains(t, migrationIDs, "v99c_neat_migration") + + // should be able to query all three of the fields from this table created, verifying both migrations creating the + // table and adding a column were run + rec := make([]map[string]any, 0) + err = x.Cols("id", "name", "new_field").Table("forgejo_magic_functionality").Find(&rec) + assert.NoError(t, err) +} + +func TestMigrateFreshDB(t *testing.T) { + resetMigrations() + x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoMigration)) + defer deferable() + require.NotNil(t, x) + + v77aRun := false + defer test.MockVariableValue(&getMigrationFilename, func() string { + return "some-path/v77a_neat_migration.go" + })() + registerMigration(&Migration{ + Description: "nothing", + Upgrade: func(x *xorm.Engine) error { + v77aRun = true + return nil + }, + }) + + v99bRun := false + defer test.MockVariableValue(&getMigrationFilename, func() string { + return "some-path/v99b_neat_migration.go" + })() + registerMigration(&Migration{ + Description: "nothing", + Upgrade: func(x *xorm.Engine) error { + v99bRun = true + type ForgejoMagicFunctionality struct { + ID int64 `xorm:"pk autoincr"` + Name string + } + return x.Sync(new(ForgejoMagicFunctionality)) + }, + }) + + v99cRun := false + defer test.MockVariableValue(&getMigrationFilename, func() string { + return "some-path/v99c_neat_migration.go" + })() + registerMigration(&Migration{ + Description: "nothing", + Upgrade: func(x *xorm.Engine) error { + v99cRun = true + type ForgejoMagicFunctionality struct { + NewField string + } + return x.Sync(new(ForgejoMagicFunctionality)) + }, + }) + + err := Migrate(x, true) + require.NoError(t, err) + + assert.False(t, v77aRun, "v77aRun") // none should be run due to freshDB flag + assert.False(t, v99bRun, "v99bRun") + assert.False(t, v99cRun, "v99cRun") + migrationIDs, err := getInDBMigrationIDs(x) + require.NoError(t, err) + assert.Contains(t, migrationIDs, "v77a_neat_migration") + assert.Contains(t, migrationIDs, "v99b_neat_migration") + assert.Contains(t, migrationIDs, "v99c_neat_migration") } diff --git a/models/forgejo_migrations/v14a_add-foreign-keys-collaboration.go b/models/forgejo_migrations/v14a_add-foreign-keys-collaboration.go new file mode 100644 index 0000000000..849c2e5ea9 --- /dev/null +++ b/models/forgejo_migrations/v14a_add-foreign-keys-collaboration.go @@ -0,0 +1,25 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations + +import ( + "xorm.io/xorm" +) + +func init() { + registerMigration(&Migration{ + Description: "add forgejo_migration table", + Upgrade: addForeignKeysCollaboration, + }) +} + +func addForeignKeysCollaboration(x *xorm.Engine) error { + type Collaboration struct { + RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL REFERENCES(repository, id)"` + UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL REFERENCES(user, id)"` + } + return syncDoctorForeignKey(x, []any{ + new(Collaboration), + }) +} diff --git a/models/forgejo_migrations/v14a_add-forgejo-migrations-table.go b/models/forgejo_migrations/v14a_add-forgejo-migrations-table.go new file mode 100644 index 0000000000..f78eec8789 --- /dev/null +++ b/models/forgejo_migrations/v14a_add-forgejo-migrations-table.go @@ -0,0 +1,25 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations + +import ( + "forgejo.org/modules/timeutil" + + "xorm.io/xorm" +) + +func init() { + registerMigration(&Migration{ + Description: "add forgejo_migration table", + Upgrade: addForgejoMigration, + }) +} + +func addForgejoMigration(x *xorm.Engine) error { + type ForgejoMigration struct { + ID string `xorm:"pk"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` + } + return x.Sync(new(ForgejoMigration)) +} diff --git a/models/forgejo_migrations_legacy/main_test.go b/models/forgejo_migrations_legacy/main_test.go new file mode 100644 index 0000000000..2622af8e22 --- /dev/null +++ b/models/forgejo_migrations_legacy/main_test.go @@ -0,0 +1,14 @@ +// Copyright 2023 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package forgejo_migrations_legacy + +import ( + "testing" + + migration_tests "forgejo.org/models/gitea_migrations/test" +) + +func TestMain(m *testing.M) { + migration_tests.MainTest(m) +} diff --git a/models/forgejo_migrations_legacy/migrate.go b/models/forgejo_migrations_legacy/migrate.go new file mode 100644 index 0000000000..b1782de777 --- /dev/null +++ b/models/forgejo_migrations_legacy/migrate.go @@ -0,0 +1,256 @@ +// Copyright 2023 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package forgejo_migrations_legacy + +import ( + "context" + "errors" + "fmt" + "os" + + "forgejo.org/models/forgejo/semver" + "forgejo.org/models/forgejo_migrations" + forgejo_v1_20 "forgejo.org/models/forgejo_migrations_legacy/v1_20" + forgejo_v1_22 "forgejo.org/models/forgejo_migrations_legacy/v1_22" + "forgejo.org/modules/git" + "forgejo.org/modules/log" + "forgejo.org/modules/setting" + + "xorm.io/xorm" + "xorm.io/xorm/names" +) + +// ForgejoVersion describes the Forgejo version table. Should have only one row with id = 1. +type ForgejoVersion struct { + ID int64 `xorm:"pk autoincr"` + Version int64 +} + +type Migration struct { + description string + migrate func(*xorm.Engine) error +} + +// NewMigration creates a new migration. +func NewMigration(desc string, fn func(*xorm.Engine) error) *Migration { + return &Migration{desc, fn} +} + +// This is a sequence of additional Forgejo migrations. +// Add new migrations to the bottom of the list. +var migrations = []*Migration{ + // v0 -> v1 + NewMigration("Create the `forgejo_blocked_user` table", forgejo_v1_20.AddForgejoBlockedUser), + // v1 -> v2 + NewMigration("Create the `forgejo_sem_ver` table", forgejo_v1_20.CreateSemVerTable), + // v2 -> v3 + NewMigration("Create the `forgejo_auth_token` table", forgejo_v1_20.CreateAuthorizationTokenTable), + // v3 -> v4 + NewMigration("Add the `default_permissions` column to the `repo_unit` table", forgejo_v1_22.AddDefaultPermissionsToRepoUnit), + // v4 -> v5 + NewMigration("Create the `forgejo_repo_flag` table", forgejo_v1_22.CreateRepoFlagTable), + // v5 -> v6 + NewMigration("Add the `wiki_branch` column to the `repository` table", forgejo_v1_22.AddWikiBranchToRepository), + // v6 -> v7 + NewMigration("Add the `enable_repo_unit_hints` column to the `user` table", forgejo_v1_22.AddUserRepoUnitHintsSetting), + // v7 -> v8 + NewMigration("Modify the `release`.`note` content to remove SSH signatures", forgejo_v1_22.RemoveSSHSignaturesFromReleaseNotes), + // v8 -> v9 + NewMigration("Add the `apply_to_admins` column to the `protected_branch` table", forgejo_v1_22.AddApplyToAdminsSetting), + // v9 -> v10 + NewMigration("Add pronouns to user", forgejo_v1_22.AddPronounsToUser), + // v10 -> v11 + NewMigration("Add the `created` column to the `issue` table", forgejo_v1_22.AddCreatedToIssue), + // v11 -> v12 + NewMigration("Add repo_archive_download_count table", forgejo_v1_22.AddRepoArchiveDownloadCount), + // v12 -> v13 + NewMigration("Add `hide_archive_links` column to `release` table", AddHideArchiveLinksToRelease), + // v13 -> v14 + NewMigration("Remove Gitea-specific columns from the repository and badge tables", RemoveGiteaSpecificColumnsFromRepositoryAndBadge), + // v14 -> v15 + NewMigration("Create the `federation_host` table", CreateFederationHostTable), + // v15 -> v16 + NewMigration("Create the `federated_user` table", CreateFederatedUserTable), + // v16 -> v17 + NewMigration("Add `normalized_federated_uri` column to `user` table", AddNormalizedFederatedURIToUser), + // v17 -> v18 + NewMigration("Create the `following_repo` table", CreateFollowingRepoTable), + // v18 -> v19 + NewMigration("Add external_url to attachment table", AddExternalURLColumnToAttachmentTable), + // v19 -> v20 + NewMigration("Creating Quota-related tables", CreateQuotaTables), + // v20 -> v21 + NewMigration("Add SSH keypair to `pull_mirror` table", AddSSHKeypairToPushMirror), + // v21 -> v22 + NewMigration("Add `legacy` to `web_authn_credential` table", AddLegacyToWebAuthnCredential), + // v22 -> v23 + NewMigration("Add `delete_branch_after_merge` to `auto_merge` table", AddDeleteBranchAfterMergeToAutoMerge), + // v23 -> v24 + NewMigration("Add `purpose` column to `forgejo_auth_token` table", AddPurposeToForgejoAuthToken), + // v24 -> v25 + NewMigration("Migrate `secret` column to store keying material", MigrateTwoFactorToKeying), + // v25 -> v26 + NewMigration("Add `hash_blake2b` column to `package_blob` table", AddHashBlake2bToPackageBlob), + // v26 -> v27 + NewMigration("Add `created_unix` column to `user_redirect` table", AddCreatedUnixToRedirect), + // v27 -> v28 + NewMigration("Add pronoun privacy settings to user", AddHidePronounsOptionToUser), + // v28 -> v29 + NewMigration("Add public key information to `FederatedUser` and `FederationHost`", AddPublicKeyInformationForFederation), + // v29 -> v30 + NewMigration("Migrate `User.NormalizedFederatedURI` column to extract port & schema into FederatedHost", MigrateNormalizedFederatedURI), + // v30 -> v31 + 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("Noop because of https://codeberg.org/forgejo/forgejo/issues/8373", NoopAddIndexToActionRunStopped), + // v35 -> v36 + NewMigration("Fix wiki unit default permission", FixWikiUnitDefaultPermission), + // v36 -> v37 + NewMigration("Add `branch_filter` to `push_mirror` table", AddPushMirrorBranchFilter), + // v37 -> v38 + NewMigration("Add `resolved_unix` column to `abuse_report` table", AddResolvedUnixToAbuseReport), + // v38 -> v39 + NewMigration("Migrate `data` column of `secret` table to store keying material", MigrateActionSecretsToKeying), + // v39 -> v40 + NewMigration("Add index for release sha1", AddIndexForReleaseSha1), + // v40 -> v41 + NewMigration("Add foreign keys to stopwatch & tracked_time", AddForeignKeysStopwatchTrackedTime), + // v41 -> v42 + NewMigration("Add action_run concurrency fields", AddActionRunConcurrency), + // v42 -> v43 + NewMigration("Add action_run pre_execution_error field", AddActionRunPreExecutionError), + // v43 -> v44 + NewMigration("Add foreign keys to access", AddForeignKeysAccess), +} + +// GetCurrentDBVersion returns the current Forgejo database version. +func GetCurrentDBVersion(x *xorm.Engine) (int64, error) { + if err := x.Sync(new(ForgejoVersion)); err != nil { + return -1, fmt.Errorf("sync: %w", err) + } + + currentVersion := &ForgejoVersion{ID: 1} + has, err := x.Get(currentVersion) + if err != nil { + return -1, fmt.Errorf("get: %w", err) + } + if !has { + return -1, nil + } + return currentVersion.Version, nil +} + +// ExpectedVersion returns the expected Forgejo database version. +func ExpectedVersion() int64 { + return int64(len(migrations)) +} + +// EnsureUpToDate will check if the Forgejo database is at the correct version. +func EnsureUpToDate(x *xorm.Engine) error { + currentDB, err := GetCurrentDBVersion(x) + if err != nil { + return err + } + + if currentDB < 0 { + return errors.New("database has not been initialized") + } + + expected := ExpectedVersion() + + if currentDB != expected { + return fmt.Errorf(`current Forgejo database version %d is not equal to the expected version %d. Please run "forgejo [--config /path/to/app.ini] migrate" to update the database version`, currentDB, expected) + } + + return forgejoMigrationsEnsureUpToDate(x) +} + +var forgejoMigrationsEnsureUpToDate = forgejo_migrations.EnsureUpToDate + +// Migrate Forgejo database to current version. +func Migrate(x *xorm.Engine) error { + // Set a new clean the default mapper to GonicMapper as that is the default for . + x.SetMapper(names.GonicMapper{}) + if err := x.Sync(new(ForgejoVersion)); err != nil { + return fmt.Errorf("sync: %w", err) + } + + freshDB := false + var versionRecords []*ForgejoVersion + if err := x.Find(&versionRecords); err != nil { + return fmt.Errorf("find: %w", err) + } + if len(versionRecords) == 0 { + // If the version record does not exist we think it is a fresh installation and we can skip all migrations; + // engine init calls `SyncAllTables` which will create the fresh database. + upToDate := &ForgejoVersion{ID: 1, Version: ExpectedVersion()} + if _, err := x.InsertOne(upToDate); err != nil { + return fmt.Errorf("insert: %w", err) + } + // continue with the migration routine, but nothing will be applied; this allows transition into the newer + // forgejo_migration library and for it to be configured and populated. + versionRecords = []*ForgejoVersion{upToDate} + freshDB = true + } else if len(versionRecords) > 1 { + return fmt.Errorf( + "corrupt migrations: Forgejo database has unexpected records in the table `forgejo_version`; a single record is expected w/ ID=1, but %d records were found", + len(versionRecords)) + } + currentVersion := versionRecords[0] + if currentVersion.ID != 1 { + return fmt.Errorf( + "corrupt migrations: Forgejo database has corrupted records in the table `forgejo_version`; a single record with ID=1 is expected, but a record with ID=%d was found instead", currentVersion.ID) + } + + v := currentVersion.Version + + // Downgrading Forgejo's database version not supported + if v > ExpectedVersion() { + msg := fmt.Sprintf("Your Forgejo database (migration version: %d) is for a newer version of Forgejo, you cannot use the newer database for this old Forgejo release (%d).", v, ExpectedVersion()) + msg += "\nForgejo will exit to keep your database safe and unchanged. Please use the correct Forgejo release, do not change the migration version manually (incorrect manual operation may cause data loss)." + if !setting.IsProd { + msg += fmt.Sprintf("\nIf you are in development and really know what you're doing, you can force changing the migration version by executing: UPDATE forgejo_version SET version=%d WHERE id=1;", ExpectedVersion()) + } + _, _ = fmt.Fprintln(os.Stderr, msg) + log.Fatal(msg) + return nil + } + + // Some migration tasks depend on the git command + if git.DefaultContext == nil { + if err := git.InitSimple(context.Background()); err != nil { + return err + } + } + + // Migrate + for i, m := range migrations[v:] { + log.Info("Migration[%d]: %s", v+int64(i), m.description) + // Reset the mapper between each migration - migrations are not supposed to depend on each other + x.SetMapper(names.GonicMapper{}) + if err := m.migrate(x); err != nil { + return fmt.Errorf("migration[%d]: %s failed: %w", v+int64(i), m.description, err) + } + currentVersion.Version = v + int64(i) + 1 + if _, err := x.ID(1).Update(currentVersion); err != nil { + return err + } + } + + if err := x.Sync(new(semver.ForgejoSemVer)); err != nil { + return fmt.Errorf("sync: %w", err) + } + + if err := semver.SetVersionStringWithEngine(x, setting.ForgejoVersion); err != nil { + return fmt.Errorf("SetVersionStringWithEngine: %w", err) + } + + return forgejo_migrations.Migrate(x, freshDB) +} diff --git a/models/forgejo_migrations_legacy/migrate_test.go b/models/forgejo_migrations_legacy/migrate_test.go new file mode 100644 index 0000000000..4c1de20c26 --- /dev/null +++ b/models/forgejo_migrations_legacy/migrate_test.go @@ -0,0 +1,81 @@ +// Copyright 2023 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package forgejo_migrations_legacy + +import ( + "testing" + + migration_tests "forgejo.org/models/gitea_migrations/test" + "forgejo.org/modules/test" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "xorm.io/xorm" +) + +// TestEnsureUpToDate tests the behavior of EnsureUpToDate. +func TestEnsureUpToDate(t *testing.T) { + defer test.MockVariableValue(&forgejoMigrationsEnsureUpToDate, func(x *xorm.Engine) error { + return nil + })() + + x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoVersion)) + defer deferable() + if x == nil || t.Failed() { + return + } + + // Ensure error if there's no row in Forgejo Version. + err := EnsureUpToDate(x) + require.Error(t, err) + + // Insert 'good' Forgejo Version row. + _, err = x.InsertOne(&ForgejoVersion{ID: 1, Version: ExpectedVersion()}) + require.NoError(t, err) + + err = EnsureUpToDate(x) + require.NoError(t, err) + + // Modify forgejo version to have a lower version. + _, err = x.Exec("UPDATE `forgejo_version` SET version = ? WHERE id = 1", ExpectedVersion()-1) + require.NoError(t, err) + + err = EnsureUpToDate(x) + require.Error(t, err) +} + +func TestMigrateFreshDB(t *testing.T) { + x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoVersion)) + defer deferable() + require.NotNil(t, x) + + err := Migrate(x) + require.NoError(t, err) + + var versionRecords []*ForgejoVersion + err = x.Find(&versionRecords) + require.NoError(t, err) + require.Len(t, versionRecords, 1) + v := versionRecords[0] + assert.EqualValues(t, 1, v.ID) + assert.EqualValues(t, 44, v.Version) +} + +func TestMigrateFailWithCorruption(t *testing.T) { + x, deferable := migration_tests.PrepareTestEnv(t, 0, new(ForgejoVersion)) + defer deferable() + require.NotNil(t, x) + + // ID != 1 + _, err := x.InsertOne(&ForgejoVersion{ID: 100, Version: 100}) + require.NoError(t, err) + err = Migrate(x) + require.ErrorContains(t, err, "corrupted records in the table `forgejo_version`") + + // Two versions... + _, err = x.InsertOne(&ForgejoVersion{ID: 1, Version: 1000}) + require.NoError(t, err) + err = Migrate(x) + require.ErrorContains(t, err, "unexpected records in the table `forgejo_version`") +} diff --git a/models/forgejo_migrations/v13.go b/models/forgejo_migrations_legacy/v13.go similarity index 90% rename from models/forgejo_migrations/v13.go rename to models/forgejo_migrations_legacy/v13.go index ba4183885e..6c216b3732 100644 --- a/models/forgejo_migrations/v13.go +++ b/models/forgejo_migrations_legacy/v13.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v14.go b/models/forgejo_migrations_legacy/v14.go similarity index 91% rename from models/forgejo_migrations/v14.go rename to models/forgejo_migrations_legacy/v14.go index 65b857d343..7085fb0722 100644 --- a/models/forgejo_migrations/v14.go +++ b/models/forgejo_migrations_legacy/v14.go @@ -1,10 +1,10 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" ) diff --git a/models/forgejo_migrations/v15.go b/models/forgejo_migrations_legacy/v15.go similarity index 95% rename from models/forgejo_migrations/v15.go rename to models/forgejo_migrations_legacy/v15.go index a63199ab19..13e1651fd3 100644 --- a/models/forgejo_migrations/v15.go +++ b/models/forgejo_migrations_legacy/v15.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import ( "time" diff --git a/models/forgejo_migrations/v16.go b/models/forgejo_migrations_legacy/v16.go similarity index 93% rename from models/forgejo_migrations/v16.go rename to models/forgejo_migrations_legacy/v16.go index a7d4d5d590..7d18751262 100644 --- a/models/forgejo_migrations/v16.go +++ b/models/forgejo_migrations_legacy/v16.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v17.go b/models/forgejo_migrations_legacy/v17.go similarity index 90% rename from models/forgejo_migrations/v17.go rename to models/forgejo_migrations_legacy/v17.go index 8ef6f2c681..403351e221 100644 --- a/models/forgejo_migrations/v17.go +++ b/models/forgejo_migrations_legacy/v17.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v18.go b/models/forgejo_migrations_legacy/v18.go similarity index 94% rename from models/forgejo_migrations/v18.go rename to models/forgejo_migrations_legacy/v18.go index e39b0cbf10..e2d2937bfa 100644 --- a/models/forgejo_migrations/v18.go +++ b/models/forgejo_migrations_legacy/v18.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v19.go b/models/forgejo_migrations_legacy/v19.go similarity index 90% rename from models/forgejo_migrations/v19.go rename to models/forgejo_migrations_legacy/v19.go index 43d279dcb0..e0a48a388d 100644 --- a/models/forgejo_migrations/v19.go +++ b/models/forgejo_migrations_legacy/v19.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v1_20/v1.go b/models/forgejo_migrations_legacy/v1_20/v1.go similarity index 100% rename from models/forgejo_migrations/v1_20/v1.go rename to models/forgejo_migrations_legacy/v1_20/v1.go diff --git a/models/forgejo_migrations/v1_20/v2.go b/models/forgejo_migrations_legacy/v1_20/v2.go similarity index 100% rename from models/forgejo_migrations/v1_20/v2.go rename to models/forgejo_migrations_legacy/v1_20/v2.go diff --git a/models/forgejo_migrations/v1_20/v3.go b/models/forgejo_migrations_legacy/v1_20/v3.go similarity index 100% rename from models/forgejo_migrations/v1_20/v3.go rename to models/forgejo_migrations_legacy/v1_20/v3.go diff --git a/models/forgejo_migrations/v1_22/main_test.go b/models/forgejo_migrations_legacy/v1_22/main_test.go similarity index 76% rename from models/forgejo_migrations/v1_22/main_test.go rename to models/forgejo_migrations_legacy/v1_22/main_test.go index d6a5bdacee..26930f8e70 100644 --- a/models/forgejo_migrations/v1_22/main_test.go +++ b/models/forgejo_migrations_legacy/v1_22/main_test.go @@ -6,7 +6,7 @@ package v1_22 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" ) func TestMain(m *testing.M) { diff --git a/models/forgejo_migrations/v1_22/v10.go b/models/forgejo_migrations_legacy/v1_22/v10.go similarity index 100% rename from models/forgejo_migrations/v1_22/v10.go rename to models/forgejo_migrations_legacy/v1_22/v10.go diff --git a/models/forgejo_migrations/v1_22/v11.go b/models/forgejo_migrations_legacy/v1_22/v11.go similarity index 100% rename from models/forgejo_migrations/v1_22/v11.go rename to models/forgejo_migrations_legacy/v1_22/v11.go diff --git a/models/forgejo_migrations/v1_22/v12.go b/models/forgejo_migrations_legacy/v1_22/v12.go similarity index 100% rename from models/forgejo_migrations/v1_22/v12.go rename to models/forgejo_migrations_legacy/v1_22/v12.go diff --git a/models/forgejo_migrations/v1_22/v4.go b/models/forgejo_migrations_legacy/v1_22/v4.go similarity index 100% rename from models/forgejo_migrations/v1_22/v4.go rename to models/forgejo_migrations_legacy/v1_22/v4.go diff --git a/models/forgejo_migrations/v1_22/v5.go b/models/forgejo_migrations_legacy/v1_22/v5.go similarity index 100% rename from models/forgejo_migrations/v1_22/v5.go rename to models/forgejo_migrations_legacy/v1_22/v5.go diff --git a/models/forgejo_migrations/v1_22/v6.go b/models/forgejo_migrations_legacy/v1_22/v6.go similarity index 100% rename from models/forgejo_migrations/v1_22/v6.go rename to models/forgejo_migrations_legacy/v1_22/v6.go diff --git a/models/forgejo_migrations/v1_22/v7.go b/models/forgejo_migrations_legacy/v1_22/v7.go similarity index 100% rename from models/forgejo_migrations/v1_22/v7.go rename to models/forgejo_migrations_legacy/v1_22/v7.go diff --git a/models/forgejo_migrations/v1_22/v8.go b/models/forgejo_migrations_legacy/v1_22/v8.go similarity index 100% rename from models/forgejo_migrations/v1_22/v8.go rename to models/forgejo_migrations_legacy/v1_22/v8.go diff --git a/models/forgejo_migrations/v1_22/v8_test.go b/models/forgejo_migrations_legacy/v1_22/v8_test.go similarity index 93% rename from models/forgejo_migrations/v1_22/v8_test.go rename to models/forgejo_migrations_legacy/v1_22/v8_test.go index 5117dd2dfb..3ef4a05f9f 100644 --- a/models/forgejo_migrations/v1_22/v8_test.go +++ b/models/forgejo_migrations_legacy/v1_22/v8_test.go @@ -6,7 +6,7 @@ package v1_22 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/models/forgejo_migrations/v1_22/v9.go b/models/forgejo_migrations_legacy/v1_22/v9.go similarity index 100% rename from models/forgejo_migrations/v1_22/v9.go rename to models/forgejo_migrations_legacy/v1_22/v9.go diff --git a/models/forgejo_migrations/v20.go b/models/forgejo_migrations_legacy/v20.go similarity index 97% rename from models/forgejo_migrations/v20.go rename to models/forgejo_migrations_legacy/v20.go index 91c7b8e911..4bd0be6047 100644 --- a/models/forgejo_migrations/v20.go +++ b/models/forgejo_migrations_legacy/v20.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v21.go b/models/forgejo_migrations_legacy/v21.go similarity index 91% rename from models/forgejo_migrations/v21.go rename to models/forgejo_migrations_legacy/v21.go index 61d7950c5a..cc7da2772c 100644 --- a/models/forgejo_migrations/v21.go +++ b/models/forgejo_migrations_legacy/v21.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v22.go b/models/forgejo_migrations_legacy/v22.go similarity index 93% rename from models/forgejo_migrations/v22.go rename to models/forgejo_migrations_legacy/v22.go index 8078591da6..892a5ce851 100644 --- a/models/forgejo_migrations/v22.go +++ b/models/forgejo_migrations_legacy/v22.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v23.go b/models/forgejo_migrations_legacy/v23.go similarity index 93% rename from models/forgejo_migrations/v23.go rename to models/forgejo_migrations_legacy/v23.go index a79a4f3d6e..48aac74204 100644 --- a/models/forgejo_migrations/v23.go +++ b/models/forgejo_migrations_legacy/v23.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v24.go b/models/forgejo_migrations_legacy/v24.go similarity index 93% rename from models/forgejo_migrations/v24.go rename to models/forgejo_migrations_legacy/v24.go index 084a57e1ce..bf3b7b2220 100644 --- a/models/forgejo_migrations/v24.go +++ b/models/forgejo_migrations_legacy/v24.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v25.go b/models/forgejo_migrations_legacy/v25.go similarity index 96% rename from models/forgejo_migrations/v25.go rename to models/forgejo_migrations_legacy/v25.go index 56cde499a3..8c4b623c84 100644 --- a/models/forgejo_migrations/v25.go +++ b/models/forgejo_migrations_legacy/v25.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import ( "context" @@ -23,7 +23,7 @@ func MigrateTwoFactorToKeying(x *xorm.Engine) error { var err error // When upgrading from Forgejo v9 to v10, this migration will already be - // called from models/migrations/migrations.go migration 304 and must not + // called from models/gitea_migrations/migrations.go migration 304 and must not // be run twice. var version int _, err = x.Table("version").Where("`id` = 1").Select("version").Get(&version) diff --git a/models/forgejo_migrations/v25_test.go b/models/forgejo_migrations_legacy/v25_test.go similarity index 94% rename from models/forgejo_migrations/v25_test.go rename to models/forgejo_migrations_legacy/v25_test.go index 68e71da012..86286b2ab1 100644 --- a/models/forgejo_migrations/v25_test.go +++ b/models/forgejo_migrations_legacy/v25_test.go @@ -1,13 +1,13 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import ( "testing" "forgejo.org/models/auth" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "forgejo.org/modules/keying" "forgejo.org/modules/timeutil" diff --git a/models/forgejo_migrations/v26.go b/models/forgejo_migrations_legacy/v26.go similarity index 91% rename from models/forgejo_migrations/v26.go rename to models/forgejo_migrations_legacy/v26.go index a0c47799c2..03d05ad96d 100644 --- a/models/forgejo_migrations/v26.go +++ b/models/forgejo_migrations_legacy/v26.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v27.go b/models/forgejo_migrations_legacy/v27.go similarity index 92% rename from models/forgejo_migrations/v27.go rename to models/forgejo_migrations_legacy/v27.go index 9cfbc64370..531170d06f 100644 --- a/models/forgejo_migrations/v27.go +++ b/models/forgejo_migrations_legacy/v27.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import ( "forgejo.org/modules/timeutil" diff --git a/models/forgejo_migrations/v28.go b/models/forgejo_migrations_legacy/v28.go similarity index 90% rename from models/forgejo_migrations/v28.go rename to models/forgejo_migrations_legacy/v28.go index 19f0dcd862..443c3f5d66 100644 --- a/models/forgejo_migrations/v28.go +++ b/models/forgejo_migrations_legacy/v28.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v29.go b/models/forgejo_migrations_legacy/v29.go similarity index 94% rename from models/forgejo_migrations/v29.go rename to models/forgejo_migrations_legacy/v29.go index 92eb05e8b3..9b79139fcc 100644 --- a/models/forgejo_migrations/v29.go +++ b/models/forgejo_migrations_legacy/v29.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import ( "database/sql" diff --git a/models/forgejo_migrations/v30.go b/models/forgejo_migrations_legacy/v30.go similarity index 97% rename from models/forgejo_migrations/v30.go rename to models/forgejo_migrations_legacy/v30.go index 05a1dff898..2ffafd3fe7 100644 --- a/models/forgejo_migrations/v30.go +++ b/models/forgejo_migrations_legacy/v30.go @@ -1,12 +1,12 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import ( "time" - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "forgejo.org/modules/forgefed" "forgejo.org/modules/log" "forgejo.org/modules/timeutil" diff --git a/models/forgejo_migrations/v30_test.go b/models/forgejo_migrations_legacy/v30_test.go similarity index 96% rename from models/forgejo_migrations/v30_test.go rename to models/forgejo_migrations_legacy/v30_test.go index 152fddeb47..73bfefac90 100644 --- a/models/forgejo_migrations/v30_test.go +++ b/models/forgejo_migrations_legacy/v30_test.go @@ -1,13 +1,13 @@ // Copyright 2025 The Forgejo Authors. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import ( "testing" "time" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "forgejo.org/modules/timeutil" "github.com/stretchr/testify/require" diff --git a/models/forgejo_migrations/v31.go b/models/forgejo_migrations_legacy/v31.go similarity index 97% rename from models/forgejo_migrations/v31.go rename to models/forgejo_migrations_legacy/v31.go index 23397c7c13..f500ffa231 100644 --- a/models/forgejo_migrations/v31.go +++ b/models/forgejo_migrations_legacy/v31.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import ( "xorm.io/xorm" diff --git a/models/forgejo_migrations/v31_test.go b/models/forgejo_migrations_legacy/v31_test.go similarity index 89% rename from models/forgejo_migrations/v31_test.go rename to models/forgejo_migrations_legacy/v31_test.go index 6d1690aae0..0adc9cc69b 100644 --- a/models/forgejo_migrations/v31_test.go +++ b/models/forgejo_migrations_legacy/v31_test.go @@ -1,12 +1,12 @@ // Copyright 2025 The Forgejo Authors. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/require" ) diff --git a/models/forgejo_migrations/v32.go b/models/forgejo_migrations_legacy/v32.go similarity index 99% rename from models/forgejo_migrations/v32.go rename to models/forgejo_migrations_legacy/v32.go index ce3f855694..bd5176db1e 100644 --- a/models/forgejo_migrations/v32.go +++ b/models/forgejo_migrations_legacy/v32.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import ( "encoding/xml" diff --git a/models/forgejo_migrations/v32_test.go b/models/forgejo_migrations_legacy/v32_test.go similarity index 99% rename from models/forgejo_migrations/v32_test.go rename to models/forgejo_migrations_legacy/v32_test.go index 24cda891bc..6abb5f9004 100644 --- a/models/forgejo_migrations/v32_test.go +++ b/models/forgejo_migrations_legacy/v32_test.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import ( "bytes" @@ -11,7 +11,7 @@ import ( "strings" "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "forgejo.org/models/packages" "github.com/stretchr/testify/assert" diff --git a/models/forgejo_migrations/v33.go b/models/forgejo_migrations_legacy/v33.go similarity index 98% rename from models/forgejo_migrations/v33.go rename to models/forgejo_migrations_legacy/v33.go index b9ea8efe47..ce220d8179 100644 --- a/models/forgejo_migrations/v33.go +++ b/models/forgejo_migrations_legacy/v33.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import ( "fmt" diff --git a/models/forgejo_migrations/v33_test.go b/models/forgejo_migrations_legacy/v33_test.go similarity index 92% rename from models/forgejo_migrations/v33_test.go rename to models/forgejo_migrations_legacy/v33_test.go index 1d3298da15..a77dfed0c2 100644 --- a/models/forgejo_migrations/v33_test.go +++ b/models/forgejo_migrations_legacy/v33_test.go @@ -1,13 +1,13 @@ // Copyright 2025 The Forgejo Authors. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import ( "testing" "time" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "forgejo.org/modules/log" ft "forgejo.org/modules/test" diff --git a/models/forgejo_migrations/v34.go b/models/forgejo_migrations_legacy/v34.go similarity index 89% rename from models/forgejo_migrations/v34.go rename to models/forgejo_migrations_legacy/v34.go index d193d799e7..c1a1bfc8d6 100644 --- a/models/forgejo_migrations/v34.go +++ b/models/forgejo_migrations_legacy/v34.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v35.go b/models/forgejo_migrations_legacy/v35.go similarity index 88% rename from models/forgejo_migrations/v35.go rename to models/forgejo_migrations_legacy/v35.go index 9b389fcc12..ea3765fce5 100644 --- a/models/forgejo_migrations/v35.go +++ b/models/forgejo_migrations_legacy/v35.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import ( "xorm.io/xorm" diff --git a/models/forgejo_migrations/v36.go b/models/forgejo_migrations_legacy/v36.go similarity index 97% rename from models/forgejo_migrations/v36.go rename to models/forgejo_migrations_legacy/v36.go index 1a798147cf..5c13f3878e 100644 --- a/models/forgejo_migrations/v36.go +++ b/models/forgejo_migrations_legacy/v36.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import ( "xorm.io/xorm" diff --git a/models/forgejo_migrations/v37.go b/models/forgejo_migrations_legacy/v37.go similarity index 90% rename from models/forgejo_migrations/v37.go rename to models/forgejo_migrations_legacy/v37.go index 89358991af..508f798055 100644 --- a/models/forgejo_migrations/v37.go +++ b/models/forgejo_migrations_legacy/v37.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import ( "xorm.io/xorm" diff --git a/models/forgejo_migrations/v38.go b/models/forgejo_migrations_legacy/v38.go similarity index 92% rename from models/forgejo_migrations/v38.go rename to models/forgejo_migrations_legacy/v38.go index 24240f15a0..93cff2cabb 100644 --- a/models/forgejo_migrations/v38.go +++ b/models/forgejo_migrations_legacy/v38.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import ( "forgejo.org/modules/timeutil" diff --git a/models/forgejo_migrations/v39.go b/models/forgejo_migrations_legacy/v39.go similarity index 98% rename from models/forgejo_migrations/v39.go rename to models/forgejo_migrations_legacy/v39.go index 9af1c250b3..3f0d94b6aa 100644 --- a/models/forgejo_migrations/v39.go +++ b/models/forgejo_migrations_legacy/v39.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import ( "context" diff --git a/models/forgejo_migrations/v39_test.go b/models/forgejo_migrations_legacy/v39_test.go similarity index 94% rename from models/forgejo_migrations/v39_test.go rename to models/forgejo_migrations_legacy/v39_test.go index 42934d912f..6c82f7180f 100644 --- a/models/forgejo_migrations/v39_test.go +++ b/models/forgejo_migrations_legacy/v39_test.go @@ -1,12 +1,12 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations +package forgejo_migrations_legacy import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "forgejo.org/models/secret" "forgejo.org/modules/keying" "forgejo.org/modules/timeutil" diff --git a/models/forgejo_migrations/v40.go b/models/forgejo_migrations_legacy/v40.go similarity index 88% rename from models/forgejo_migrations/v40.go rename to models/forgejo_migrations_legacy/v40.go index 11e8fbd85e..50dccd393c 100644 --- a/models/forgejo_migrations/v40.go +++ b/models/forgejo_migrations_legacy/v40.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations +package forgejo_migrations_legacy import "xorm.io/xorm" diff --git a/models/forgejo_migrations_legacy/v41.go b/models/forgejo_migrations_legacy/v41.go new file mode 100644 index 0000000000..4b3306debd --- /dev/null +++ b/models/forgejo_migrations_legacy/v41.go @@ -0,0 +1,70 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations_legacy + +import ( + "errors" + "fmt" + + "forgejo.org/modules/log" + + "xorm.io/builder" + "xorm.io/xorm" +) + +func syncDoctorForeignKey(x *xorm.Engine, beans []any) error { + for _, bean := range beans { + // Sync() drops indexes by default, which will cause unnecessary rebuilding of indexes when syncDoctorForeignKey + // is used with partial bean definitions; so we disable that option + _, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, bean) + if err != nil { + if errors.Is(err, xorm.ErrForeignKeyViolation) { + tableName := x.TableName(bean) + log.Error( + "Foreign key creation on table %s failed. Run `forgejo doctor check --all` to identify the orphaned records preventing this foreign key from being created. Error was: %v", + tableName, err) + return err + } + return err + } + } + return nil +} + +func AddForeignKeysStopwatchTrackedTime(x *xorm.Engine) error { + type Stopwatch struct { + IssueID int64 `xorm:"INDEX REFERENCES(issue, id)"` + UserID int64 `xorm:"INDEX REFERENCES(user, id)"` + } + type TrackedTime struct { + ID int64 `xorm:"pk autoincr"` + IssueID int64 `xorm:"INDEX REFERENCES(issue, id)"` + UserID int64 `xorm:"INDEX REFERENCES(user, id)"` + } + + // TrackedTime.UserID used to be an intentionally dangling reference if a user was deleted, in order to maintain the + // time that was tracked against an issue. With the addition of a foreign key, we set UserID to NULL where the user + // doesn't exist instead of leaving it pointing to an invalid record: + var trackedTime []TrackedTime + err := x.Table("tracked_time"). + Join("LEFT", "`user`", "`tracked_time`.user_id = `user`.id"). + Where(builder.IsNull{"`user`.id"}). + Find(&trackedTime) + if err != nil { + return err + } + for _, tt := range trackedTime { + affected, err := x.Table(&TrackedTime{}).Where("id = ?", tt.ID).Update(map[string]any{"user_id": nil}) + if err != nil { + return err + } else if affected != 1 { + return fmt.Errorf("expected to update 1 tracked_time record with ID %d, but actually affected %d records", tt.ID, affected) + } + } + + return syncDoctorForeignKey(x, []any{ + new(Stopwatch), + new(TrackedTime), + }) +} diff --git a/models/forgejo_migrations_legacy/v42.go b/models/forgejo_migrations_legacy/v42.go new file mode 100644 index 0000000000..940c270699 --- /dev/null +++ b/models/forgejo_migrations_legacy/v42.go @@ -0,0 +1,20 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations_legacy + +import "xorm.io/xorm" + +func AddActionRunConcurrency(x *xorm.Engine) error { + type ActionRun struct { + RepoID int64 `xorm:"index unique(repo_index) index(concurrency)"` + Index int64 `xorm:"index unique(repo_index)"` + ConcurrencyGroup string `xorm:"index(concurrency)"` + ConcurrencyType int + } + _, err := x.SyncWithOptions(xorm.SyncOptions{ + // Sync drops indexes by default, and this local ActionRun doesn't have all the indexes -- so disable that. + IgnoreDropIndices: true, + }, new(ActionRun)) + return err +} diff --git a/models/forgejo_migrations_legacy/v43.go b/models/forgejo_migrations_legacy/v43.go new file mode 100644 index 0000000000..215299031c --- /dev/null +++ b/models/forgejo_migrations_legacy/v43.go @@ -0,0 +1,17 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations_legacy + +import "xorm.io/xorm" + +func AddActionRunPreExecutionError(x *xorm.Engine) error { + type ActionRun struct { + PreExecutionError string `xorm:"LONGTEXT"` + } + _, err := x.SyncWithOptions(xorm.SyncOptions{ + // Sync drops indexes by default, and this local ActionRun doesn't have all the indexes -- so disable that. + IgnoreDropIndices: true, + }, new(ActionRun)) + return err +} diff --git a/models/forgejo_migrations_legacy/v44.go b/models/forgejo_migrations_legacy/v44.go new file mode 100644 index 0000000000..459c7ac29d --- /dev/null +++ b/models/forgejo_migrations_legacy/v44.go @@ -0,0 +1,18 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations_legacy + +import ( + "xorm.io/xorm" +) + +func AddForeignKeysAccess(x *xorm.Engine) error { + type Access struct { + UserID int64 `xorm:"UNIQUE(s) REFERENCES(user, id)"` + RepoID int64 `xorm:"UNIQUE(s) REFERENCES(repository, id)"` + } + return syncDoctorForeignKey(x, []any{ + new(Access), + }) +} diff --git a/models/migrations/base/db.go b/models/gitea_migrations/base/db.go similarity index 59% rename from models/migrations/base/db.go rename to models/gitea_migrations/base/db.go index 7f7edda53b..2f70fc0806 100644 --- a/models/migrations/base/db.go +++ b/models/gitea_migrations/base/db.go @@ -8,8 +8,10 @@ import ( "fmt" "reflect" "regexp" + "slices" "strings" + "forgejo.org/models/db" "forgejo.org/modules/log" "forgejo.org/modules/setting" @@ -17,7 +19,12 @@ import ( "xorm.io/xorm/schemas" ) -// RecreateTables will recreate the tables for the provided beans using the newly provided bean definition and move all data to that new table +// RecreateTables returns a function that will recreate the tables for the provided beans using the newly provided bean +// definition, move all data to the new tables, and then replace the original tables with a drop and rename. +// +// If any 'base' table is requested to be rebuilt where one-or-more 'satellite' tables exists that references it through +// a foreign key, you must rebuild the satellite tables as well or you will receive an error 'incomplete table set'. +// // WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION func RecreateTables(beans ...any) func(*xorm.Engine) error { return func(x *xorm.Engine) error { @@ -27,34 +34,179 @@ func RecreateTables(beans ...any) func(*xorm.Engine) error { return err } sess = sess.StoreEngine("InnoDB") + + tableNames := make(map[any]string, len(beans)) + tempTableNames := make(map[any]string, len(beans)) + tempTableNamesByOriginalName := make(map[string]string, len(beans)) for _, bean := range beans { - log.Info("Recreating Table: %s for Bean: %s", x.TableName(bean), reflect.Indirect(reflect.ValueOf(bean)).Type().Name()) - if err := RecreateTable(sess, bean); err != nil { + tableName := sess.Engine().TableName(bean) + tableNames[bean] = tableName + tempTableName := fmt.Sprintf("tmp_recreate__%s", tableName) + tempTableNames[bean] = tempTableName + tempTableNamesByOriginalName[tableName] = tempTableName + } + + // Create a set of temp tables. + for _, bean := range beans { + log.Info("Creating temp table: %s for Bean: %s", tempTableNames[bean], reflect.Indirect(reflect.ValueOf(bean)).Type().Name()) + if err := createTempTable(sess, bean, tempTableNames[bean]); err != nil { return err } } + + // Our new temp tables tables will have foreign keys that point to the original tables we are recreating. + // Before we put data into these tables, we need to drop those foreign keys and add new foreign keys that point + // to the temp tables. + tableSchemas := make(map[any]*schemas.Table, len(beans)) + for _, bean := range beans { + tableSchema, err := sess.Engine().TableInfo(bean) + if err != nil { + log.Error("Unable to get table info. Error: %v", err) + return err + } + tableSchemas[bean] = tableSchema + modifications := make([]schemas.TableModification, 0, len(tableSchema.ForeignKeys)*2) + for _, fk := range tableSchema.ForeignKeys { + targetTempTableName, ok := tempTableNamesByOriginalName[fk.TargetTableName] + if !ok { + return fmt.Errorf("incomplete table set: Found a foreign key reference to table %s, but it is not included in RecreateTables", fk.TargetTableName) + } + fkName := fk.Name + if setting.Database.Type.IsMySQL() { + // See MySQL explanation in createTempTable. + fkName = "_" + fkName + } + modifications = append(modifications, schemas.DropForeignKey{ForeignKey: schemas.ForeignKey{ + Name: fkName, + SourceFieldName: fk.SourceFieldName, + TargetTableName: fk.TargetTableName, + TargetFieldName: fk.TargetFieldName, + }}) + modifications = append(modifications, schemas.AddForeignKey{ForeignKey: schemas.ForeignKey{ + Name: fkName, + SourceFieldName: fk.SourceFieldName, + TargetTableName: targetTempTableName, // FK changed to new temp table + TargetFieldName: fk.TargetFieldName, + }}) + } + + if len(modifications) != 0 { + log.Info("Modifying temp table %s foreign keys to point to other temp tables", tempTableNames[bean]) + if err := sess.Table(tempTableNames[bean]).AlterTable(bean, modifications...); err != nil { + return fmt.Errorf("alter table failed: while rewriting foreign keys to temp tables, error occurred: %w", err) + } + } + } + + // Insert into the set of temp tables in the right order, starting with base tables, working outwards to + // satellite tables. + orderedBeans := slices.Clone(beans) + slices.SortFunc(orderedBeans, func(b1, b2 any) int { + return db.TableNameInsertionOrderSortFunc(tableNames[b1], tableNames[b2]) + }) + for _, bean := range orderedBeans { + log.Info("Copying table %s to temp table %s", tableNames[bean], tempTableNames[bean]) + if err := copyData(sess, bean, tableNames[bean], tempTableNames[bean]); err != nil { + // copyData does its own logging + return err + } + } + + // Drop all the old tables in the right order, starting with satellite tables working inwards to base tables, + // and rename all the temp tables to the final tables. The database will automatically update the foreign key + // references during the rename from temp to final tables. + for i := len(orderedBeans) - 1; i >= 0; i-- { + bean := orderedBeans[i] + log.Info("Dropping existing table %s, and renaming temp table %s in its place", tableNames[bean], tempTableNames[bean]) + if err := renameTable(sess, bean, tableNames[bean], tempTableNames[bean], tableSchemas[bean]); err != nil { + // renameTable does its own logging + return err + } + } + return sess.Commit() } } -// RecreateTable will recreate the table using the newly provided bean definition and move all data to that new table +// LegacyRecreateTable will recreate the table using the newly provided bean definition and move all data to that new +// table. +// // WARNING: YOU MUST PROVIDE THE FULL BEAN DEFINITION +// // WARNING: YOU MUST COMMIT THE SESSION AT THE END -func RecreateTable(sess *xorm.Session, bean any) error { - // TODO: This will not work if there are foreign keys - +// +// Deprecated: LegacyRecreateTable exists for historical migrations and should not be used in current code -- tt does +// not support foreign key management. Use RecreateTables instead which provides foreign key support. +func LegacyRecreateTable(sess *xorm.Session, bean any) error { tableName := sess.Engine().TableName(bean) tempTableName := fmt.Sprintf("tmp_recreate__%s", tableName) + tableSchema, err := sess.Engine().TableInfo(bean) + if err != nil { + log.Error("Unable to get table info. Error: %v", err) + return err + } + // We need to move the old table away and create a new one with the correct columns // We will need to do this in stages to prevent data loss // // First create the temporary table - if err := sess.Table(tempTableName).CreateTable(bean); err != nil { - log.Error("Unable to create table %s. Error: %v", tempTableName, err) + if err := createTempTable(sess, bean, tempTableName); err != nil { + // createTempTable does its own logging return err } + if err := copyData(sess, bean, tableName, tempTableName); err != nil { + // copyData does its own logging + return err + } + + if err := renameTable(sess, bean, tableName, tempTableName, tableSchema); err != nil { + // renameTable does its own logging + return err + } + + return nil +} + +func createTempTable(sess *xorm.Session, bean any, tempTableName string) error { + if setting.Database.Type.IsMySQL() { + // Can't have identical foreign key names in MySQL, and Table(tempTableName) only affects the table name and not + // the schema definition generated from the bean, so, we do a little adjusting by appending a `_` at the + // beginning of each foreign key name on the temp table. We'll remove this by renaming the constraint after we + // drop the original table, in renameTable. + originalTableSchema, err := sess.Engine().TableInfo(bean) + if err != nil { + log.Error("Unable to get table info. Error: %v", err) + return err + } + + // `TableInfo()` will return a `*schema.Table` that is stored in a shared cache. We don't want to mutate that + // object as it will stick around and affect other things. Make a mostly-shallow clone, with a new slice for + // what we're changing. + tableSchema := *originalTableSchema + tableSchema.ForeignKeys = slices.Clone(originalTableSchema.ForeignKeys) + for i := range tableSchema.ForeignKeys { + tableSchema.ForeignKeys[i].Name = "_" + tableSchema.ForeignKeys[i].Name + } + + sql, _, err := sess.Engine().Dialect().CreateTableSQL(&tableSchema, tempTableName) + if err != nil { + log.Error("Unable to generate CREATE TABLE query. Error: %v", err) + return err + } + _, err = sess.Exec(sql) + if err != nil { + log.Error("Unable to create table %s. Error: %v", tempTableName, err) + return err + } + } else { + if err := sess.Table(tempTableName).CreateTable(bean); err != nil { + log.Error("Unable to create table %s. Error: %v", tempTableName, err) + return err + } + } + if err := sess.Table(tempTableName).CreateUniques(bean); err != nil { log.Error("Unable to create uniques for table %s. Error: %v", tempTableName, err) return err @@ -65,11 +217,14 @@ func RecreateTable(sess *xorm.Session, bean any) error { return err } + return nil +} + +func copyData(sess *xorm.Session, bean any, tableName, tempTableName string) error { // Work out the column names from the bean - these are the columns to select from the old table and install into the new table table, err := sess.Engine().TableInfo(bean) if err != nil { log.Error("Unable to get table info. Error: %v", err) - return err } newTableColumns := table.Columns() @@ -128,9 +283,12 @@ func RecreateTable(sess *xorm.Session, bean any) error { return err } + return nil +} + +func renameTable(sess *xorm.Session, bean any, tableName, tempTableName string, tableSchema *schemas.Table) error { switch { case setting.Database.Type.IsSQLite3(): - // SQLite will drop all the constraints on the old table if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { log.Error("Unable to drop old table %s. Error: %v", tableName, err) return err @@ -157,32 +315,44 @@ func RecreateTable(sess *xorm.Session, bean any) error { } case setting.Database.Type.IsMySQL(): - // MySQL will drop all the constraints on the old table if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { log.Error("Unable to drop old table %s. Error: %v", tableName, err) return err } - if err := sess.Table(tempTableName).DropIndexes(bean); err != nil { - log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err) - return err - } - - // SQLite and MySQL will move all the constraints from the temporary table to the new table + // MySQL will move all the constraints that reference this table from the temporary table to the new table if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil { log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) return err } - if err := sess.Table(tableName).CreateIndexes(bean); err != nil { - log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err) - return err + // In `RecreateTables` the foreign keys were renamed with a '_' prefix to avoid conflicting on the original + // table's constraint names. Now that table has been dropped, so we can rename them back to leave the table in + // the right state. Unfortunately this will cause a recheck of the constraint's validity against the target + // table which will be slow for large tables, but it's unavoidable without the ability to rename constraints + // in-place. Awkwardly these FKs are still a reference to the tmp_recreate target table since we drop in reverse + // FK order -- the ALTER TABLE ... RENAME .. on those tmp tables will correct the FKs later. + modifications := make([]schemas.TableModification, 0, len(tableSchema.ForeignKeys)*2) + for _, fk := range tableSchema.ForeignKeys { + modifications = append(modifications, schemas.DropForeignKey{ForeignKey: schemas.ForeignKey{ + Name: "_" + fk.Name, + SourceFieldName: fk.SourceFieldName, + TargetTableName: fmt.Sprintf("tmp_recreate__%s", fk.TargetTableName), + TargetFieldName: fk.TargetFieldName, + }}) + modifications = append(modifications, schemas.AddForeignKey{ForeignKey: schemas.ForeignKey{ + Name: fk.Name, + SourceFieldName: fk.SourceFieldName, + TargetTableName: fmt.Sprintf("tmp_recreate__%s", fk.TargetTableName), + TargetFieldName: fk.TargetFieldName, + }}) + } + if len(modifications) != 0 { + if err := sess.Table(tableName).AlterTable(bean, modifications...); err != nil { + return fmt.Errorf("alter table failed: while rewriting foreign keys to original names, error occurred: %w", err) + } } - if err := sess.Table(tableName).CreateUniques(bean); err != nil { - log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err) - return err - } case setting.Database.Type.IsPostgreSQL(): var originalSequences []string type sequenceData struct { @@ -193,7 +363,7 @@ func RecreateTable(sess *xorm.Session, bean any) error { schema := sess.Engine().Dialect().URI().Schema sess.Engine().SetSchema("") - if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE ? || '_%' AND sequence_catalog = ?", tableName, setting.Database.Name).Find(&originalSequences); err != nil { + if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE ? || '_id_seq' AND sequence_catalog = ?", tableName, setting.Database.Name).Find(&originalSequences); err != nil { log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) return err } @@ -238,7 +408,7 @@ func RecreateTable(sess *xorm.Session, bean any) error { var sequences []string sess.Engine().SetSchema("") - if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__' || ? || '_%' AND sequence_catalog = ?", tableName, setting.Database.Name).Find(&sequences); err != nil { + if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__' || ? || '_id_seq' AND sequence_catalog = ?", tableName, setting.Database.Name).Find(&sequences); err != nil { log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) return err } @@ -275,6 +445,7 @@ func RecreateTable(sess *xorm.Session, bean any) error { default: log.Fatal("Unrecognized DB") } + return nil } diff --git a/models/migrations/base/db_test.go b/models/gitea_migrations/base/db_test.go similarity index 97% rename from models/migrations/base/db_test.go rename to models/gitea_migrations/base/db_test.go index 4a610e065d..4633c7de10 100644 --- a/models/migrations/base/db_test.go +++ b/models/gitea_migrations/base/db_test.go @@ -6,7 +6,7 @@ package base import ( "testing" - migrations_tests "forgejo.org/models/migrations/test" + migrations_tests "forgejo.org/models/gitea_migrations/test" "forgejo.org/modules/timeutil" "xorm.io/xorm/names" diff --git a/models/migrations/base/hash.go b/models/gitea_migrations/base/hash.go similarity index 100% rename from models/migrations/base/hash.go rename to models/gitea_migrations/base/hash.go diff --git a/models/migrations/base/main_test.go b/models/gitea_migrations/base/main_test.go similarity index 75% rename from models/migrations/base/main_test.go rename to models/gitea_migrations/base/main_test.go index 2b3889441a..73bc7a3521 100644 --- a/models/migrations/base/main_test.go +++ b/models/gitea_migrations/base/main_test.go @@ -6,7 +6,7 @@ package base import ( "testing" - migrations_tests "forgejo.org/models/migrations/test" + migrations_tests "forgejo.org/models/gitea_migrations/test" ) func TestMain(m *testing.M) { diff --git a/models/migrations/fixtures/Test_AddCombinedIndexToIssueUser/issue_user.yml b/models/gitea_migrations/fixtures/Test_AddCombinedIndexToIssueUser/issue_user.yml similarity index 100% rename from models/migrations/fixtures/Test_AddCombinedIndexToIssueUser/issue_user.yml rename to models/gitea_migrations/fixtures/Test_AddCombinedIndexToIssueUser/issue_user.yml diff --git a/models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/oauth2_application.yml b/models/gitea_migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/oauth2_application.yml similarity index 100% rename from models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/oauth2_application.yml rename to models/gitea_migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/oauth2_application.yml diff --git a/models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/expected_webhook.yml b/models/gitea_migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/expected_webhook.yml similarity index 100% rename from models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/expected_webhook.yml rename to models/gitea_migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/expected_webhook.yml diff --git a/models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/hook_task.yml b/models/gitea_migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/hook_task.yml similarity index 100% rename from models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/hook_task.yml rename to models/gitea_migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/hook_task.yml diff --git a/models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/webhook.yml b/models/gitea_migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/webhook.yml similarity index 100% rename from models/migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/webhook.yml rename to models/gitea_migrations/fixtures/Test_AddHeaderAuthorizationEncryptedColWebhook/webhook.yml diff --git a/models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml b/models/gitea_migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml similarity index 100% rename from models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml rename to models/gitea_migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml diff --git a/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml b/models/gitea_migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml similarity index 100% rename from models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml rename to models/gitea_migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task.yml diff --git a/models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml b/models/gitea_migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml similarity index 100% rename from models/migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml rename to models/gitea_migrations/fixtures/Test_AddPayloadVersionToHookTaskTable/hook_task_migrated.yml diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml b/models/gitea_migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml similarity index 100% rename from models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml rename to models/gitea_migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml b/models/gitea_migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml similarity index 100% rename from models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml rename to models/gitea_migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml b/models/gitea_migrations/fixtures/Test_AddRepoIDForAttachment/release.yml similarity index 100% rename from models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml rename to models/gitea_migrations/fixtures/Test_AddRepoIDForAttachment/release.yml diff --git a/models/migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml b/models/gitea_migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml similarity index 100% rename from models/migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml rename to models/gitea_migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml diff --git a/models/migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package.yml b/models/gitea_migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package.yml similarity index 100% rename from models/migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package.yml rename to models/gitea_migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package.yml diff --git a/models/migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package_blob.yml b/models/gitea_migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package_blob.yml similarity index 100% rename from models/migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package_blob.yml rename to models/gitea_migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package_blob.yml diff --git a/models/migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package_file.yml b/models/gitea_migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package_file.yml similarity index 100% rename from models/migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package_file.yml rename to models/gitea_migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package_file.yml diff --git a/models/migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package_version.yml b/models/gitea_migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package_version.yml similarity index 100% rename from models/migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package_version.yml rename to models/gitea_migrations/fixtures/Test_ChangeMavenArtifactConcatenation/package_version.yml diff --git a/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml b/models/gitea_migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml similarity index 100% rename from models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml rename to models/gitea_migrations/fixtures/Test_CheckProjectColumnsConsistency/project.yml diff --git a/models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml b/models/gitea_migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml similarity index 100% rename from models/migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml rename to models/gitea_migrations/fixtures/Test_CheckProjectColumnsConsistency/project_board.yml diff --git a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml b/models/gitea_migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml similarity index 100% rename from models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml rename to models/gitea_migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml diff --git a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml b/models/gitea_migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml similarity index 100% rename from models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml rename to models/gitea_migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml diff --git a/models/migrations/fixtures/Test_MigrateActionSecretToKeying/secret.yml b/models/gitea_migrations/fixtures/Test_MigrateActionSecretToKeying/secret.yml similarity index 100% rename from models/migrations/fixtures/Test_MigrateActionSecretToKeying/secret.yml rename to models/gitea_migrations/fixtures/Test_MigrateActionSecretToKeying/secret.yml diff --git a/models/migrations/fixtures/Test_MigrateNormalizedFederatedURI/federated_user.yaml b/models/gitea_migrations/fixtures/Test_MigrateNormalizedFederatedURI/federated_user.yaml similarity index 100% rename from models/migrations/fixtures/Test_MigrateNormalizedFederatedURI/federated_user.yaml rename to models/gitea_migrations/fixtures/Test_MigrateNormalizedFederatedURI/federated_user.yaml diff --git a/models/migrations/fixtures/Test_MigrateNormalizedFederatedURI/federation_host.yaml b/models/gitea_migrations/fixtures/Test_MigrateNormalizedFederatedURI/federation_host.yaml similarity index 100% rename from models/migrations/fixtures/Test_MigrateNormalizedFederatedURI/federation_host.yaml rename to models/gitea_migrations/fixtures/Test_MigrateNormalizedFederatedURI/federation_host.yaml diff --git a/models/migrations/fixtures/Test_MigrateNormalizedFederatedURI/user.yaml b/models/gitea_migrations/fixtures/Test_MigrateNormalizedFederatedURI/user.yaml similarity index 100% rename from models/migrations/fixtures/Test_MigrateNormalizedFederatedURI/user.yaml rename to models/gitea_migrations/fixtures/Test_MigrateNormalizedFederatedURI/user.yaml diff --git a/models/migrations/fixtures/Test_MigrateTwoFactorToKeying/two_factor.yml b/models/gitea_migrations/fixtures/Test_MigrateTwoFactorToKeying/two_factor.yml similarity index 100% rename from models/migrations/fixtures/Test_MigrateTwoFactorToKeying/two_factor.yml rename to models/gitea_migrations/fixtures/Test_MigrateTwoFactorToKeying/two_factor.yml diff --git a/models/migrations/fixtures/Test_RemigrateU2FCredentials/expected_webauthn_credential.yml b/models/gitea_migrations/fixtures/Test_RemigrateU2FCredentials/expected_webauthn_credential.yml similarity index 100% rename from models/migrations/fixtures/Test_RemigrateU2FCredentials/expected_webauthn_credential.yml rename to models/gitea_migrations/fixtures/Test_RemigrateU2FCredentials/expected_webauthn_credential.yml diff --git a/models/migrations/fixtures/Test_RemigrateU2FCredentials/u2f_registration.yml b/models/gitea_migrations/fixtures/Test_RemigrateU2FCredentials/u2f_registration.yml similarity index 100% rename from models/migrations/fixtures/Test_RemigrateU2FCredentials/u2f_registration.yml rename to models/gitea_migrations/fixtures/Test_RemigrateU2FCredentials/u2f_registration.yml diff --git a/models/migrations/fixtures/Test_RemigrateU2FCredentials/webauthn_credential.yml b/models/gitea_migrations/fixtures/Test_RemigrateU2FCredentials/webauthn_credential.yml similarity index 100% rename from models/migrations/fixtures/Test_RemigrateU2FCredentials/webauthn_credential.yml rename to models/gitea_migrations/fixtures/Test_RemigrateU2FCredentials/webauthn_credential.yml diff --git a/models/migrations/fixtures/Test_RemoveInvalidLabels/comment.yml b/models/gitea_migrations/fixtures/Test_RemoveInvalidLabels/comment.yml similarity index 100% rename from models/migrations/fixtures/Test_RemoveInvalidLabels/comment.yml rename to models/gitea_migrations/fixtures/Test_RemoveInvalidLabels/comment.yml diff --git a/models/migrations/fixtures/Test_RemoveInvalidLabels/issue.yml b/models/gitea_migrations/fixtures/Test_RemoveInvalidLabels/issue.yml similarity index 100% rename from models/migrations/fixtures/Test_RemoveInvalidLabels/issue.yml rename to models/gitea_migrations/fixtures/Test_RemoveInvalidLabels/issue.yml diff --git a/models/migrations/fixtures/Test_RemoveInvalidLabels/issue_label.yml b/models/gitea_migrations/fixtures/Test_RemoveInvalidLabels/issue_label.yml similarity index 100% rename from models/migrations/fixtures/Test_RemoveInvalidLabels/issue_label.yml rename to models/gitea_migrations/fixtures/Test_RemoveInvalidLabels/issue_label.yml diff --git a/models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml b/models/gitea_migrations/fixtures/Test_RemoveInvalidLabels/label.yml similarity index 100% rename from models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml rename to models/gitea_migrations/fixtures/Test_RemoveInvalidLabels/label.yml diff --git a/models/migrations/fixtures/Test_RemoveInvalidLabels/repository.yml b/models/gitea_migrations/fixtures/Test_RemoveInvalidLabels/repository.yml similarity index 100% rename from models/migrations/fixtures/Test_RemoveInvalidLabels/repository.yml rename to models/gitea_migrations/fixtures/Test_RemoveInvalidLabels/repository.yml diff --git a/models/migrations/fixtures/Test_RemoveSSHSignaturesFromReleaseNotes/release.yml b/models/gitea_migrations/fixtures/Test_RemoveSSHSignaturesFromReleaseNotes/release.yml similarity index 100% rename from models/migrations/fixtures/Test_RemoveSSHSignaturesFromReleaseNotes/release.yml rename to models/gitea_migrations/fixtures/Test_RemoveSSHSignaturesFromReleaseNotes/release.yml diff --git a/models/migrations/fixtures/Test_RepositoryFormat/comment.yml b/models/gitea_migrations/fixtures/Test_RepositoryFormat/comment.yml similarity index 100% rename from models/migrations/fixtures/Test_RepositoryFormat/comment.yml rename to models/gitea_migrations/fixtures/Test_RepositoryFormat/comment.yml diff --git a/models/migrations/fixtures/Test_RepositoryFormat/commit_status.yml b/models/gitea_migrations/fixtures/Test_RepositoryFormat/commit_status.yml similarity index 100% rename from models/migrations/fixtures/Test_RepositoryFormat/commit_status.yml rename to models/gitea_migrations/fixtures/Test_RepositoryFormat/commit_status.yml diff --git a/models/migrations/fixtures/Test_RepositoryFormat/pull_request.yml b/models/gitea_migrations/fixtures/Test_RepositoryFormat/pull_request.yml similarity index 100% rename from models/migrations/fixtures/Test_RepositoryFormat/pull_request.yml rename to models/gitea_migrations/fixtures/Test_RepositoryFormat/pull_request.yml diff --git a/models/migrations/fixtures/Test_RepositoryFormat/release.yml b/models/gitea_migrations/fixtures/Test_RepositoryFormat/release.yml similarity index 100% rename from models/migrations/fixtures/Test_RepositoryFormat/release.yml rename to models/gitea_migrations/fixtures/Test_RepositoryFormat/release.yml diff --git a/models/migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml b/models/gitea_migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml similarity index 100% rename from models/migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml rename to models/gitea_migrations/fixtures/Test_RepositoryFormat/repo_archiver.yml diff --git a/models/migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml b/models/gitea_migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml similarity index 100% rename from models/migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml rename to models/gitea_migrations/fixtures/Test_RepositoryFormat/repo_indexer_status.yml diff --git a/models/migrations/fixtures/Test_RepositoryFormat/repository.yml b/models/gitea_migrations/fixtures/Test_RepositoryFormat/repository.yml similarity index 100% rename from models/migrations/fixtures/Test_RepositoryFormat/repository.yml rename to models/gitea_migrations/fixtures/Test_RepositoryFormat/repository.yml diff --git a/models/migrations/fixtures/Test_RepositoryFormat/review_state.yml b/models/gitea_migrations/fixtures/Test_RepositoryFormat/review_state.yml similarity index 100% rename from models/migrations/fixtures/Test_RepositoryFormat/review_state.yml rename to models/gitea_migrations/fixtures/Test_RepositoryFormat/review_state.yml diff --git a/models/migrations/fixtures/Test_SetTopicsAsEmptySlice/repository.yml b/models/gitea_migrations/fixtures/Test_SetTopicsAsEmptySlice/repository.yml similarity index 100% rename from models/migrations/fixtures/Test_SetTopicsAsEmptySlice/repository.yml rename to models/gitea_migrations/fixtures/Test_SetTopicsAsEmptySlice/repository.yml diff --git a/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml b/models/gitea_migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml similarity index 100% rename from models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml rename to models/gitea_migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/expected_webauthn_credential.yml diff --git a/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml b/models/gitea_migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml similarity index 100% rename from models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml rename to models/gitea_migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml diff --git a/models/migrations/fixtures/Test_UnwrapLDAPSourceCfg/login_source.yml b/models/gitea_migrations/fixtures/Test_UnwrapLDAPSourceCfg/login_source.yml similarity index 100% rename from models/migrations/fixtures/Test_UnwrapLDAPSourceCfg/login_source.yml rename to models/gitea_migrations/fixtures/Test_UnwrapLDAPSourceCfg/login_source.yml diff --git a/models/migrations/fixtures/Test_UpdateBadgeColName/badge.yml b/models/gitea_migrations/fixtures/Test_UpdateBadgeColName/badge.yml similarity index 100% rename from models/migrations/fixtures/Test_UpdateBadgeColName/badge.yml rename to models/gitea_migrations/fixtures/Test_UpdateBadgeColName/badge.yml diff --git a/models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/expected_milestone.yml b/models/gitea_migrations/fixtures/Test_UpdateOpenMilestoneCounts/expected_milestone.yml similarity index 100% rename from models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/expected_milestone.yml rename to models/gitea_migrations/fixtures/Test_UpdateOpenMilestoneCounts/expected_milestone.yml diff --git a/models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/issue.yml b/models/gitea_migrations/fixtures/Test_UpdateOpenMilestoneCounts/issue.yml similarity index 100% rename from models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/issue.yml rename to models/gitea_migrations/fixtures/Test_UpdateOpenMilestoneCounts/issue.yml diff --git a/models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/milestone.yml b/models/gitea_migrations/fixtures/Test_UpdateOpenMilestoneCounts/milestone.yml similarity index 100% rename from models/migrations/fixtures/Test_UpdateOpenMilestoneCounts/milestone.yml rename to models/gitea_migrations/fixtures/Test_UpdateOpenMilestoneCounts/milestone.yml diff --git a/models/gitea_migrations/main_test.go b/models/gitea_migrations/main_test.go new file mode 100644 index 0000000000..64f8cadd27 --- /dev/null +++ b/models/gitea_migrations/main_test.go @@ -0,0 +1,14 @@ +// Copyright 2023 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitea_migrations + +import ( + "testing" + + migration_tests "forgejo.org/models/gitea_migrations/test" +) + +func TestMain(m *testing.M) { + migration_tests.MainTest(m) +} diff --git a/models/migrations/migrations.go b/models/gitea_migrations/migrations.go similarity index 92% rename from models/migrations/migrations.go rename to models/gitea_migrations/migrations.go index 2a5b97f519..be5cc8ebee 100644 --- a/models/migrations/migrations.go +++ b/models/gitea_migrations/migrations.go @@ -2,7 +2,7 @@ // Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package migrations +package gitea_migrations import ( "context" @@ -10,25 +10,25 @@ import ( "fmt" "forgejo.org/models/db" - "forgejo.org/models/forgejo_migrations" - "forgejo.org/models/migrations/v1_10" - "forgejo.org/models/migrations/v1_11" - "forgejo.org/models/migrations/v1_12" - "forgejo.org/models/migrations/v1_13" - "forgejo.org/models/migrations/v1_14" - "forgejo.org/models/migrations/v1_15" - "forgejo.org/models/migrations/v1_16" - "forgejo.org/models/migrations/v1_17" - "forgejo.org/models/migrations/v1_18" - "forgejo.org/models/migrations/v1_19" - "forgejo.org/models/migrations/v1_20" - "forgejo.org/models/migrations/v1_21" - "forgejo.org/models/migrations/v1_22" - "forgejo.org/models/migrations/v1_23" - "forgejo.org/models/migrations/v1_6" - "forgejo.org/models/migrations/v1_7" - "forgejo.org/models/migrations/v1_8" - "forgejo.org/models/migrations/v1_9" + "forgejo.org/models/forgejo_migrations_legacy" + "forgejo.org/models/gitea_migrations/v1_10" + "forgejo.org/models/gitea_migrations/v1_11" + "forgejo.org/models/gitea_migrations/v1_12" + "forgejo.org/models/gitea_migrations/v1_13" + "forgejo.org/models/gitea_migrations/v1_14" + "forgejo.org/models/gitea_migrations/v1_15" + "forgejo.org/models/gitea_migrations/v1_16" + "forgejo.org/models/gitea_migrations/v1_17" + "forgejo.org/models/gitea_migrations/v1_18" + "forgejo.org/models/gitea_migrations/v1_19" + "forgejo.org/models/gitea_migrations/v1_20" + "forgejo.org/models/gitea_migrations/v1_21" + "forgejo.org/models/gitea_migrations/v1_22" + "forgejo.org/models/gitea_migrations/v1_23" + "forgejo.org/models/gitea_migrations/v1_6" + "forgejo.org/models/gitea_migrations/v1_7" + "forgejo.org/models/gitea_migrations/v1_8" + "forgejo.org/models/gitea_migrations/v1_9" "forgejo.org/modules/git" "forgejo.org/modules/log" "forgejo.org/modules/setting" @@ -367,7 +367,7 @@ func prepareMigrationTasks() []*migration { // Migration to Forgejo v10 newMigration(303, "Gitea last drop", v1_23.GiteaLastDrop), - newMigration(304, "Migrate `secret` column to store keying material", forgejo_migrations.MigrateTwoFactorToKeying), + newMigration(304, "Migrate `secret` column to store keying material", forgejo_migrations_legacy.MigrateTwoFactorToKeying), } return preparedMigrations } @@ -426,7 +426,7 @@ func EnsureUpToDate(x *xorm.Engine) error { return fmt.Errorf(`current database version %d is not equal to the expected version %d. Please run "forgejo [--config /path/to/app.ini] migrate" to update the database version`, currentDB, expectedDB) } - return forgejo_migrations.EnsureUpToDate(x) + return forgejo_migrations_legacy.EnsureUpToDate(x) } func getPendingMigrations(curDBVer int64, migrations []*migration) []*migration { @@ -449,20 +449,31 @@ func Migrate(x *xorm.Engine) error { } var previousVersion int64 - currentVersion := &Version{ID: 1} - has, err := x.Get(currentVersion) - if err != nil { - return fmt.Errorf("get: %w", err) - } else if !has { - // If the version record does not exist, it is a fresh installation, and we can skip all migrations. - // XORM model framework will create all tables when initializing. - currentVersion.ID = 0 - currentVersion.Version = maxDBVer - if _, err = x.InsertOne(currentVersion); err != nil { + var versionRecords []*Version + if err := x.Find(&versionRecords); err != nil { + return fmt.Errorf("find: %w", err) + } + if len(versionRecords) == 0 { + // If the version record does not exist we think it is a fresh installation and we can skip all migrations; + // engine init calls `SyncAllTables` which will create the fresh database. + upToDate := &Version{ID: 1, Version: maxDBVer} + if _, err := x.InsertOne(upToDate); err != nil { return fmt.Errorf("insert: %w", err) } + // continue with the migration routine, but nothing will be applied; this allows transition into the newer + // forgejo library and for it to be configured and populated. + versionRecords = []*Version{upToDate} + } else if len(versionRecords) > 1 { + return fmt.Errorf( + "corrupt migrations: Forgejo database has unexpected records in the table `version`; a single record is expected w/ ID=1, but %d records were found", + len(versionRecords)) } else { - previousVersion = currentVersion.Version + previousVersion = versionRecords[0].Version + } + currentVersion := versionRecords[0] + if currentVersion.ID != 1 { + return fmt.Errorf( + "corrupt migrations: Forgejo database has corrupted records in the table `version`; a single record with ID=1 is expected, but a record with ID=%d was found instead", currentVersion.ID) } curDBVer := currentVersion.Version @@ -486,7 +497,7 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t // Some migration tasks depend on the git command if git.DefaultContext == nil { - if err = git.InitSimple(context.Background()); err != nil { + if err := git.InitSimple(context.Background()); err != nil { return err } } @@ -500,17 +511,17 @@ Please try upgrading to a lower version first (suggested v1.6.4), then upgrade t log.Info("Migration[%d]: %s", m.idNumber, m.description) // Reset the mapper between each migration - migrations are not supposed to depend on each other x.SetMapper(names.GonicMapper{}) - if err = m.Migrate(x); err != nil { + if err := m.Migrate(x); err != nil { return fmt.Errorf("migration[%d]: %s failed: %w", m.idNumber, m.description, err) } currentVersion.Version = migrationIDNumberToDBVersion(m.idNumber) - if _, err = x.ID(1).Update(currentVersion); err != nil { + if _, err := x.ID(1).Update(currentVersion); err != nil { return err } } // Execute Forgejo specific migrations. - return forgejo_migrations.Migrate(x) + return forgejo_migrations_legacy.Migrate(x) } // WrapperMigrate is a wrapper for Migrate to be called in diagnostics diff --git a/models/gitea_migrations/migrations_test.go b/models/gitea_migrations/migrations_test.go new file mode 100644 index 0000000000..600b7e3b7a --- /dev/null +++ b/models/gitea_migrations/migrations_test.go @@ -0,0 +1,64 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitea_migrations + +import ( + "testing" + + migration_tests "forgejo.org/models/gitea_migrations/test" + "forgejo.org/modules/test" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMigrations(t *testing.T) { + defer test.MockVariableValue(&preparedMigrations, []*migration{ + {idNumber: 70}, + {idNumber: 71}, + })() + assert.EqualValues(t, 72, calcDBVersion(preparedMigrations)) + assert.EqualValues(t, 72, ExpectedDBVersion()) + + assert.EqualValues(t, 71, migrationIDNumberToDBVersion(70)) + + assert.Equal(t, []*migration{{idNumber: 70}, {idNumber: 71}}, getPendingMigrations(70, preparedMigrations)) + assert.Equal(t, []*migration{{idNumber: 71}}, getPendingMigrations(71, preparedMigrations)) + assert.Equal(t, []*migration{}, getPendingMigrations(72, preparedMigrations)) +} + +func TestMigrateFreshDB(t *testing.T) { + x, deferable := migration_tests.PrepareTestEnv(t, 0, new(Version)) + defer deferable() + require.NotNil(t, x) + + err := Migrate(x) + require.NoError(t, err) + + var versionRecords []*Version + err = x.Find(&versionRecords) + require.NoError(t, err) + require.Len(t, versionRecords, 1) + v := versionRecords[0] + assert.EqualValues(t, 1, v.ID) + assert.EqualValues(t, 305, v.Version) +} + +func TestMigrateFailWithCorruption(t *testing.T) { + x, deferable := migration_tests.PrepareTestEnv(t, 0, new(Version)) + defer deferable() + require.NotNil(t, x) + + // ID != 1 + _, err := x.InsertOne(&Version{ID: 100, Version: 100}) + require.NoError(t, err) + err = Migrate(x) + require.ErrorContains(t, err, "corrupted records in the table `version`") + + // Two versions... + _, err = x.InsertOne(&Version{ID: 1, Version: 1000}) + require.NoError(t, err) + err = Migrate(x) + require.ErrorContains(t, err, "unexpected records in the table `version`") +} diff --git a/models/migrations/test/tests.go b/models/gitea_migrations/test/tests.go similarity index 97% rename from models/migrations/test/tests.go rename to models/gitea_migrations/test/tests.go index 6be3b3c2fc..6a4865194d 100644 --- a/models/migrations/test/tests.go +++ b/models/gitea_migrations/test/tests.go @@ -31,7 +31,7 @@ import ( // PrepareTestEnv prepares the test environment and reset the database. The skip parameter should usually be 0. // Provide models to be sync'd with the database - in particular any models you expect fixtures to be loaded from. // -// fixtures in `models/migrations/fixtures/` will be loaded automatically +// fixtures in `models/gitea_migrations/fixtures/` will be loaded automatically func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, func()) { t.Helper() ourSkip := 2 @@ -89,14 +89,14 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu } } - fixturesDir := filepath.Join(filepath.Dir(setting.AppPath), "models", "migrations", "fixtures", t.Name()) + fixturesDir := filepath.Join(filepath.Dir(setting.AppPath), "models", "gitea_migrations", "fixtures", t.Name()) if _, err := os.Stat(fixturesDir); err == nil { t.Logf("initializing fixtures from: %s", fixturesDir) if err := unittest.InitFixtures( unittest.FixturesOptions{ - Dir: fixturesDir, - SkipCleanRegistedModels: true, + Dir: fixturesDir, + OnlyAffectModels: syncModels, }, x); err != nil { t.Errorf("error whilst initializing fixtures from %s: %v", fixturesDir, err) return x, deferFn diff --git a/models/migrations/v1_10/v100.go b/models/gitea_migrations/v1_10/v100.go similarity index 100% rename from models/migrations/v1_10/v100.go rename to models/gitea_migrations/v1_10/v100.go diff --git a/models/migrations/v1_10/v101.go b/models/gitea_migrations/v1_10/v101.go similarity index 100% rename from models/migrations/v1_10/v101.go rename to models/gitea_migrations/v1_10/v101.go diff --git a/models/migrations/v1_10/v88.go b/models/gitea_migrations/v1_10/v88.go similarity index 100% rename from models/migrations/v1_10/v88.go rename to models/gitea_migrations/v1_10/v88.go diff --git a/models/migrations/v1_10/v89.go b/models/gitea_migrations/v1_10/v89.go similarity index 100% rename from models/migrations/v1_10/v89.go rename to models/gitea_migrations/v1_10/v89.go diff --git a/models/migrations/v1_10/v90.go b/models/gitea_migrations/v1_10/v90.go similarity index 100% rename from models/migrations/v1_10/v90.go rename to models/gitea_migrations/v1_10/v90.go diff --git a/models/migrations/v1_10/v91.go b/models/gitea_migrations/v1_10/v91.go similarity index 100% rename from models/migrations/v1_10/v91.go rename to models/gitea_migrations/v1_10/v91.go diff --git a/models/migrations/v1_10/v92.go b/models/gitea_migrations/v1_10/v92.go similarity index 100% rename from models/migrations/v1_10/v92.go rename to models/gitea_migrations/v1_10/v92.go diff --git a/models/migrations/v1_10/v93.go b/models/gitea_migrations/v1_10/v93.go similarity index 100% rename from models/migrations/v1_10/v93.go rename to models/gitea_migrations/v1_10/v93.go diff --git a/models/migrations/v1_10/v94.go b/models/gitea_migrations/v1_10/v94.go similarity index 100% rename from models/migrations/v1_10/v94.go rename to models/gitea_migrations/v1_10/v94.go diff --git a/models/migrations/v1_10/v95.go b/models/gitea_migrations/v1_10/v95.go similarity index 100% rename from models/migrations/v1_10/v95.go rename to models/gitea_migrations/v1_10/v95.go diff --git a/models/migrations/v1_10/v96.go b/models/gitea_migrations/v1_10/v96.go similarity index 100% rename from models/migrations/v1_10/v96.go rename to models/gitea_migrations/v1_10/v96.go diff --git a/models/migrations/v1_10/v97.go b/models/gitea_migrations/v1_10/v97.go similarity index 100% rename from models/migrations/v1_10/v97.go rename to models/gitea_migrations/v1_10/v97.go diff --git a/models/migrations/v1_10/v98.go b/models/gitea_migrations/v1_10/v98.go similarity index 100% rename from models/migrations/v1_10/v98.go rename to models/gitea_migrations/v1_10/v98.go diff --git a/models/migrations/v1_10/v99.go b/models/gitea_migrations/v1_10/v99.go similarity index 100% rename from models/migrations/v1_10/v99.go rename to models/gitea_migrations/v1_10/v99.go diff --git a/models/migrations/v1_11/v102.go b/models/gitea_migrations/v1_11/v102.go similarity index 90% rename from models/migrations/v1_11/v102.go rename to models/gitea_migrations/v1_11/v102.go index 15f0c83c36..53288f18ec 100644 --- a/models/migrations/v1_11/v102.go +++ b/models/gitea_migrations/v1_11/v102.go @@ -4,7 +4,7 @@ package v1_11 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" ) diff --git a/models/migrations/v1_11/v103.go b/models/gitea_migrations/v1_11/v103.go similarity index 100% rename from models/migrations/v1_11/v103.go rename to models/gitea_migrations/v1_11/v103.go diff --git a/models/migrations/v1_11/v104.go b/models/gitea_migrations/v1_11/v104.go similarity index 93% rename from models/migrations/v1_11/v104.go rename to models/gitea_migrations/v1_11/v104.go index 7461f0cda3..47cf320359 100644 --- a/models/migrations/v1_11/v104.go +++ b/models/gitea_migrations/v1_11/v104.go @@ -4,7 +4,7 @@ package v1_11 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" ) diff --git a/models/migrations/v1_11/v105.go b/models/gitea_migrations/v1_11/v105.go similarity index 100% rename from models/migrations/v1_11/v105.go rename to models/gitea_migrations/v1_11/v105.go diff --git a/models/migrations/v1_11/v106.go b/models/gitea_migrations/v1_11/v106.go similarity index 100% rename from models/migrations/v1_11/v106.go rename to models/gitea_migrations/v1_11/v106.go diff --git a/models/migrations/v1_11/v107.go b/models/gitea_migrations/v1_11/v107.go similarity index 100% rename from models/migrations/v1_11/v107.go rename to models/gitea_migrations/v1_11/v107.go diff --git a/models/migrations/v1_11/v108.go b/models/gitea_migrations/v1_11/v108.go similarity index 100% rename from models/migrations/v1_11/v108.go rename to models/gitea_migrations/v1_11/v108.go diff --git a/models/migrations/v1_11/v109.go b/models/gitea_migrations/v1_11/v109.go similarity index 100% rename from models/migrations/v1_11/v109.go rename to models/gitea_migrations/v1_11/v109.go diff --git a/models/migrations/v1_11/v110.go b/models/gitea_migrations/v1_11/v110.go similarity index 100% rename from models/migrations/v1_11/v110.go rename to models/gitea_migrations/v1_11/v110.go diff --git a/models/migrations/v1_11/v111.go b/models/gitea_migrations/v1_11/v111.go similarity index 100% rename from models/migrations/v1_11/v111.go rename to models/gitea_migrations/v1_11/v111.go diff --git a/models/migrations/v1_11/v112.go b/models/gitea_migrations/v1_11/v112.go similarity index 100% rename from models/migrations/v1_11/v112.go rename to models/gitea_migrations/v1_11/v112.go diff --git a/models/migrations/v1_11/v113.go b/models/gitea_migrations/v1_11/v113.go similarity index 100% rename from models/migrations/v1_11/v113.go rename to models/gitea_migrations/v1_11/v113.go diff --git a/models/migrations/v1_11/v114.go b/models/gitea_migrations/v1_11/v114.go similarity index 100% rename from models/migrations/v1_11/v114.go rename to models/gitea_migrations/v1_11/v114.go diff --git a/models/migrations/v1_11/v115.go b/models/gitea_migrations/v1_11/v115.go similarity index 100% rename from models/migrations/v1_11/v115.go rename to models/gitea_migrations/v1_11/v115.go diff --git a/models/migrations/v1_11/v116.go b/models/gitea_migrations/v1_11/v116.go similarity index 100% rename from models/migrations/v1_11/v116.go rename to models/gitea_migrations/v1_11/v116.go diff --git a/models/migrations/v1_12/v117.go b/models/gitea_migrations/v1_12/v117.go similarity index 100% rename from models/migrations/v1_12/v117.go rename to models/gitea_migrations/v1_12/v117.go diff --git a/models/migrations/v1_12/v118.go b/models/gitea_migrations/v1_12/v118.go similarity index 100% rename from models/migrations/v1_12/v118.go rename to models/gitea_migrations/v1_12/v118.go diff --git a/models/migrations/v1_12/v119.go b/models/gitea_migrations/v1_12/v119.go similarity index 100% rename from models/migrations/v1_12/v119.go rename to models/gitea_migrations/v1_12/v119.go diff --git a/models/migrations/v1_12/v120.go b/models/gitea_migrations/v1_12/v120.go similarity index 100% rename from models/migrations/v1_12/v120.go rename to models/gitea_migrations/v1_12/v120.go diff --git a/models/migrations/v1_12/v121.go b/models/gitea_migrations/v1_12/v121.go similarity index 100% rename from models/migrations/v1_12/v121.go rename to models/gitea_migrations/v1_12/v121.go diff --git a/models/migrations/v1_12/v122.go b/models/gitea_migrations/v1_12/v122.go similarity index 100% rename from models/migrations/v1_12/v122.go rename to models/gitea_migrations/v1_12/v122.go diff --git a/models/migrations/v1_12/v123.go b/models/gitea_migrations/v1_12/v123.go similarity index 100% rename from models/migrations/v1_12/v123.go rename to models/gitea_migrations/v1_12/v123.go diff --git a/models/migrations/v1_12/v124.go b/models/gitea_migrations/v1_12/v124.go similarity index 100% rename from models/migrations/v1_12/v124.go rename to models/gitea_migrations/v1_12/v124.go diff --git a/models/migrations/v1_12/v125.go b/models/gitea_migrations/v1_12/v125.go similarity index 100% rename from models/migrations/v1_12/v125.go rename to models/gitea_migrations/v1_12/v125.go diff --git a/models/migrations/v1_12/v126.go b/models/gitea_migrations/v1_12/v126.go similarity index 100% rename from models/migrations/v1_12/v126.go rename to models/gitea_migrations/v1_12/v126.go diff --git a/models/migrations/v1_12/v127.go b/models/gitea_migrations/v1_12/v127.go similarity index 100% rename from models/migrations/v1_12/v127.go rename to models/gitea_migrations/v1_12/v127.go diff --git a/models/migrations/v1_12/v128.go b/models/gitea_migrations/v1_12/v128.go similarity index 100% rename from models/migrations/v1_12/v128.go rename to models/gitea_migrations/v1_12/v128.go diff --git a/models/migrations/v1_12/v129.go b/models/gitea_migrations/v1_12/v129.go similarity index 100% rename from models/migrations/v1_12/v129.go rename to models/gitea_migrations/v1_12/v129.go diff --git a/models/migrations/v1_12/v130.go b/models/gitea_migrations/v1_12/v130.go similarity index 100% rename from models/migrations/v1_12/v130.go rename to models/gitea_migrations/v1_12/v130.go diff --git a/models/migrations/v1_12/v131.go b/models/gitea_migrations/v1_12/v131.go similarity index 100% rename from models/migrations/v1_12/v131.go rename to models/gitea_migrations/v1_12/v131.go diff --git a/models/migrations/v1_12/v132.go b/models/gitea_migrations/v1_12/v132.go similarity index 100% rename from models/migrations/v1_12/v132.go rename to models/gitea_migrations/v1_12/v132.go diff --git a/models/migrations/v1_12/v133.go b/models/gitea_migrations/v1_12/v133.go similarity index 100% rename from models/migrations/v1_12/v133.go rename to models/gitea_migrations/v1_12/v133.go diff --git a/models/migrations/v1_12/v134.go b/models/gitea_migrations/v1_12/v134.go similarity index 100% rename from models/migrations/v1_12/v134.go rename to models/gitea_migrations/v1_12/v134.go diff --git a/models/migrations/v1_12/v135.go b/models/gitea_migrations/v1_12/v135.go similarity index 100% rename from models/migrations/v1_12/v135.go rename to models/gitea_migrations/v1_12/v135.go diff --git a/models/migrations/v1_12/v136.go b/models/gitea_migrations/v1_12/v136.go similarity index 100% rename from models/migrations/v1_12/v136.go rename to models/gitea_migrations/v1_12/v136.go diff --git a/models/migrations/v1_12/v137.go b/models/gitea_migrations/v1_12/v137.go similarity index 100% rename from models/migrations/v1_12/v137.go rename to models/gitea_migrations/v1_12/v137.go diff --git a/models/migrations/v1_12/v138.go b/models/gitea_migrations/v1_12/v138.go similarity index 100% rename from models/migrations/v1_12/v138.go rename to models/gitea_migrations/v1_12/v138.go diff --git a/models/migrations/v1_12/v139.go b/models/gitea_migrations/v1_12/v139.go similarity index 100% rename from models/migrations/v1_12/v139.go rename to models/gitea_migrations/v1_12/v139.go diff --git a/models/migrations/v1_13/v140.go b/models/gitea_migrations/v1_13/v140.go similarity index 96% rename from models/migrations/v1_13/v140.go rename to models/gitea_migrations/v1_13/v140.go index 5bb612c098..c9213a3f54 100644 --- a/models/migrations/v1_13/v140.go +++ b/models/gitea_migrations/v1_13/v140.go @@ -6,7 +6,7 @@ package v1_13 import ( "fmt" - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "forgejo.org/modules/setting" "xorm.io/xorm" diff --git a/models/migrations/v1_13/v141.go b/models/gitea_migrations/v1_13/v141.go similarity index 100% rename from models/migrations/v1_13/v141.go rename to models/gitea_migrations/v1_13/v141.go diff --git a/models/migrations/v1_13/v142.go b/models/gitea_migrations/v1_13/v142.go similarity index 100% rename from models/migrations/v1_13/v142.go rename to models/gitea_migrations/v1_13/v142.go diff --git a/models/migrations/v1_13/v143.go b/models/gitea_migrations/v1_13/v143.go similarity index 100% rename from models/migrations/v1_13/v143.go rename to models/gitea_migrations/v1_13/v143.go diff --git a/models/migrations/v1_13/v144.go b/models/gitea_migrations/v1_13/v144.go similarity index 100% rename from models/migrations/v1_13/v144.go rename to models/gitea_migrations/v1_13/v144.go diff --git a/models/migrations/v1_13/v145.go b/models/gitea_migrations/v1_13/v145.go similarity index 100% rename from models/migrations/v1_13/v145.go rename to models/gitea_migrations/v1_13/v145.go diff --git a/models/migrations/v1_13/v146.go b/models/gitea_migrations/v1_13/v146.go similarity index 100% rename from models/migrations/v1_13/v146.go rename to models/gitea_migrations/v1_13/v146.go diff --git a/models/migrations/v1_13/v147.go b/models/gitea_migrations/v1_13/v147.go similarity index 100% rename from models/migrations/v1_13/v147.go rename to models/gitea_migrations/v1_13/v147.go diff --git a/models/migrations/v1_13/v148.go b/models/gitea_migrations/v1_13/v148.go similarity index 100% rename from models/migrations/v1_13/v148.go rename to models/gitea_migrations/v1_13/v148.go diff --git a/models/migrations/v1_13/v149.go b/models/gitea_migrations/v1_13/v149.go similarity index 100% rename from models/migrations/v1_13/v149.go rename to models/gitea_migrations/v1_13/v149.go diff --git a/models/migrations/v1_13/v150.go b/models/gitea_migrations/v1_13/v150.go similarity index 81% rename from models/migrations/v1_13/v150.go rename to models/gitea_migrations/v1_13/v150.go index 471a531024..0f92712327 100644 --- a/models/migrations/v1_13/v150.go +++ b/models/gitea_migrations/v1_13/v150.go @@ -4,7 +4,7 @@ package v1_13 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "forgejo.org/modules/timeutil" "xorm.io/xorm" @@ -32,8 +32,8 @@ func AddPrimaryKeyToRepoTopic(x *xorm.Engine) error { return err } - base.RecreateTable(sess, &Topic{}) - base.RecreateTable(sess, &RepoTopic{}) + base.LegacyRecreateTable(sess, &Topic{}) //nolint:staticcheck + base.LegacyRecreateTable(sess, &RepoTopic{}) //nolint:staticcheck return sess.Commit() } diff --git a/models/migrations/v1_13/v151.go b/models/gitea_migrations/v1_13/v151.go similarity index 100% rename from models/migrations/v1_13/v151.go rename to models/gitea_migrations/v1_13/v151.go diff --git a/models/migrations/v1_13/v152.go b/models/gitea_migrations/v1_13/v152.go similarity index 100% rename from models/migrations/v1_13/v152.go rename to models/gitea_migrations/v1_13/v152.go diff --git a/models/migrations/v1_13/v153.go b/models/gitea_migrations/v1_13/v153.go similarity index 100% rename from models/migrations/v1_13/v153.go rename to models/gitea_migrations/v1_13/v153.go diff --git a/models/migrations/v1_13/v154.go b/models/gitea_migrations/v1_13/v154.go similarity index 100% rename from models/migrations/v1_13/v154.go rename to models/gitea_migrations/v1_13/v154.go diff --git a/models/migrations/v1_14/main_test.go b/models/gitea_migrations/v1_14/main_test.go similarity index 76% rename from models/migrations/v1_14/main_test.go rename to models/gitea_migrations/v1_14/main_test.go index 57cf995be1..129e66f032 100644 --- a/models/migrations/v1_14/main_test.go +++ b/models/gitea_migrations/v1_14/main_test.go @@ -6,7 +6,7 @@ package v1_14 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" ) func TestMain(m *testing.M) { diff --git a/models/migrations/v1_14/v155.go b/models/gitea_migrations/v1_14/v155.go similarity index 100% rename from models/migrations/v1_14/v155.go rename to models/gitea_migrations/v1_14/v155.go diff --git a/models/migrations/v1_14/v156.go b/models/gitea_migrations/v1_14/v156.go similarity index 100% rename from models/migrations/v1_14/v156.go rename to models/gitea_migrations/v1_14/v156.go diff --git a/models/migrations/v1_14/v157.go b/models/gitea_migrations/v1_14/v157.go similarity index 100% rename from models/migrations/v1_14/v157.go rename to models/gitea_migrations/v1_14/v157.go diff --git a/models/migrations/v1_14/v158.go b/models/gitea_migrations/v1_14/v158.go similarity index 100% rename from models/migrations/v1_14/v158.go rename to models/gitea_migrations/v1_14/v158.go diff --git a/models/migrations/v1_14/v159.go b/models/gitea_migrations/v1_14/v159.go similarity index 88% rename from models/migrations/v1_14/v159.go rename to models/gitea_migrations/v1_14/v159.go index 4e921ea1c6..d8193d25bc 100644 --- a/models/migrations/v1_14/v159.go +++ b/models/gitea_migrations/v1_14/v159.go @@ -4,7 +4,7 @@ package v1_14 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "forgejo.org/modules/timeutil" "xorm.io/xorm" @@ -30,7 +30,7 @@ func UpdateReactionConstraint(x *xorm.Engine) error { return err } - if err := base.RecreateTable(sess, &Reaction{}); err != nil { + if err := base.LegacyRecreateTable(sess, &Reaction{}); err != nil { //nolint:staticcheck return err } diff --git a/models/migrations/v1_14/v160.go b/models/gitea_migrations/v1_14/v160.go similarity index 100% rename from models/migrations/v1_14/v160.go rename to models/gitea_migrations/v1_14/v160.go diff --git a/models/migrations/v1_14/v161.go b/models/gitea_migrations/v1_14/v161.go similarity index 96% rename from models/migrations/v1_14/v161.go rename to models/gitea_migrations/v1_14/v161.go index 9c850ad0c2..b689a75a05 100644 --- a/models/migrations/v1_14/v161.go +++ b/models/gitea_migrations/v1_14/v161.go @@ -6,7 +6,7 @@ package v1_14 import ( "context" - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" ) diff --git a/models/migrations/v1_14/v162.go b/models/gitea_migrations/v1_14/v162.go similarity index 96% rename from models/migrations/v1_14/v162.go rename to models/gitea_migrations/v1_14/v162.go index ead63f16f4..69016f3f72 100644 --- a/models/migrations/v1_14/v162.go +++ b/models/gitea_migrations/v1_14/v162.go @@ -4,7 +4,7 @@ package v1_14 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" ) diff --git a/models/migrations/v1_14/v163.go b/models/gitea_migrations/v1_14/v163.go similarity index 82% rename from models/migrations/v1_14/v163.go rename to models/gitea_migrations/v1_14/v163.go index 06ac36cbc7..4c838df865 100644 --- a/models/migrations/v1_14/v163.go +++ b/models/gitea_migrations/v1_14/v163.go @@ -4,7 +4,7 @@ package v1_14 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" ) @@ -27,7 +27,7 @@ func ConvertTopicNameFrom25To50(x *xorm.Engine) error { if err := sess.Begin(); err != nil { return err } - if err := base.RecreateTable(sess, new(Topic)); err != nil { + if err := base.LegacyRecreateTable(sess, new(Topic)); err != nil { //nolint:staticcheck return err } diff --git a/models/migrations/v1_14/v164.go b/models/gitea_migrations/v1_14/v164.go similarity index 100% rename from models/migrations/v1_14/v164.go rename to models/gitea_migrations/v1_14/v164.go diff --git a/models/migrations/v1_14/v165.go b/models/gitea_migrations/v1_14/v165.go similarity index 96% rename from models/migrations/v1_14/v165.go rename to models/gitea_migrations/v1_14/v165.go index 90fd2b1e46..11b46e5742 100644 --- a/models/migrations/v1_14/v165.go +++ b/models/gitea_migrations/v1_14/v165.go @@ -4,7 +4,7 @@ package v1_14 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" "xorm.io/xorm/schemas" diff --git a/models/migrations/v1_14/v166.go b/models/gitea_migrations/v1_14/v166.go similarity index 100% rename from models/migrations/v1_14/v166.go rename to models/gitea_migrations/v1_14/v166.go diff --git a/models/migrations/v1_14/v167.go b/models/gitea_migrations/v1_14/v167.go similarity index 100% rename from models/migrations/v1_14/v167.go rename to models/gitea_migrations/v1_14/v167.go diff --git a/models/migrations/v1_14/v168.go b/models/gitea_migrations/v1_14/v168.go similarity index 100% rename from models/migrations/v1_14/v168.go rename to models/gitea_migrations/v1_14/v168.go diff --git a/models/migrations/v1_14/v169.go b/models/gitea_migrations/v1_14/v169.go similarity index 100% rename from models/migrations/v1_14/v169.go rename to models/gitea_migrations/v1_14/v169.go diff --git a/models/migrations/v1_14/v170.go b/models/gitea_migrations/v1_14/v170.go similarity index 100% rename from models/migrations/v1_14/v170.go rename to models/gitea_migrations/v1_14/v170.go diff --git a/models/migrations/v1_14/v171.go b/models/gitea_migrations/v1_14/v171.go similarity index 100% rename from models/migrations/v1_14/v171.go rename to models/gitea_migrations/v1_14/v171.go diff --git a/models/migrations/v1_14/v172.go b/models/gitea_migrations/v1_14/v172.go similarity index 100% rename from models/migrations/v1_14/v172.go rename to models/gitea_migrations/v1_14/v172.go diff --git a/models/migrations/v1_14/v173.go b/models/gitea_migrations/v1_14/v173.go similarity index 100% rename from models/migrations/v1_14/v173.go rename to models/gitea_migrations/v1_14/v173.go diff --git a/models/migrations/v1_14/v174.go b/models/gitea_migrations/v1_14/v174.go similarity index 100% rename from models/migrations/v1_14/v174.go rename to models/gitea_migrations/v1_14/v174.go diff --git a/models/migrations/v1_14/v175.go b/models/gitea_migrations/v1_14/v175.go similarity index 100% rename from models/migrations/v1_14/v175.go rename to models/gitea_migrations/v1_14/v175.go diff --git a/models/migrations/v1_14/v176.go b/models/gitea_migrations/v1_14/v176.go similarity index 100% rename from models/migrations/v1_14/v176.go rename to models/gitea_migrations/v1_14/v176.go diff --git a/models/migrations/v1_14/v176_test.go b/models/gitea_migrations/v1_14/v176_test.go similarity index 98% rename from models/migrations/v1_14/v176_test.go rename to models/gitea_migrations/v1_14/v176_test.go index d56b3e0470..60b555b520 100644 --- a/models/migrations/v1_14/v176_test.go +++ b/models/gitea_migrations/v1_14/v176_test.go @@ -6,7 +6,7 @@ package v1_14 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/assert" ) diff --git a/models/migrations/v1_14/v177.go b/models/gitea_migrations/v1_14/v177.go similarity index 100% rename from models/migrations/v1_14/v177.go rename to models/gitea_migrations/v1_14/v177.go diff --git a/models/migrations/v1_14/v177_test.go b/models/gitea_migrations/v1_14/v177_test.go similarity index 97% rename from models/migrations/v1_14/v177_test.go rename to models/gitea_migrations/v1_14/v177_test.go index 0e0a67fd33..ee69dbad53 100644 --- a/models/migrations/v1_14/v177_test.go +++ b/models/gitea_migrations/v1_14/v177_test.go @@ -6,7 +6,7 @@ package v1_14 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "forgejo.org/modules/timeutil" "github.com/stretchr/testify/assert" diff --git a/models/migrations/v1_15/main_test.go b/models/gitea_migrations/v1_15/main_test.go similarity index 76% rename from models/migrations/v1_15/main_test.go rename to models/gitea_migrations/v1_15/main_test.go index 4cf6d6f695..4811142c43 100644 --- a/models/migrations/v1_15/main_test.go +++ b/models/gitea_migrations/v1_15/main_test.go @@ -6,7 +6,7 @@ package v1_15 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" ) func TestMain(m *testing.M) { diff --git a/models/migrations/v1_15/v178.go b/models/gitea_migrations/v1_15/v178.go similarity index 100% rename from models/migrations/v1_15/v178.go rename to models/gitea_migrations/v1_15/v178.go diff --git a/models/migrations/v1_15/v179.go b/models/gitea_migrations/v1_15/v179.go similarity index 93% rename from models/migrations/v1_15/v179.go rename to models/gitea_migrations/v1_15/v179.go index ce514cc4a9..eb87702475 100644 --- a/models/migrations/v1_15/v179.go +++ b/models/gitea_migrations/v1_15/v179.go @@ -4,7 +4,7 @@ package v1_15 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" "xorm.io/xorm/schemas" diff --git a/models/migrations/v1_15/v180.go b/models/gitea_migrations/v1_15/v180.go similarity index 100% rename from models/migrations/v1_15/v180.go rename to models/gitea_migrations/v1_15/v180.go diff --git a/models/migrations/v1_15/v181.go b/models/gitea_migrations/v1_15/v181.go similarity index 100% rename from models/migrations/v1_15/v181.go rename to models/gitea_migrations/v1_15/v181.go diff --git a/models/migrations/v1_15/v181_test.go b/models/gitea_migrations/v1_15/v181_test.go similarity index 95% rename from models/migrations/v1_15/v181_test.go rename to models/gitea_migrations/v1_15/v181_test.go index 8196f751e5..130bb35cc7 100644 --- a/models/migrations/v1_15/v181_test.go +++ b/models/gitea_migrations/v1_15/v181_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/models/migrations/v1_15/v182.go b/models/gitea_migrations/v1_15/v182.go similarity index 100% rename from models/migrations/v1_15/v182.go rename to models/gitea_migrations/v1_15/v182.go diff --git a/models/migrations/v1_15/v182_test.go b/models/gitea_migrations/v1_15/v182_test.go similarity index 95% rename from models/migrations/v1_15/v182_test.go rename to models/gitea_migrations/v1_15/v182_test.go index 2baf90d06a..f67fc6586a 100644 --- a/models/migrations/v1_15/v182_test.go +++ b/models/gitea_migrations/v1_15/v182_test.go @@ -6,7 +6,7 @@ package v1_15 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/models/migrations/v1_15/v183.go b/models/gitea_migrations/v1_15/v183.go similarity index 100% rename from models/migrations/v1_15/v183.go rename to models/gitea_migrations/v1_15/v183.go diff --git a/models/migrations/v1_15/v184.go b/models/gitea_migrations/v1_15/v184.go similarity index 97% rename from models/migrations/v1_15/v184.go rename to models/gitea_migrations/v1_15/v184.go index fbe0dcd780..58cf99af43 100644 --- a/models/migrations/v1_15/v184.go +++ b/models/gitea_migrations/v1_15/v184.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "forgejo.org/modules/setting" "xorm.io/xorm" diff --git a/models/migrations/v1_15/v185.go b/models/gitea_migrations/v1_15/v185.go similarity index 100% rename from models/migrations/v1_15/v185.go rename to models/gitea_migrations/v1_15/v185.go diff --git a/models/migrations/v1_15/v186.go b/models/gitea_migrations/v1_15/v186.go similarity index 100% rename from models/migrations/v1_15/v186.go rename to models/gitea_migrations/v1_15/v186.go diff --git a/models/migrations/v1_15/v187.go b/models/gitea_migrations/v1_15/v187.go similarity index 96% rename from models/migrations/v1_15/v187.go rename to models/gitea_migrations/v1_15/v187.go index fabef14779..76c29755f1 100644 --- a/models/migrations/v1_15/v187.go +++ b/models/gitea_migrations/v1_15/v187.go @@ -4,7 +4,7 @@ package v1_15 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" ) diff --git a/models/migrations/v1_15/v188.go b/models/gitea_migrations/v1_15/v188.go similarity index 100% rename from models/migrations/v1_15/v188.go rename to models/gitea_migrations/v1_15/v188.go diff --git a/models/migrations/v1_16/main_test.go b/models/gitea_migrations/v1_16/main_test.go similarity index 76% rename from models/migrations/v1_16/main_test.go rename to models/gitea_migrations/v1_16/main_test.go index 8c0a043be6..5b7cdb0032 100644 --- a/models/migrations/v1_16/main_test.go +++ b/models/gitea_migrations/v1_16/main_test.go @@ -6,7 +6,7 @@ package v1_16 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" ) func TestMain(m *testing.M) { diff --git a/models/migrations/v1_16/v189.go b/models/gitea_migrations/v1_16/v189.go similarity index 98% rename from models/migrations/v1_16/v189.go rename to models/gitea_migrations/v1_16/v189.go index 19bfcb2423..a2ba3a0c64 100644 --- a/models/migrations/v1_16/v189.go +++ b/models/gitea_migrations/v1_16/v189.go @@ -7,7 +7,7 @@ import ( "encoding/binary" "fmt" - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "forgejo.org/modules/json" "xorm.io/xorm" diff --git a/models/migrations/v1_16/v189_test.go b/models/gitea_migrations/v1_16/v189_test.go similarity index 97% rename from models/migrations/v1_16/v189_test.go rename to models/gitea_migrations/v1_16/v189_test.go index 9d74462a92..7b50cac3d8 100644 --- a/models/migrations/v1_16/v189_test.go +++ b/models/gitea_migrations/v1_16/v189_test.go @@ -6,7 +6,7 @@ package v1_16 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "forgejo.org/modules/json" "github.com/stretchr/testify/assert" diff --git a/models/migrations/v1_16/v190.go b/models/gitea_migrations/v1_16/v190.go similarity index 100% rename from models/migrations/v1_16/v190.go rename to models/gitea_migrations/v1_16/v190.go diff --git a/models/migrations/v1_16/v191.go b/models/gitea_migrations/v1_16/v191.go similarity index 100% rename from models/migrations/v1_16/v191.go rename to models/gitea_migrations/v1_16/v191.go diff --git a/models/migrations/v1_16/v192.go b/models/gitea_migrations/v1_16/v192.go similarity index 88% rename from models/migrations/v1_16/v192.go rename to models/gitea_migrations/v1_16/v192.go index 31e8c36346..0e2fd522f9 100644 --- a/models/migrations/v1_16/v192.go +++ b/models/gitea_migrations/v1_16/v192.go @@ -4,7 +4,7 @@ package v1_16 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" ) diff --git a/models/migrations/v1_16/v193.go b/models/gitea_migrations/v1_16/v193.go similarity index 100% rename from models/migrations/v1_16/v193.go rename to models/gitea_migrations/v1_16/v193.go diff --git a/models/migrations/v1_16/v193_test.go b/models/gitea_migrations/v1_16/v193_test.go similarity index 97% rename from models/migrations/v1_16/v193_test.go rename to models/gitea_migrations/v1_16/v193_test.go index bf8d8a7dc6..73159118ab 100644 --- a/models/migrations/v1_16/v193_test.go +++ b/models/gitea_migrations/v1_16/v193_test.go @@ -6,7 +6,7 @@ package v1_16 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/models/migrations/v1_16/v194.go b/models/gitea_migrations/v1_16/v194.go similarity index 100% rename from models/migrations/v1_16/v194.go rename to models/gitea_migrations/v1_16/v194.go diff --git a/models/migrations/v1_16/v195.go b/models/gitea_migrations/v1_16/v195.go similarity index 100% rename from models/migrations/v1_16/v195.go rename to models/gitea_migrations/v1_16/v195.go diff --git a/models/migrations/v1_16/v195_test.go b/models/gitea_migrations/v1_16/v195_test.go similarity index 96% rename from models/migrations/v1_16/v195_test.go rename to models/gitea_migrations/v1_16/v195_test.go index 1fc7b51f3c..d3e4ffc56e 100644 --- a/models/migrations/v1_16/v195_test.go +++ b/models/gitea_migrations/v1_16/v195_test.go @@ -6,7 +6,7 @@ package v1_16 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/models/migrations/v1_16/v196.go b/models/gitea_migrations/v1_16/v196.go similarity index 100% rename from models/migrations/v1_16/v196.go rename to models/gitea_migrations/v1_16/v196.go diff --git a/models/migrations/v1_16/v197.go b/models/gitea_migrations/v1_16/v197.go similarity index 100% rename from models/migrations/v1_16/v197.go rename to models/gitea_migrations/v1_16/v197.go diff --git a/models/migrations/v1_16/v198.go b/models/gitea_migrations/v1_16/v198.go similarity index 100% rename from models/migrations/v1_16/v198.go rename to models/gitea_migrations/v1_16/v198.go diff --git a/models/migrations/v1_16/v199.go b/models/gitea_migrations/v1_16/v199.go similarity index 100% rename from models/migrations/v1_16/v199.go rename to models/gitea_migrations/v1_16/v199.go diff --git a/models/migrations/v1_16/v200.go b/models/gitea_migrations/v1_16/v200.go similarity index 100% rename from models/migrations/v1_16/v200.go rename to models/gitea_migrations/v1_16/v200.go diff --git a/models/migrations/v1_16/v201.go b/models/gitea_migrations/v1_16/v201.go similarity index 100% rename from models/migrations/v1_16/v201.go rename to models/gitea_migrations/v1_16/v201.go diff --git a/models/migrations/v1_16/v202.go b/models/gitea_migrations/v1_16/v202.go similarity index 100% rename from models/migrations/v1_16/v202.go rename to models/gitea_migrations/v1_16/v202.go diff --git a/models/migrations/v1_16/v203.go b/models/gitea_migrations/v1_16/v203.go similarity index 100% rename from models/migrations/v1_16/v203.go rename to models/gitea_migrations/v1_16/v203.go diff --git a/models/migrations/v1_16/v204.go b/models/gitea_migrations/v1_16/v204.go similarity index 100% rename from models/migrations/v1_16/v204.go rename to models/gitea_migrations/v1_16/v204.go diff --git a/models/migrations/v1_16/v205.go b/models/gitea_migrations/v1_16/v205.go similarity index 94% rename from models/migrations/v1_16/v205.go rename to models/gitea_migrations/v1_16/v205.go index cb452dfd7f..a89edfdef4 100644 --- a/models/migrations/v1_16/v205.go +++ b/models/gitea_migrations/v1_16/v205.go @@ -4,7 +4,7 @@ package v1_16 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" "xorm.io/xorm/schemas" diff --git a/models/migrations/v1_16/v206.go b/models/gitea_migrations/v1_16/v206.go similarity index 100% rename from models/migrations/v1_16/v206.go rename to models/gitea_migrations/v1_16/v206.go diff --git a/models/migrations/v1_16/v207.go b/models/gitea_migrations/v1_16/v207.go similarity index 100% rename from models/migrations/v1_16/v207.go rename to models/gitea_migrations/v1_16/v207.go diff --git a/models/migrations/v1_16/v208.go b/models/gitea_migrations/v1_16/v208.go similarity index 100% rename from models/migrations/v1_16/v208.go rename to models/gitea_migrations/v1_16/v208.go diff --git a/models/migrations/v1_16/v209.go b/models/gitea_migrations/v1_16/v209.go similarity index 100% rename from models/migrations/v1_16/v209.go rename to models/gitea_migrations/v1_16/v209.go diff --git a/models/migrations/v1_16/v210.go b/models/gitea_migrations/v1_16/v210.go similarity index 100% rename from models/migrations/v1_16/v210.go rename to models/gitea_migrations/v1_16/v210.go diff --git a/models/migrations/v1_16/v210_test.go b/models/gitea_migrations/v1_16/v210_test.go similarity index 98% rename from models/migrations/v1_16/v210_test.go rename to models/gitea_migrations/v1_16/v210_test.go index 8454920aa0..7972c191f0 100644 --- a/models/migrations/v1_16/v210_test.go +++ b/models/gitea_migrations/v1_16/v210_test.go @@ -7,7 +7,7 @@ import ( "encoding/hex" "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "forgejo.org/modules/timeutil" "github.com/stretchr/testify/assert" diff --git a/models/migrations/v1_17/main_test.go b/models/gitea_migrations/v1_17/main_test.go similarity index 76% rename from models/migrations/v1_17/main_test.go rename to models/gitea_migrations/v1_17/main_test.go index 166860b3b1..e54e2a9029 100644 --- a/models/migrations/v1_17/main_test.go +++ b/models/gitea_migrations/v1_17/main_test.go @@ -6,7 +6,7 @@ package v1_17 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" ) func TestMain(m *testing.M) { diff --git a/models/migrations/v1_17/v211.go b/models/gitea_migrations/v1_17/v211.go similarity index 100% rename from models/migrations/v1_17/v211.go rename to models/gitea_migrations/v1_17/v211.go diff --git a/models/migrations/v1_17/v212.go b/models/gitea_migrations/v1_17/v212.go similarity index 100% rename from models/migrations/v1_17/v212.go rename to models/gitea_migrations/v1_17/v212.go diff --git a/models/migrations/v1_17/v213.go b/models/gitea_migrations/v1_17/v213.go similarity index 100% rename from models/migrations/v1_17/v213.go rename to models/gitea_migrations/v1_17/v213.go diff --git a/models/migrations/v1_17/v214.go b/models/gitea_migrations/v1_17/v214.go similarity index 100% rename from models/migrations/v1_17/v214.go rename to models/gitea_migrations/v1_17/v214.go diff --git a/models/migrations/v1_17/v215.go b/models/gitea_migrations/v1_17/v215.go similarity index 100% rename from models/migrations/v1_17/v215.go rename to models/gitea_migrations/v1_17/v215.go diff --git a/models/migrations/v1_17/v216.go b/models/gitea_migrations/v1_17/v216.go similarity index 100% rename from models/migrations/v1_17/v216.go rename to models/gitea_migrations/v1_17/v216.go diff --git a/models/migrations/v1_17/v217.go b/models/gitea_migrations/v1_17/v217.go similarity index 100% rename from models/migrations/v1_17/v217.go rename to models/gitea_migrations/v1_17/v217.go diff --git a/models/migrations/v1_17/v218.go b/models/gitea_migrations/v1_17/v218.go similarity index 100% rename from models/migrations/v1_17/v218.go rename to models/gitea_migrations/v1_17/v218.go diff --git a/models/migrations/v1_17/v219.go b/models/gitea_migrations/v1_17/v219.go similarity index 100% rename from models/migrations/v1_17/v219.go rename to models/gitea_migrations/v1_17/v219.go diff --git a/models/migrations/v1_17/v220.go b/models/gitea_migrations/v1_17/v220.go similarity index 100% rename from models/migrations/v1_17/v220.go rename to models/gitea_migrations/v1_17/v220.go diff --git a/models/migrations/v1_17/v221.go b/models/gitea_migrations/v1_17/v221.go similarity index 100% rename from models/migrations/v1_17/v221.go rename to models/gitea_migrations/v1_17/v221.go diff --git a/models/migrations/v1_17/v221_test.go b/models/gitea_migrations/v1_17/v221_test.go similarity index 96% rename from models/migrations/v1_17/v221_test.go rename to models/gitea_migrations/v1_17/v221_test.go index a9c47136b2..592fd15f5e 100644 --- a/models/migrations/v1_17/v221_test.go +++ b/models/gitea_migrations/v1_17/v221_test.go @@ -7,7 +7,7 @@ import ( "encoding/base32" "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/models/migrations/v1_17/v222.go b/models/gitea_migrations/v1_17/v222.go similarity index 97% rename from models/migrations/v1_17/v222.go rename to models/gitea_migrations/v1_17/v222.go index 873769881e..77095f4e7e 100644 --- a/models/migrations/v1_17/v222.go +++ b/models/gitea_migrations/v1_17/v222.go @@ -8,7 +8,7 @@ import ( "errors" "fmt" - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "forgejo.org/modules/timeutil" "xorm.io/xorm" diff --git a/models/migrations/v1_17/v223.go b/models/gitea_migrations/v1_17/v223.go similarity index 98% rename from models/migrations/v1_17/v223.go rename to models/gitea_migrations/v1_17/v223.go index 4f5d34d841..4cc91c4f46 100644 --- a/models/migrations/v1_17/v223.go +++ b/models/gitea_migrations/v1_17/v223.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "forgejo.org/modules/setting" "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_18/main_test.go b/models/gitea_migrations/v1_18/main_test.go similarity index 76% rename from models/migrations/v1_18/main_test.go rename to models/gitea_migrations/v1_18/main_test.go index 0c20934cea..73e634ca35 100644 --- a/models/migrations/v1_18/main_test.go +++ b/models/gitea_migrations/v1_18/main_test.go @@ -6,7 +6,7 @@ package v1_18 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" ) func TestMain(m *testing.M) { diff --git a/models/migrations/v1_18/v224.go b/models/gitea_migrations/v1_18/v224.go similarity index 100% rename from models/migrations/v1_18/v224.go rename to models/gitea_migrations/v1_18/v224.go diff --git a/models/migrations/v1_18/v225.go b/models/gitea_migrations/v1_18/v225.go similarity index 100% rename from models/migrations/v1_18/v225.go rename to models/gitea_migrations/v1_18/v225.go diff --git a/models/migrations/v1_18/v226.go b/models/gitea_migrations/v1_18/v226.go similarity index 100% rename from models/migrations/v1_18/v226.go rename to models/gitea_migrations/v1_18/v226.go diff --git a/models/migrations/v1_18/v227.go b/models/gitea_migrations/v1_18/v227.go similarity index 100% rename from models/migrations/v1_18/v227.go rename to models/gitea_migrations/v1_18/v227.go diff --git a/models/migrations/v1_18/v228.go b/models/gitea_migrations/v1_18/v228.go similarity index 100% rename from models/migrations/v1_18/v228.go rename to models/gitea_migrations/v1_18/v228.go diff --git a/models/migrations/v1_18/v229.go b/models/gitea_migrations/v1_18/v229.go similarity index 100% rename from models/migrations/v1_18/v229.go rename to models/gitea_migrations/v1_18/v229.go diff --git a/models/migrations/v1_18/v229_test.go b/models/gitea_migrations/v1_18/v229_test.go similarity index 94% rename from models/migrations/v1_18/v229_test.go rename to models/gitea_migrations/v1_18/v229_test.go index 903a60c851..deea44ab86 100644 --- a/models/migrations/v1_18/v229_test.go +++ b/models/gitea_migrations/v1_18/v229_test.go @@ -6,8 +6,8 @@ package v1_18 import ( "testing" + migration_tests "forgejo.org/models/gitea_migrations/test" "forgejo.org/models/issues" - migration_tests "forgejo.org/models/migrations/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/models/migrations/v1_18/v230.go b/models/gitea_migrations/v1_18/v230.go similarity index 100% rename from models/migrations/v1_18/v230.go rename to models/gitea_migrations/v1_18/v230.go diff --git a/models/migrations/v1_18/v230_test.go b/models/gitea_migrations/v1_18/v230_test.go similarity index 94% rename from models/migrations/v1_18/v230_test.go rename to models/gitea_migrations/v1_18/v230_test.go index da31b0dc9b..0db7025838 100644 --- a/models/migrations/v1_18/v230_test.go +++ b/models/gitea_migrations/v1_18/v230_test.go @@ -6,7 +6,7 @@ package v1_18 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/models/migrations/v1_19/main_test.go b/models/gitea_migrations/v1_19/main_test.go similarity index 76% rename from models/migrations/v1_19/main_test.go rename to models/gitea_migrations/v1_19/main_test.go index 9d1c3a57ea..6bf5ec4cc9 100644 --- a/models/migrations/v1_19/main_test.go +++ b/models/gitea_migrations/v1_19/main_test.go @@ -6,7 +6,7 @@ package v1_19 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" ) func TestMain(m *testing.M) { diff --git a/models/migrations/v1_19/v231.go b/models/gitea_migrations/v1_19/v231.go similarity index 100% rename from models/migrations/v1_19/v231.go rename to models/gitea_migrations/v1_19/v231.go diff --git a/models/migrations/v1_19/v232.go b/models/gitea_migrations/v1_19/v232.go similarity index 100% rename from models/migrations/v1_19/v232.go rename to models/gitea_migrations/v1_19/v232.go diff --git a/models/migrations/v1_19/v233.go b/models/gitea_migrations/v1_19/v233.go similarity index 100% rename from models/migrations/v1_19/v233.go rename to models/gitea_migrations/v1_19/v233.go diff --git a/models/migrations/v1_19/v233_test.go b/models/gitea_migrations/v1_19/v233_test.go similarity index 97% rename from models/migrations/v1_19/v233_test.go rename to models/gitea_migrations/v1_19/v233_test.go index 3d5eac9887..72770d9544 100644 --- a/models/migrations/v1_19/v233_test.go +++ b/models/gitea_migrations/v1_19/v233_test.go @@ -6,7 +6,7 @@ package v1_19 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "forgejo.org/modules/json" "forgejo.org/modules/secret" "forgejo.org/modules/setting" diff --git a/models/migrations/v1_19/v234.go b/models/gitea_migrations/v1_19/v234.go similarity index 100% rename from models/migrations/v1_19/v234.go rename to models/gitea_migrations/v1_19/v234.go diff --git a/models/migrations/v1_19/v235.go b/models/gitea_migrations/v1_19/v235.go similarity index 100% rename from models/migrations/v1_19/v235.go rename to models/gitea_migrations/v1_19/v235.go diff --git a/models/migrations/v1_19/v236.go b/models/gitea_migrations/v1_19/v236.go similarity index 100% rename from models/migrations/v1_19/v236.go rename to models/gitea_migrations/v1_19/v236.go diff --git a/models/migrations/v1_19/v237.go b/models/gitea_migrations/v1_19/v237.go similarity index 100% rename from models/migrations/v1_19/v237.go rename to models/gitea_migrations/v1_19/v237.go diff --git a/models/migrations/v1_19/v238.go b/models/gitea_migrations/v1_19/v238.go similarity index 100% rename from models/migrations/v1_19/v238.go rename to models/gitea_migrations/v1_19/v238.go diff --git a/models/migrations/v1_19/v239.go b/models/gitea_migrations/v1_19/v239.go similarity index 100% rename from models/migrations/v1_19/v239.go rename to models/gitea_migrations/v1_19/v239.go diff --git a/models/migrations/v1_19/v240.go b/models/gitea_migrations/v1_19/v240.go similarity index 100% rename from models/migrations/v1_19/v240.go rename to models/gitea_migrations/v1_19/v240.go diff --git a/models/migrations/v1_19/v241.go b/models/gitea_migrations/v1_19/v241.go similarity index 100% rename from models/migrations/v1_19/v241.go rename to models/gitea_migrations/v1_19/v241.go diff --git a/models/migrations/v1_19/v242.go b/models/gitea_migrations/v1_19/v242.go similarity index 100% rename from models/migrations/v1_19/v242.go rename to models/gitea_migrations/v1_19/v242.go diff --git a/models/migrations/v1_19/v243.go b/models/gitea_migrations/v1_19/v243.go similarity index 100% rename from models/migrations/v1_19/v243.go rename to models/gitea_migrations/v1_19/v243.go diff --git a/models/migrations/v1_20/main_test.go b/models/gitea_migrations/v1_20/main_test.go similarity index 76% rename from models/migrations/v1_20/main_test.go rename to models/gitea_migrations/v1_20/main_test.go index ee5eec5ef6..dd71e73804 100644 --- a/models/migrations/v1_20/main_test.go +++ b/models/gitea_migrations/v1_20/main_test.go @@ -6,7 +6,7 @@ package v1_20 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" ) func TestMain(m *testing.M) { diff --git a/models/migrations/v1_20/v244.go b/models/gitea_migrations/v1_20/v244.go similarity index 100% rename from models/migrations/v1_20/v244.go rename to models/gitea_migrations/v1_20/v244.go diff --git a/models/migrations/v1_20/v245.go b/models/gitea_migrations/v1_20/v245.go similarity index 97% rename from models/migrations/v1_20/v245.go rename to models/gitea_migrations/v1_20/v245.go index 5e034568c4..730af95ad0 100644 --- a/models/migrations/v1_20/v245.go +++ b/models/gitea_migrations/v1_20/v245.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "forgejo.org/modules/setting" "xorm.io/xorm" diff --git a/models/migrations/v1_20/v246.go b/models/gitea_migrations/v1_20/v246.go similarity index 100% rename from models/migrations/v1_20/v246.go rename to models/gitea_migrations/v1_20/v246.go diff --git a/models/migrations/v1_20/v247.go b/models/gitea_migrations/v1_20/v247.go similarity index 100% rename from models/migrations/v1_20/v247.go rename to models/gitea_migrations/v1_20/v247.go diff --git a/models/migrations/v1_20/v248.go b/models/gitea_migrations/v1_20/v248.go similarity index 100% rename from models/migrations/v1_20/v248.go rename to models/gitea_migrations/v1_20/v248.go diff --git a/models/migrations/v1_20/v249.go b/models/gitea_migrations/v1_20/v249.go similarity index 100% rename from models/migrations/v1_20/v249.go rename to models/gitea_migrations/v1_20/v249.go diff --git a/models/migrations/v1_20/v250.go b/models/gitea_migrations/v1_20/v250.go similarity index 100% rename from models/migrations/v1_20/v250.go rename to models/gitea_migrations/v1_20/v250.go diff --git a/models/migrations/v1_20/v251.go b/models/gitea_migrations/v1_20/v251.go similarity index 100% rename from models/migrations/v1_20/v251.go rename to models/gitea_migrations/v1_20/v251.go diff --git a/models/migrations/v1_20/v252.go b/models/gitea_migrations/v1_20/v252.go similarity index 100% rename from models/migrations/v1_20/v252.go rename to models/gitea_migrations/v1_20/v252.go diff --git a/models/migrations/v1_20/v253.go b/models/gitea_migrations/v1_20/v253.go similarity index 100% rename from models/migrations/v1_20/v253.go rename to models/gitea_migrations/v1_20/v253.go diff --git a/models/migrations/v1_20/v254.go b/models/gitea_migrations/v1_20/v254.go similarity index 100% rename from models/migrations/v1_20/v254.go rename to models/gitea_migrations/v1_20/v254.go diff --git a/models/migrations/v1_20/v255.go b/models/gitea_migrations/v1_20/v255.go similarity index 100% rename from models/migrations/v1_20/v255.go rename to models/gitea_migrations/v1_20/v255.go diff --git a/models/migrations/v1_20/v256.go b/models/gitea_migrations/v1_20/v256.go similarity index 100% rename from models/migrations/v1_20/v256.go rename to models/gitea_migrations/v1_20/v256.go diff --git a/models/migrations/v1_20/v257.go b/models/gitea_migrations/v1_20/v257.go similarity index 100% rename from models/migrations/v1_20/v257.go rename to models/gitea_migrations/v1_20/v257.go diff --git a/models/migrations/v1_20/v258.go b/models/gitea_migrations/v1_20/v258.go similarity index 100% rename from models/migrations/v1_20/v258.go rename to models/gitea_migrations/v1_20/v258.go diff --git a/models/migrations/v1_20/v259.go b/models/gitea_migrations/v1_20/v259.go similarity index 100% rename from models/migrations/v1_20/v259.go rename to models/gitea_migrations/v1_20/v259.go diff --git a/models/migrations/v1_20/v259_test.go b/models/gitea_migrations/v1_20/v259_test.go similarity index 97% rename from models/migrations/v1_20/v259_test.go rename to models/gitea_migrations/v1_20/v259_test.go index b41b6c7995..6805eb31ce 100644 --- a/models/migrations/v1_20/v259_test.go +++ b/models/gitea_migrations/v1_20/v259_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/models/migrations/v1_21/main_test.go b/models/gitea_migrations/v1_21/main_test.go similarity index 76% rename from models/migrations/v1_21/main_test.go rename to models/gitea_migrations/v1_21/main_test.go index 3f10a39a94..e467c66a91 100644 --- a/models/migrations/v1_21/main_test.go +++ b/models/gitea_migrations/v1_21/main_test.go @@ -6,7 +6,7 @@ package v1_21 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" ) func TestMain(m *testing.M) { diff --git a/models/migrations/v1_21/v260.go b/models/gitea_migrations/v1_21/v260.go similarity index 91% rename from models/migrations/v1_21/v260.go rename to models/gitea_migrations/v1_21/v260.go index b73b53bd61..31f9c5b161 100644 --- a/models/migrations/v1_21/v260.go +++ b/models/gitea_migrations/v1_21/v260.go @@ -4,7 +4,7 @@ package v1_21 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" ) diff --git a/models/migrations/v1_21/v261.go b/models/gitea_migrations/v1_21/v261.go similarity index 100% rename from models/migrations/v1_21/v261.go rename to models/gitea_migrations/v1_21/v261.go diff --git a/models/migrations/v1_21/v262.go b/models/gitea_migrations/v1_21/v262.go similarity index 100% rename from models/migrations/v1_21/v262.go rename to models/gitea_migrations/v1_21/v262.go diff --git a/models/migrations/v1_21/v263.go b/models/gitea_migrations/v1_21/v263.go similarity index 100% rename from models/migrations/v1_21/v263.go rename to models/gitea_migrations/v1_21/v263.go diff --git a/models/migrations/v1_21/v264.go b/models/gitea_migrations/v1_21/v264.go similarity index 100% rename from models/migrations/v1_21/v264.go rename to models/gitea_migrations/v1_21/v264.go diff --git a/models/migrations/v1_21/v265.go b/models/gitea_migrations/v1_21/v265.go similarity index 100% rename from models/migrations/v1_21/v265.go rename to models/gitea_migrations/v1_21/v265.go diff --git a/models/migrations/v1_21/v266.go b/models/gitea_migrations/v1_21/v266.go similarity index 100% rename from models/migrations/v1_21/v266.go rename to models/gitea_migrations/v1_21/v266.go diff --git a/models/migrations/v1_21/v267.go b/models/gitea_migrations/v1_21/v267.go similarity index 100% rename from models/migrations/v1_21/v267.go rename to models/gitea_migrations/v1_21/v267.go diff --git a/models/migrations/v1_21/v268.go b/models/gitea_migrations/v1_21/v268.go similarity index 100% rename from models/migrations/v1_21/v268.go rename to models/gitea_migrations/v1_21/v268.go diff --git a/models/migrations/v1_21/v269.go b/models/gitea_migrations/v1_21/v269.go similarity index 100% rename from models/migrations/v1_21/v269.go rename to models/gitea_migrations/v1_21/v269.go diff --git a/models/migrations/v1_21/v270.go b/models/gitea_migrations/v1_21/v270.go similarity index 100% rename from models/migrations/v1_21/v270.go rename to models/gitea_migrations/v1_21/v270.go diff --git a/models/migrations/v1_21/v271.go b/models/gitea_migrations/v1_21/v271.go similarity index 100% rename from models/migrations/v1_21/v271.go rename to models/gitea_migrations/v1_21/v271.go diff --git a/models/migrations/v1_21/v272.go b/models/gitea_migrations/v1_21/v272.go similarity index 100% rename from models/migrations/v1_21/v272.go rename to models/gitea_migrations/v1_21/v272.go diff --git a/models/migrations/v1_21/v273.go b/models/gitea_migrations/v1_21/v273.go similarity index 100% rename from models/migrations/v1_21/v273.go rename to models/gitea_migrations/v1_21/v273.go diff --git a/models/migrations/v1_21/v274.go b/models/gitea_migrations/v1_21/v274.go similarity index 100% rename from models/migrations/v1_21/v274.go rename to models/gitea_migrations/v1_21/v274.go diff --git a/models/migrations/v1_21/v275.go b/models/gitea_migrations/v1_21/v275.go similarity index 100% rename from models/migrations/v1_21/v275.go rename to models/gitea_migrations/v1_21/v275.go diff --git a/models/migrations/v1_21/v276.go b/models/gitea_migrations/v1_21/v276.go similarity index 100% rename from models/migrations/v1_21/v276.go rename to models/gitea_migrations/v1_21/v276.go diff --git a/models/migrations/v1_21/v277.go b/models/gitea_migrations/v1_21/v277.go similarity index 100% rename from models/migrations/v1_21/v277.go rename to models/gitea_migrations/v1_21/v277.go diff --git a/models/migrations/v1_21/v278.go b/models/gitea_migrations/v1_21/v278.go similarity index 100% rename from models/migrations/v1_21/v278.go rename to models/gitea_migrations/v1_21/v278.go diff --git a/models/migrations/v1_21/v279.go b/models/gitea_migrations/v1_21/v279.go similarity index 100% rename from models/migrations/v1_21/v279.go rename to models/gitea_migrations/v1_21/v279.go diff --git a/models/migrations/v1_22/main_test.go b/models/gitea_migrations/v1_22/main_test.go similarity index 76% rename from models/migrations/v1_22/main_test.go rename to models/gitea_migrations/v1_22/main_test.go index 7b05993e09..81e4664b90 100644 --- a/models/migrations/v1_22/main_test.go +++ b/models/gitea_migrations/v1_22/main_test.go @@ -6,7 +6,7 @@ package v1_22 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" ) func TestMain(m *testing.M) { diff --git a/models/migrations/v1_22/v280.go b/models/gitea_migrations/v1_22/v280.go similarity index 100% rename from models/migrations/v1_22/v280.go rename to models/gitea_migrations/v1_22/v280.go diff --git a/models/migrations/v1_22/v281.go b/models/gitea_migrations/v1_22/v281.go similarity index 100% rename from models/migrations/v1_22/v281.go rename to models/gitea_migrations/v1_22/v281.go diff --git a/models/migrations/v1_22/v282.go b/models/gitea_migrations/v1_22/v282.go similarity index 100% rename from models/migrations/v1_22/v282.go rename to models/gitea_migrations/v1_22/v282.go diff --git a/models/migrations/v1_22/v283.go b/models/gitea_migrations/v1_22/v283.go similarity index 100% rename from models/migrations/v1_22/v283.go rename to models/gitea_migrations/v1_22/v283.go diff --git a/models/migrations/v1_22/v283_test.go b/models/gitea_migrations/v1_22/v283_test.go similarity index 91% rename from models/migrations/v1_22/v283_test.go rename to models/gitea_migrations/v1_22/v283_test.go index 652d96ac16..647555ca9e 100644 --- a/models/migrations/v1_22/v283_test.go +++ b/models/gitea_migrations/v1_22/v283_test.go @@ -6,7 +6,7 @@ package v1_22 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/require" ) diff --git a/models/migrations/v1_22/v284.go b/models/gitea_migrations/v1_22/v284.go similarity index 100% rename from models/migrations/v1_22/v284.go rename to models/gitea_migrations/v1_22/v284.go diff --git a/models/migrations/v1_22/v285.go b/models/gitea_migrations/v1_22/v285.go similarity index 100% rename from models/migrations/v1_22/v285.go rename to models/gitea_migrations/v1_22/v285.go diff --git a/models/migrations/v1_22/v286.go b/models/gitea_migrations/v1_22/v286.go similarity index 100% rename from models/migrations/v1_22/v286.go rename to models/gitea_migrations/v1_22/v286.go diff --git a/models/migrations/v1_22/v286_test.go b/models/gitea_migrations/v1_22/v286_test.go similarity index 97% rename from models/migrations/v1_22/v286_test.go rename to models/gitea_migrations/v1_22/v286_test.go index 5bb3334df2..fd630508ec 100644 --- a/models/migrations/v1_22/v286_test.go +++ b/models/gitea_migrations/v1_22/v286_test.go @@ -6,7 +6,7 @@ package v1_22 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/models/migrations/v1_22/v287.go b/models/gitea_migrations/v1_22/v287.go similarity index 100% rename from models/migrations/v1_22/v287.go rename to models/gitea_migrations/v1_22/v287.go diff --git a/models/migrations/v1_22/v288.go b/models/gitea_migrations/v1_22/v288.go similarity index 100% rename from models/migrations/v1_22/v288.go rename to models/gitea_migrations/v1_22/v288.go diff --git a/models/migrations/v1_22/v289.go b/models/gitea_migrations/v1_22/v289.go similarity index 100% rename from models/migrations/v1_22/v289.go rename to models/gitea_migrations/v1_22/v289.go diff --git a/models/migrations/v1_22/v290.go b/models/gitea_migrations/v1_22/v290.go similarity index 100% rename from models/migrations/v1_22/v290.go rename to models/gitea_migrations/v1_22/v290.go diff --git a/models/migrations/v1_22/v290_test.go b/models/gitea_migrations/v1_22/v290_test.go similarity index 96% rename from models/migrations/v1_22/v290_test.go rename to models/gitea_migrations/v1_22/v290_test.go index a1907cf4d6..a1c303cd28 100644 --- a/models/migrations/v1_22/v290_test.go +++ b/models/gitea_migrations/v1_22/v290_test.go @@ -7,7 +7,7 @@ import ( "strconv" "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "forgejo.org/modules/timeutil" webhook_module "forgejo.org/modules/webhook" diff --git a/models/migrations/v1_22/v291.go b/models/gitea_migrations/v1_22/v291.go similarity index 100% rename from models/migrations/v1_22/v291.go rename to models/gitea_migrations/v1_22/v291.go diff --git a/models/migrations/v1_22/v292.go b/models/gitea_migrations/v1_22/v292.go similarity index 100% rename from models/migrations/v1_22/v292.go rename to models/gitea_migrations/v1_22/v292.go diff --git a/models/migrations/v1_22/v293.go b/models/gitea_migrations/v1_22/v293.go similarity index 100% rename from models/migrations/v1_22/v293.go rename to models/gitea_migrations/v1_22/v293.go diff --git a/models/migrations/v1_22/v293_test.go b/models/gitea_migrations/v1_22/v293_test.go similarity index 95% rename from models/migrations/v1_22/v293_test.go rename to models/gitea_migrations/v1_22/v293_test.go index 6b1931b761..902e20fbc2 100644 --- a/models/migrations/v1_22/v293_test.go +++ b/models/gitea_migrations/v1_22/v293_test.go @@ -7,7 +7,7 @@ import ( "testing" "forgejo.org/models/db" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "forgejo.org/models/project" "github.com/stretchr/testify/assert" diff --git a/models/migrations/v1_22/v294.go b/models/gitea_migrations/v1_22/v294.go similarity index 100% rename from models/migrations/v1_22/v294.go rename to models/gitea_migrations/v1_22/v294.go diff --git a/models/migrations/v1_22/v294_test.go b/models/gitea_migrations/v1_22/v294_test.go similarity index 95% rename from models/migrations/v1_22/v294_test.go rename to models/gitea_migrations/v1_22/v294_test.go index e87a4bc85f..197fada2a0 100644 --- a/models/migrations/v1_22/v294_test.go +++ b/models/gitea_migrations/v1_22/v294_test.go @@ -7,7 +7,7 @@ import ( "slices" "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/models/migrations/v1_22/v295.go b/models/gitea_migrations/v1_22/v295.go similarity index 100% rename from models/migrations/v1_22/v295.go rename to models/gitea_migrations/v1_22/v295.go diff --git a/models/migrations/v1_22/v296.go b/models/gitea_migrations/v1_22/v296.go similarity index 100% rename from models/migrations/v1_22/v296.go rename to models/gitea_migrations/v1_22/v296.go diff --git a/models/migrations/v1_22/v298.go b/models/gitea_migrations/v1_22/v298.go similarity index 100% rename from models/migrations/v1_22/v298.go rename to models/gitea_migrations/v1_22/v298.go diff --git a/models/migrations/v1_23/main_test.go b/models/gitea_migrations/v1_23/main_test.go similarity index 76% rename from models/migrations/v1_23/main_test.go rename to models/gitea_migrations/v1_23/main_test.go index 5fb4fec999..f8337ab2ee 100644 --- a/models/migrations/v1_23/main_test.go +++ b/models/gitea_migrations/v1_23/main_test.go @@ -6,7 +6,7 @@ package v1_23 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" ) func TestMain(m *testing.M) { diff --git a/models/migrations/v1_23/v299.go b/models/gitea_migrations/v1_23/v299.go similarity index 100% rename from models/migrations/v1_23/v299.go rename to models/gitea_migrations/v1_23/v299.go diff --git a/models/migrations/v1_23/v300.go b/models/gitea_migrations/v1_23/v300.go similarity index 100% rename from models/migrations/v1_23/v300.go rename to models/gitea_migrations/v1_23/v300.go diff --git a/models/migrations/v1_23/v301.go b/models/gitea_migrations/v1_23/v301.go similarity index 100% rename from models/migrations/v1_23/v301.go rename to models/gitea_migrations/v1_23/v301.go diff --git a/models/migrations/v1_23/v302.go b/models/gitea_migrations/v1_23/v302.go similarity index 100% rename from models/migrations/v1_23/v302.go rename to models/gitea_migrations/v1_23/v302.go diff --git a/models/migrations/v1_23/v303.go b/models/gitea_migrations/v1_23/v303.go similarity index 96% rename from models/migrations/v1_23/v303.go rename to models/gitea_migrations/v1_23/v303.go index 03197d2857..f6cbf7d859 100644 --- a/models/migrations/v1_23/v303.go +++ b/models/gitea_migrations/v1_23/v303.go @@ -4,7 +4,7 @@ package v1_23 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" "xorm.io/xorm/schemas" diff --git a/models/migrations/v1_23/v303_test.go b/models/gitea_migrations/v1_23/v303_test.go similarity index 93% rename from models/migrations/v1_23/v303_test.go rename to models/gitea_migrations/v1_23/v303_test.go index f2c764bae3..5eee15c47a 100644 --- a/models/migrations/v1_23/v303_test.go +++ b/models/gitea_migrations/v1_23/v303_test.go @@ -6,7 +6,7 @@ package v1_23 import ( "testing" - migration_tests "forgejo.org/models/migrations/test" + migration_tests "forgejo.org/models/gitea_migrations/test" "github.com/stretchr/testify/require" "xorm.io/xorm/schemas" diff --git a/models/migrations/v1_6/v70.go b/models/gitea_migrations/v1_6/v70.go similarity index 100% rename from models/migrations/v1_6/v70.go rename to models/gitea_migrations/v1_6/v70.go diff --git a/models/migrations/v1_6/v71.go b/models/gitea_migrations/v1_6/v71.go similarity index 97% rename from models/migrations/v1_6/v71.go rename to models/gitea_migrations/v1_6/v71.go index 42fe8cd1ba..c9e96980d3 100644 --- a/models/migrations/v1_6/v71.go +++ b/models/gitea_migrations/v1_6/v71.go @@ -6,7 +6,7 @@ package v1_6 import ( "fmt" - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "forgejo.org/modules/timeutil" "forgejo.org/modules/util" diff --git a/models/migrations/v1_6/v72.go b/models/gitea_migrations/v1_6/v72.go similarity index 100% rename from models/migrations/v1_6/v72.go rename to models/gitea_migrations/v1_6/v72.go diff --git a/models/migrations/v1_7/v73.go b/models/gitea_migrations/v1_7/v73.go similarity index 100% rename from models/migrations/v1_7/v73.go rename to models/gitea_migrations/v1_7/v73.go diff --git a/models/migrations/v1_7/v74.go b/models/gitea_migrations/v1_7/v74.go similarity index 100% rename from models/migrations/v1_7/v74.go rename to models/gitea_migrations/v1_7/v74.go diff --git a/models/migrations/v1_7/v75.go b/models/gitea_migrations/v1_7/v75.go similarity index 100% rename from models/migrations/v1_7/v75.go rename to models/gitea_migrations/v1_7/v75.go diff --git a/models/migrations/v1_8/v76.go b/models/gitea_migrations/v1_8/v76.go similarity index 100% rename from models/migrations/v1_8/v76.go rename to models/gitea_migrations/v1_8/v76.go diff --git a/models/migrations/v1_8/v77.go b/models/gitea_migrations/v1_8/v77.go similarity index 100% rename from models/migrations/v1_8/v77.go rename to models/gitea_migrations/v1_8/v77.go diff --git a/models/migrations/v1_8/v78.go b/models/gitea_migrations/v1_8/v78.go similarity index 94% rename from models/migrations/v1_8/v78.go rename to models/gitea_migrations/v1_8/v78.go index 840fc20d96..829435ead6 100644 --- a/models/migrations/v1_8/v78.go +++ b/models/gitea_migrations/v1_8/v78.go @@ -4,7 +4,7 @@ package v1_8 import ( - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "xorm.io/xorm" ) diff --git a/models/migrations/v1_8/v79.go b/models/gitea_migrations/v1_8/v79.go similarity index 100% rename from models/migrations/v1_8/v79.go rename to models/gitea_migrations/v1_8/v79.go diff --git a/models/migrations/v1_8/v80.go b/models/gitea_migrations/v1_8/v80.go similarity index 100% rename from models/migrations/v1_8/v80.go rename to models/gitea_migrations/v1_8/v80.go diff --git a/models/migrations/v1_8/v81.go b/models/gitea_migrations/v1_8/v81.go similarity index 100% rename from models/migrations/v1_8/v81.go rename to models/gitea_migrations/v1_8/v81.go diff --git a/models/migrations/v1_9/v82.go b/models/gitea_migrations/v1_9/v82.go similarity index 100% rename from models/migrations/v1_9/v82.go rename to models/gitea_migrations/v1_9/v82.go diff --git a/models/migrations/v1_9/v83.go b/models/gitea_migrations/v1_9/v83.go similarity index 100% rename from models/migrations/v1_9/v83.go rename to models/gitea_migrations/v1_9/v83.go diff --git a/models/migrations/v1_9/v84.go b/models/gitea_migrations/v1_9/v84.go similarity index 100% rename from models/migrations/v1_9/v84.go rename to models/gitea_migrations/v1_9/v84.go diff --git a/models/migrations/v1_9/v85.go b/models/gitea_migrations/v1_9/v85.go similarity index 98% rename from models/migrations/v1_9/v85.go rename to models/gitea_migrations/v1_9/v85.go index 9d5adc82dd..c54a006f4c 100644 --- a/models/migrations/v1_9/v85.go +++ b/models/gitea_migrations/v1_9/v85.go @@ -6,7 +6,7 @@ package v1_9 import ( "fmt" - "forgejo.org/models/migrations/base" + "forgejo.org/models/gitea_migrations/base" "forgejo.org/modules/log" "forgejo.org/modules/timeutil" "forgejo.org/modules/util" diff --git a/models/migrations/v1_9/v86.go b/models/gitea_migrations/v1_9/v86.go similarity index 100% rename from models/migrations/v1_9/v86.go rename to models/gitea_migrations/v1_9/v86.go diff --git a/models/migrations/v1_9/v87.go b/models/gitea_migrations/v1_9/v87.go similarity index 100% rename from models/migrations/v1_9/v87.go rename to models/gitea_migrations/v1_9/v87.go diff --git a/models/issues/TestGetUIDsAndStopwatch/stopwatch.yml b/models/issues/TestGetUIDsAndStopwatch/stopwatch.yml index f564e4b389..231d142dc4 100644 --- a/models/issues/TestGetUIDsAndStopwatch/stopwatch.yml +++ b/models/issues/TestGetUIDsAndStopwatch/stopwatch.yml @@ -3,9 +3,3 @@ user_id: 1 issue_id: 2 created_unix: 1500988004 - -- - id: 4 - user_id: 3 - issue_id: 0 - created_unix: 1500988003 diff --git a/models/issues/issue.go b/models/issues/issue.go index 14848e4c98..b1966ea50f 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -116,7 +116,7 @@ type Issue struct { DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"` - Created timeutil.TimeStampNano + Created timeutil.TimeStampNano // more precise Created, but may not be populated for older issues CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` diff --git a/models/issues/stopwatch.go b/models/issues/stopwatch.go index 2ff2a17d92..90a637e993 100644 --- a/models/issues/stopwatch.go +++ b/models/issues/stopwatch.go @@ -32,8 +32,8 @@ func (err ErrIssueStopwatchNotExist) Unwrap() error { // Stopwatch represents a stopwatch for time tracking. type Stopwatch struct { ID int64 `xorm:"pk autoincr"` - IssueID int64 `xorm:"INDEX"` - UserID int64 `xorm:"INDEX"` + IssueID int64 `xorm:"INDEX REFERENCES(issue, id)"` + UserID int64 `xorm:"INDEX REFERENCES(user, id)"` CreatedUnix timeutil.TimeStamp `xorm:"created"` } @@ -63,7 +63,7 @@ func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, ex // GetUIDsAndNotificationCounts between the two provided times func GetUIDsAndStopwatch(ctx context.Context) (map[int64][]*Stopwatch, error) { sws := []*Stopwatch{} - if err := db.GetEngine(ctx).Where("issue_id != 0").Find(&sws); err != nil { + if err := db.GetEngine(ctx).Find(&sws); err != nil { return nil, err } res := map[int64][]*Stopwatch{} diff --git a/models/issues/tracked_time.go b/models/issues/tracked_time.go index 05d7b15815..d229d83470 100644 --- a/models/issues/tracked_time.go +++ b/models/issues/tracked_time.go @@ -22,9 +22,9 @@ import ( // TrackedTime represents a time that was spent for a specific issue. type TrackedTime struct { ID int64 `xorm:"pk autoincr"` - IssueID int64 `xorm:"INDEX"` + IssueID int64 `xorm:"INDEX REFERENCES(issue, id)"` Issue *Issue `xorm:"-"` - UserID int64 `xorm:"INDEX"` + UserID int64 `xorm:"INDEX REFERENCES(user, id)"` User *user_model.User `xorm:"-"` Created time.Time `xorm:"-"` CreatedUnix int64 `xorm:"created"` diff --git a/models/migrations/migrations_test.go b/models/migrations/migrations_test.go deleted file mode 100644 index 468c918c93..0000000000 --- a/models/migrations/migrations_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package migrations - -import ( - "testing" - - "forgejo.org/modules/test" - - "github.com/stretchr/testify/assert" -) - -func TestMigrations(t *testing.T) { - defer test.MockVariableValue(&preparedMigrations, []*migration{ - {idNumber: 70}, - {idNumber: 71}, - })() - assert.EqualValues(t, 72, calcDBVersion(preparedMigrations)) - assert.EqualValues(t, 72, ExpectedDBVersion()) - - assert.EqualValues(t, 71, migrationIDNumberToDBVersion(70)) - - assert.Equal(t, []*migration{{idNumber: 70}, {idNumber: 71}}, getPendingMigrations(70, preparedMigrations)) - assert.Equal(t, []*migration{{idNumber: 71}}, getPendingMigrations(71, preparedMigrations)) - assert.Equal(t, []*migration{}, getPendingMigrations(72, preparedMigrations)) -} diff --git a/models/perm/access/access.go b/models/perm/access/access.go index 87ee600a15..54526d0f5c 100644 --- a/models/perm/access/access.go +++ b/models/perm/access/access.go @@ -22,8 +22,8 @@ import ( // repository, the members of the owners team are in this table. type Access struct { ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"UNIQUE(s)"` - RepoID int64 `xorm:"UNIQUE(s)"` + UserID int64 `xorm:"UNIQUE(s) REFERENCES(user, id)"` + RepoID int64 `xorm:"UNIQUE(s) REFERENCES(repository, id)"` Mode perm.AccessMode } diff --git a/models/repo.go b/models/repo.go index 6f7ae25615..da3d33cf4a 100644 --- a/models/repo.go +++ b/models/repo.go @@ -292,42 +292,10 @@ func UpdateRepoStats(ctx context.Context, id int64) error { return nil } -func updateUserStarNumbers(ctx context.Context, users []user_model.User) error { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - for _, user := range users { - if _, err = db.Exec(ctx, "UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=?) WHERE id=?", user.ID, user.ID); err != nil { - return err - } - } - - return committer.Commit() -} - // DoctorUserStarNum recalculate Stars number for all user func DoctorUserStarNum(ctx context.Context) (err error) { - const batchSize = 100 - - for start := 0; ; start += batchSize { - users := make([]user_model.User, 0, batchSize) - if err = db.GetEngine(ctx).Limit(batchSize, start).Where("type = ?", 0).Cols("id").Find(&users); err != nil { - return err - } - if len(users) == 0 { - break - } - - if err = updateUserStarNumbers(ctx, users); err != nil { - return err - } - } - + _, err = db.Exec(ctx, "UPDATE `user` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE uid=`user`.id) WHERE type = 0") log.Debug("recalculate Stars number for all user finished") - return err } diff --git a/models/repo/TestGetUserForkLax/repository.yml b/models/repo/TestGetUserForkLax/repository.yml new file mode 100644 index 0000000000..c91ba18678 --- /dev/null +++ b/models/repo/TestGetUserForkLax/repository.yml @@ -0,0 +1,32 @@ +- + id: 64 + owner_id: 15 + owner_name: user15 + lower_name: repo10 + name: repo10 + default_branch: master + num_watches: 0 + num_stars: 0 + num_forks: 0 + num_issues: 0 + num_closed_issues: 0 + num_pulls: 0 + num_closed_pulls: 0 + num_milestones: 0 + num_closed_milestones: 0 + num_projects: 0 + num_closed_projects: 0 + is_private: false + is_empty: false + is_archived: false + is_mirror: false + status: 0 + is_fork: true + fork_id: 10 + is_template: false + template_id: 0 + size: 0 + is_fsck_enabled: true + close_issues_via_commit_in_any_branch: false + topics: '[]' + diff --git a/models/repo/TestGetUserForkLaxWithTwoChoices/repository.yml b/models/repo/TestGetUserForkLaxWithTwoChoices/repository.yml new file mode 100644 index 0000000000..859e76db88 --- /dev/null +++ b/models/repo/TestGetUserForkLaxWithTwoChoices/repository.yml @@ -0,0 +1,63 @@ +- + id: 64 + owner_id: 15 + owner_name: user15 + lower_name: repo10 + name: repo10 + default_branch: master + num_watches: 0 + num_stars: 0 + num_forks: 0 + num_issues: 0 + num_closed_issues: 0 + num_pulls: 0 + num_closed_pulls: 0 + num_milestones: 0 + num_closed_milestones: 0 + num_projects: 0 + num_closed_projects: 0 + is_private: false + is_empty: false + is_archived: false + is_mirror: false + status: 0 + is_fork: true + fork_id: 10 + is_template: false + template_id: 0 + size: 0 + is_fsck_enabled: true + close_issues_via_commit_in_any_branch: false + topics: '[]' +- + id: 65 + owner_id: 15 + owner_name: user15 + lower_name: repo11 + name: repo11 + default_branch: master + num_watches: 0 + num_stars: 0 + num_forks: 0 + num_issues: 0 + num_closed_issues: 0 + num_pulls: 0 + num_closed_pulls: 0 + num_milestones: 0 + num_closed_milestones: 0 + num_projects: 0 + num_closed_projects: 0 + is_private: false + is_empty: false + is_archived: false + is_mirror: false + status: 0 + is_fork: true + fork_id: 11 + is_template: false + template_id: 0 + size: 0 + is_fsck_enabled: true + close_issues_via_commit_in_any_branch: false + topics: '[]' + diff --git a/models/repo/collaboration.go b/models/repo/collaboration.go index 16d10d38b6..eec03b92b5 100644 --- a/models/repo/collaboration.go +++ b/models/repo/collaboration.go @@ -19,8 +19,8 @@ import ( // Collaboration represent the relation between an individual and a repository. type Collaboration struct { ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` - UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` + RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL REFERENCES(repository, id)"` + UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL REFERENCES(user, id)"` Mode perm.AccessMode `xorm:"DEFAULT 2 NOT NULL"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` diff --git a/models/repo/fork.go b/models/repo/fork.go index ed8b488738..f96f4cf117 100644 --- a/models/repo/fork.go +++ b/models/repo/fork.go @@ -5,6 +5,7 @@ package repo import ( "context" + "fmt" "forgejo.org/models/db" "forgejo.org/models/unit" @@ -42,6 +43,22 @@ func HasForkedRepo(ctx context.Context, ownerID, repoID int64) bool { return has } +// HasForkedRepoLax checks if given user has already forked a repository with given ID, +// or if it the target repository is itself a fork, whether the user has a fork of its base +// (as that can also be used to make a PR). +func HasForkedRepoLax(ctx context.Context, ownerID int64, baseRepo *Repository) bool { + query := db.GetEngine(ctx). + Table("repository"). + Where("owner_id=?", ownerID) + if baseRepo.IsFork { + query = query.And("fork_id=? OR fork_id=?", baseRepo.ID, baseRepo.ForkID) + } else { + query = query.And("fork_id=?", baseRepo.ID) + } + has, _ := query.Exist() + return has +} + // GetUserFork return user forked repository from this repository, if not forked return nil func GetUserFork(ctx context.Context, repoID, userID int64) (*Repository, error) { var forkedRepo Repository @@ -55,6 +72,31 @@ func GetUserFork(ctx context.Context, repoID, userID int64) (*Repository, error) return &forkedRepo, nil } +// GetUserForkLax returns the user forked repository from this repository. +// If the passed repository is itself a fork and we have a fork of its base, it will be used as +// a fall-back. Otherwise return nil. +func GetUserForkLax(ctx context.Context, baseRepo *Repository, userID int64) (*Repository, error) { + var forkedRepo Repository + query := db.GetEngine(ctx).Where("owner_id = ?", userID) + if baseRepo.IsFork { + query = query.And("fork_id = ? OR fork_id = ?", baseRepo.ID, baseRepo.ForkID) + // prefer any repository that is marked as an exact fork of the target + // (ordering by a boolean means returning the rows where the condition is false first, + // hence the counter-intuitive condition) + query.OrderBy(fmt.Sprintf("fork_id != %d", baseRepo.ID)) + } else { + query = query.And("fork_id = ?", baseRepo.ID) + } + has, err := query.Get(&forkedRepo) + if err != nil { + return nil, err + } + if !has { + return nil, nil + } + return &forkedRepo, nil +} + // GetForks returns all the forks of the repository that are visible to the user. func GetForks(ctx context.Context, repo *Repository, user *user_model.User, listOptions db.ListOptions) ([]*Repository, int64, error) { sess := db.GetEngine(ctx).Where(AccessibleRepositoryCondition(user, unit.TypeInvalid)) diff --git a/models/repo/fork_test.go b/models/repo/fork_test.go index d567081ee6..fe18bbb423 100644 --- a/models/repo/fork_test.go +++ b/models/repo/fork_test.go @@ -32,3 +32,80 @@ func TestGetUserFork(t *testing.T) { require.NoError(t, err) assert.Nil(t, repo) } + +func TestGetUserForkLax(t *testing.T) { + defer unittest.OverrideFixtures("models/repo/TestGetUserForkLax")() + require.NoError(t, unittest.PrepareTestDatabase()) + + // User13 has repo 11 forked from repo10 + repo10, err := repo_model.GetRepositoryByID(db.DefaultContext, 10) + require.NoError(t, err) + assert.NotNil(t, repo10) + require.True(t, repo_model.HasForkedRepoLax(db.DefaultContext, 13, repo10)) + repo11, err := repo_model.GetUserForkLax(db.DefaultContext, repo10, 13) + require.NoError(t, err) + assert.NotNil(t, repo11) + assert.Equal(t, int64(11), repo11.ID) + assert.Equal(t, int64(10), repo11.ForkID) + + // user13 does not have a fork of repo9 + repo9, err := repo_model.GetRepositoryByID(db.DefaultContext, 9) + require.NoError(t, err) + assert.NotNil(t, repo9) + require.False(t, repo_model.HasForkedRepoLax(db.DefaultContext, 13, repo9)) + fork, err := repo_model.GetUserForkLax(db.DefaultContext, repo9, 13) + require.NoError(t, err) + assert.Nil(t, fork) + + // User15 has repo id 64 forked from repo10, which counts as a fork of repo11 since they have a common base + require.False(t, repo_model.HasForkedRepo(db.DefaultContext, 15, repo11.ID)) + require.True(t, repo_model.HasForkedRepoLax(db.DefaultContext, 15, repo11)) + fork, err = repo_model.GetUserForkLax(db.DefaultContext, repo11, 15) + require.NoError(t, err) + assert.NotNil(t, fork) + assert.Equal(t, int64(64), fork.ID) + assert.Equal(t, int64(10), fork.ForkID) +} + +func TestGetUserForkLaxWithTwoChoices(t *testing.T) { + defer unittest.OverrideFixtures("models/repo/TestGetUserForkLaxWithTwoChoices")() + require.NoError(t, unittest.PrepareTestDatabase()) + + // Test scenario: + // + // - repo10 + // - forked by user15 as repo64 + // - forked by user13 as repo11 + // - forked by user15 as repo65 + // + // In this scenario, both repo64 and repo65 can be used as forks of repo11 for user15, + // but we prefer to use repo65 because it's specifically marked as a fork of repo11 + + repo10, err := repo_model.GetRepositoryByID(db.DefaultContext, 10) + require.NoError(t, err) + assert.NotNil(t, repo10) + + // User15 has repo64 forked from repo10 + require.True(t, repo_model.HasForkedRepoLax(db.DefaultContext, 15, repo10)) + repo64, err := repo_model.GetUserForkLax(db.DefaultContext, repo10, 15) + require.NoError(t, err) + assert.NotNil(t, repo64) + assert.Equal(t, int64(64), repo64.ID) + assert.Equal(t, int64(10), repo64.ForkID) + + // User13 has repo11 forked from repo10 + require.True(t, repo_model.HasForkedRepoLax(db.DefaultContext, 13, repo10)) + repo11, err := repo_model.GetUserForkLax(db.DefaultContext, repo10, 13) + require.NoError(t, err) + assert.NotNil(t, repo11) + assert.Equal(t, int64(11), repo11.ID) + assert.Equal(t, int64(10), repo11.ForkID) + + // User15 has repo65 forked from repo11 + require.True(t, repo_model.HasForkedRepoLax(db.DefaultContext, 15, repo11)) + repo65, err := repo_model.GetUserForkLax(db.DefaultContext, repo11, 15) + require.NoError(t, err) + assert.NotNil(t, repo65) + assert.Equal(t, int64(65), repo65.ID) + assert.Equal(t, int64(11), repo65.ForkID) +} diff --git a/models/repo/upload.go b/models/repo/upload.go index 49152db7fd..a213cb1986 100644 --- a/models/repo/upload.go +++ b/models/repo/upload.go @@ -104,17 +104,6 @@ func GetUploadByUUID(ctx context.Context, uuid string) (*Upload, error) { return upload, nil } -// GetUploadsByUUIDs returns multiple uploads by UUIDS -func GetUploadsByUUIDs(ctx context.Context, uuids []string) ([]*Upload, error) { - if len(uuids) == 0 { - return []*Upload{}, nil - } - - // Silently drop invalid uuids. - uploads := make([]*Upload, 0, len(uuids)) - return uploads, db.GetEngine(ctx).In("uuid", uuids).Find(&uploads) -} - // DeleteUploads deletes multiple uploads func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) { if len(uploads) == 0 { diff --git a/models/unit/lint-locale-usage/llu.go b/models/unit/lint-locale-usage/llu.go new file mode 100644 index 0000000000..51e120dba2 --- /dev/null +++ b/models/unit/lint-locale-usage/llu.go @@ -0,0 +1,38 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package lintLocaleUsage + +import ( + "go/ast" + "go/token" + "strconv" + + llu "forgejo.org/build/lint-locale-usage" +) + +func HandleCompositeUnit(handler llu.Handler, fset *token.FileSet, n *ast.CompositeLit) { + ident, ok := n.Type.(*ast.Ident) + if !ok || ident.Name != "Unit" { + return + } + + if len(n.Elts) != 6 { + handler.OnWarning(fset, n.Pos(), "unexpected initialization of 'Unit' (unexpected number of arguments)") + return + } + // NameKey has index 2 + // invoked like '{{ctx.Locale.Tr $unit.NameKey}}' + nameKey, ok := n.Elts[2].(*ast.BasicLit) + if !ok || nameKey.Kind != token.STRING { + handler.OnWarning(fset, n.Elts[2].Pos(), "unexpected initialization of 'Unit' (expected string literal as NameKey)") + return + } + + // extract string content + arg, err := strconv.Unquote(nameKey.Value) + if err == nil { + // found interesting strings + handler.OnMsgid(fset, nameKey.ValuePos, arg, false) + } +} diff --git a/models/unittest/fixture_loader.go b/models/unittest/fixture_loader.go index 0b4ab52c61..5aea06550c 100644 --- a/models/unittest/fixture_loader.go +++ b/models/unittest/fixture_loader.go @@ -10,8 +10,10 @@ import ( "fmt" "os" "path/filepath" + "slices" "strings" + "forgejo.org/models/db" "forgejo.org/modules/container" "go.yaml.in/yaml/v3" @@ -41,7 +43,7 @@ func newFixtureLoader(db *sql.DB, dialect string, fixturePaths []string, allTabl fixtureFiles: []*fixtureFile{}, } - tablesWithoutFixture := allTableNames + tablesWithoutFixture := allTableNames.Clone() // Load fixtures for _, fixturePath := range fixturePaths { @@ -63,8 +65,10 @@ func newFixtureLoader(db *sql.DB, dialect string, fixturePaths []string, allTabl if err != nil { return nil, err } - l.fixtureFiles = append(l.fixtureFiles, fixtureFile) - tablesWithoutFixture.Remove(fixtureFile.name) + if allTableNames.Contains(fixtureFile.name) { + l.fixtureFiles = append(l.fixtureFiles, fixtureFile) + tablesWithoutFixture.Remove(fixtureFile.name) + } } } } else { @@ -72,7 +76,10 @@ func newFixtureLoader(db *sql.DB, dialect string, fixturePaths []string, allTabl if err != nil { return nil, err } - l.fixtureFiles = append(l.fixtureFiles, fixtureFile) + if allTableNames.Contains(fixtureFile.name) { + l.fixtureFiles = append(l.fixtureFiles, fixtureFile) + tablesWithoutFixture.Remove(fixtureFile.name) + } } } @@ -84,6 +91,8 @@ func newFixtureLoader(db *sql.DB, dialect string, fixturePaths []string, allTabl }) } + l.orderFixtures() + return l, nil } @@ -179,6 +188,14 @@ func (l *loader) buildFixtureFile(fixturePath string) (*fixtureFile, error) { return fixture, nil } +// Reorganize `fixtureFiles` based upon the order that they'll need to be inserted into the database to satisfy foreign +// key constraints. +func (l *loader) orderFixtures() { + slices.SortFunc(l.fixtureFiles, func(v1, v2 *fixtureFile) int { + return db.TableNameInsertionOrderSortFunc(v1.name, v2.name) + }) +} + func (l *loader) Load() error { // Start transaction. tx, err := l.db.Begin() @@ -192,14 +209,18 @@ func (l *loader) Load() error { // Clean the table and re-insert the fixtures. tableDeleted := make(container.Set[string]) - for _, fixture := range l.fixtureFiles { + + // Issue deletes first, in reverse of insertion order, to maintain foreign key constraints. + for i := len(l.fixtureFiles) - 1; i >= 0; i-- { + fixture := l.fixtureFiles[i] if !tableDeleted.Contains(fixture.name) { if _, err := tx.Exec(fmt.Sprintf("DELETE FROM %s", l.quoteKeyword(fixture.name))); err != nil { return fmt.Errorf("cannot delete table %s: %w", fixture.name, err) } tableDeleted.Add(fixture.name) } - + } + for _, fixture := range l.fixtureFiles { for _, insertSQL := range fixture.insertSQLs { if _, err := tx.Exec(insertSQL.statement, insertSQL.values...); err != nil { return fmt.Errorf("cannot insert %q with values %q: %w", insertSQL.statement, insertSQL.values, err) diff --git a/models/unittest/fixtures.go b/models/unittest/fixtures.go index 829cc16466..0019ae147b 100644 --- a/models/unittest/fixtures.go +++ b/models/unittest/fixtures.go @@ -80,8 +80,13 @@ func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) { } var allTables container.Set[string] - if !opts.SkipCleanRegistedModels { + if opts.OnlyAffectModels == nil { allTables = allTableNames().Clone() + } else { + allTables = make(container.Set[string]) + for _, bean := range opts.OnlyAffectModels { + allTables.Add(e.TableName(bean)) + } } fixturesLoader, err = newFixtureLoader(e.DB().DB, dialect, fixturePaths, allTables) diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index 795b1f8719..0fbf7ff6bb 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -217,10 +217,9 @@ type FixturesOptions struct { Files []string Dirs []string Base string - // By default all registered models are cleaned, even if they do not have - // fixture. Enabling this will skip that and only models with fixtures are - // considered. - SkipCleanRegistedModels bool + // By default all registered models are cleaned, even if they do not have fixture. When OnlyAffectModels is not-nil, + // cleaning registered models will be skipped and only these models with fixtures are considered. + OnlyAffectModels []any } // CreateTestEngine creates a memory database and loads the fixture data from fixturesDir diff --git a/models/user/search.go b/models/user/search.go index b30422e269..08cf6a14a3 100644 --- a/models/user/search.go +++ b/models/user/search.go @@ -38,6 +38,7 @@ type SearchUserOptions struct { IsRestricted optional.Option[bool] IsTwoFactorEnabled optional.Option[bool] IsProhibitLogin optional.Option[bool] + AccountType optional.Option[UserType] IncludeReserved bool Load2FAStatus bool @@ -123,6 +124,10 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess cond = cond.And(builder.Eq{"prohibit_login": opts.IsProhibitLogin.Value()}) } + if opts.AccountType.Has() { + cond = cond.And(builder.Eq{"type": opts.AccountType.Value()}) + } + e := db.GetEngine(ctx) if !opts.IsTwoFactorEnabled.Has() { return e.Where(cond) diff --git a/models/user/user.go b/models/user/user.go index 8cdecc06e4..bf9060e0bc 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -236,7 +236,7 @@ func GetAllAdmins(ctx context.Context) ([]*User, error) { // MustHaveTwoFactor returns true if the user is a individual and requires 2fa func (u *User) MustHaveTwoFactor() bool { - if !u.IsIndividual() || setting.GlobalTwoFactorRequirement.IsNone() { + if u.IsActions() || !u.IsIndividual() || setting.GlobalTwoFactorRequirement.IsNone() { return false } @@ -1194,7 +1194,9 @@ func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) [] return newCommits } -// GetUserByEmail returns the user object by given e-mail if exists. +// GetUserByEmail returns the user associated with the email, if it exists +// and is activated. If the email is a no-reply address, then the user +// associated with that no-reply address is returned. func GetUserByEmail(ctx context.Context, email string) (*User, error) { if len(email) == 0 { return nil, ErrUserNotExist{Name: email} @@ -1227,6 +1229,26 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) { return nil, ErrUserNotExist{Name: email} } +// GetUserByEmailSimple returns the user associated with the email, if it exists. +// +// NOTE: You likely should use `GetUserByEmail`, which handles the no-reply +// address and only uses activated emails to get the user. +func GetUserByEmailSimple(ctx context.Context, email string) (*User, error) { + if len(email) == 0 { + return nil, ErrUserNotExist{Name: email} + } + + emailAddress := &EmailAddress{} + has, err := db.GetEngine(ctx).Where("lower_email = ?", strings.ToLower(email)).Get(emailAddress) + if err != nil { + return nil, err + } else if !has { + return nil, ErrUserNotExist{Name: email} + } + + return GetUserByID(ctx, emailAddress.UID) +} + // GetUser checks if a user already exists func GetUser(ctx context.Context, user *User) (bool, error) { return db.GetEngine(ctx).Get(user) diff --git a/models/user/user_test.go b/models/user/user_test.go index e4a94cbc57..50d741c839 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -219,10 +219,10 @@ func TestSearchUsers(t *testing.T) { } testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}}, - []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40, 1041}) + []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40, 42, 1041}) testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(false)}, - []int64{9}) + []int64{42, 9}) testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)}, []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40, 1041}) @@ -645,6 +645,7 @@ func TestMustHaveTwoFactor(t *testing.T) { org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}) ghostUser := user_model.NewGhostUser() + actionsUser := user_model.NewActionsUser() t.Run("NoneTwoFactorRequirement", func(t *testing.T) { // this should be the default, so don't have to set the variable @@ -653,6 +654,7 @@ func TestMustHaveTwoFactor(t *testing.T) { assert.False(t, restrictedUser.MustHaveTwoFactor()) assert.False(t, org.MustHaveTwoFactor()) assert.False(t, ghostUser.MustHaveTwoFactor()) + assert.False(t, actionsUser.MustHaveTwoFactor()) }) t.Run("AllTwoFactorRequirement", func(t *testing.T) { @@ -663,6 +665,7 @@ func TestMustHaveTwoFactor(t *testing.T) { assert.True(t, restrictedUser.MustHaveTwoFactor()) assert.False(t, org.MustHaveTwoFactor()) assert.True(t, ghostUser.MustHaveTwoFactor()) + assert.False(t, actionsUser.MustHaveTwoFactor()) }) t.Run("AdminTwoFactorRequirement", func(t *testing.T) { @@ -673,6 +676,7 @@ func TestMustHaveTwoFactor(t *testing.T) { assert.False(t, restrictedUser.MustHaveTwoFactor()) assert.False(t, org.MustHaveTwoFactor()) assert.False(t, ghostUser.MustHaveTwoFactor()) + assert.False(t, actionsUser.MustHaveTwoFactor()) }) } @@ -696,6 +700,7 @@ func TestIsAccessAllowed(t *testing.T) { restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}) prohibitLoginUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 37}) ghostUser := user_model.NewGhostUser() + actionsUser := user_model.NewActionsUser() // users with enabled WebAuthn normalWebAuthnUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 32}) @@ -711,6 +716,7 @@ func TestIsAccessAllowed(t *testing.T) { runTest(t, restrictedUser, false, true) runTest(t, prohibitLoginUser, false, false) runTest(t, ghostUser, false, false) + runTest(t, actionsUser, false, true) }) t.Run("enabled 2fa", func(t *testing.T) { @@ -736,6 +742,7 @@ func TestIsAccessAllowed(t *testing.T) { runTest(t, restrictedUser, false, false) runTest(t, prohibitLoginUser, false, false) runTest(t, ghostUser, false, false) + runTest(t, actionsUser, false, true) }) t.Run("enabled 2fa", func(t *testing.T) { @@ -761,6 +768,7 @@ func TestIsAccessAllowed(t *testing.T) { runTest(t, restrictedUser, false, true) runTest(t, prohibitLoginUser, false, false) runTest(t, ghostUser, false, false) + runTest(t, actionsUser, false, true) }) t.Run("enabled 2fa", func(t *testing.T) { @@ -999,6 +1007,7 @@ func TestPronounsPrivacy(t *testing.T) { func TestGetUserByEmail(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) + defer test.MockVariableValue(&setting.Service.NoReplyAddress, "noreply.example.org")() t.Run("Normal", func(t *testing.T) { u, err := user_model.GetUserByEmail(t.Context(), "user2@example.com") @@ -1017,4 +1026,33 @@ func TestGetUserByEmail(t *testing.T) { require.NoError(t, err) assert.EqualValues(t, 1, u.ID) }) + + t.Run("No-reply", func(t *testing.T) { + u, err := user_model.GetUserByEmail(t.Context(), "user1@noreply.example.org") + require.NoError(t, err) + assert.EqualValues(t, 1, u.ID) + }) +} + +func TestGetUserByEmailSimple(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + defer test.MockVariableValue(&setting.Service.NoReplyAddress, "noreply.example.org")() + + t.Run("Normal", func(t *testing.T) { + u, err := user_model.GetUserByEmailSimple(t.Context(), "user2@example.com") + require.NoError(t, err) + assert.EqualValues(t, 2, u.ID) + }) + + t.Run("Not activated", func(t *testing.T) { + u, err := user_model.GetUserByEmailSimple(t.Context(), "user11@example.com") + require.NoError(t, err) + assert.EqualValues(t, 11, u.ID) + }) + + t.Run("No-reply", func(t *testing.T) { + u, err := user_model.GetUserByEmailSimple(t.Context(), "user1@noreply.example.org") + require.ErrorIs(t, err, user_model.ErrUserNotExist{Name: "user1@noreply.example.org"}) + assert.Nil(t, u) + }) } diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index c3960d140a..f50e5f8289 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -21,9 +21,10 @@ import ( ) type DetectedWorkflow struct { - EntryName string - TriggerEvent *jobparser.Event - Content []byte + EntryName string + TriggerEvent *jobparser.Event + Content []byte + EventDetectionError error } func init() { @@ -127,7 +128,8 @@ func DetectWorkflows( TriggerEvent: &jobparser.Event{ Name: triggedEvent.Event(), }, - Content: content, + Content: content, + EventDetectionError: err, } workflows = append(workflows, dwf) continue diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go index 33af60a3b8..5ab872f953 100644 --- a/modules/avatar/avatar.go +++ b/modules/avatar/avatar.go @@ -10,6 +10,7 @@ import ( "image" "image/color" "image/png" + "io" _ "image/gif" // for processing gif images _ "image/jpeg" // for processing jpeg images @@ -17,6 +18,7 @@ import ( "forgejo.org/modules/avatar/identicon" "forgejo.org/modules/setting" + exif_terminator "code.superseriousbusiness.org/exif-terminator" "golang.org/x/image/draw" _ "golang.org/x/image/webp" // for processing webp images @@ -66,15 +68,29 @@ func processAvatarImage(data []byte, maxOriginSize int64) ([]byte, error) { return nil, fmt.Errorf("image height is too large: %d > %d", imgCfg.Height, setting.Avatar.MaxHeight) } + var cleanedBytes []byte + if imgType != "gif" { // "gif" is the only imgType supported above, but not supported by exif_terminator + cleanedData, err := exif_terminator.Terminate(bytes.NewReader(data), imgType) + if err != nil { + return nil, fmt.Errorf("error cleaning exif data: %w", err) + } + cleanedBytes, err = io.ReadAll(cleanedData) + if err != nil { + return nil, fmt.Errorf("error reading cleaned data: %w", err) + } + } else { // gif + cleanedBytes = data + } + // If the origin is small enough, just use it, then APNG could be supported, // otherwise, if the image is processed later, APNG loses animation. // And one more thing, webp is not fully supported, for animated webp, image.DecodeConfig works but Decode fails. // So for animated webp, if the uploaded file is smaller than maxOriginSize, it will be used, if it's larger, there will be an error. if len(data) < int(maxOriginSize) { - return data, nil + return cleanedBytes, nil } - img, _, err := image.Decode(bytes.NewReader(data)) + img, _, err := image.Decode(bytes.NewReader(cleanedBytes)) if err != nil { return nil, fmt.Errorf("image.Decode: %w", err) } @@ -94,7 +110,7 @@ func processAvatarImage(data []byte, maxOriginSize int64) ([]byte, error) { // usually the png compression is not good enough, use the original image (no cropping/resizing) if the origin is smaller if len(data) <= len(resized) { - return data, nil + return cleanedBytes, nil } return resized, nil diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go index 5c21ad5824..7a395c49cc 100644 --- a/modules/avatar/avatar_test.go +++ b/modules/avatar/avatar_test.go @@ -11,7 +11,10 @@ import ( "testing" "forgejo.org/modules/setting" + "forgejo.org/modules/test" + jpegstructure "code.superseriousbusiness.org/go-jpeg-image-structure/v2" + "github.com/dsoprea/go-exif/v3" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -30,8 +33,8 @@ func Test_RandomImage(t *testing.T) { } func Test_ProcessAvatarPNG(t *testing.T) { - setting.Avatar.MaxWidth = 4096 - setting.Avatar.MaxHeight = 4096 + defer test.MockVariableValue(&setting.Avatar.MaxWidth, 4096)() + defer test.MockVariableValue(&setting.Avatar.MaxHeight, 4096)() data, err := os.ReadFile("testdata/avatar.png") require.NoError(t, err) @@ -41,8 +44,8 @@ func Test_ProcessAvatarPNG(t *testing.T) { } func Test_ProcessAvatarJPEG(t *testing.T) { - setting.Avatar.MaxWidth = 4096 - setting.Avatar.MaxHeight = 4096 + defer test.MockVariableValue(&setting.Avatar.MaxWidth, 4096)() + defer test.MockVariableValue(&setting.Avatar.MaxHeight, 4096)() data, err := os.ReadFile("testdata/avatar.jpeg") require.NoError(t, err) @@ -51,17 +54,28 @@ func Test_ProcessAvatarJPEG(t *testing.T) { require.NoError(t, err) } +func Test_ProcessAvatarGIF(t *testing.T) { + defer test.MockVariableValue(&setting.Avatar.MaxWidth, 4096)() + defer test.MockVariableValue(&setting.Avatar.MaxHeight, 4096)() + + data, err := os.ReadFile("testdata/avatar.gif") + require.NoError(t, err) + + _, err = processAvatarImage(data, 262144) + require.NoError(t, err) +} + func Test_ProcessAvatarInvalidData(t *testing.T) { - setting.Avatar.MaxWidth = 5 - setting.Avatar.MaxHeight = 5 + defer test.MockVariableValue(&setting.Avatar.MaxWidth, 5)() + defer test.MockVariableValue(&setting.Avatar.MaxHeight, 5)() _, err := processAvatarImage([]byte{}, 12800) assert.EqualError(t, err, "image.DecodeConfig: image: unknown format") } func Test_ProcessAvatarInvalidImageSize(t *testing.T) { - setting.Avatar.MaxWidth = 5 - setting.Avatar.MaxHeight = 5 + defer test.MockVariableValue(&setting.Avatar.MaxWidth, 5)() + defer test.MockVariableValue(&setting.Avatar.MaxHeight, 5)() data, err := os.ReadFile("testdata/avatar.png") require.NoError(t, err) @@ -71,8 +85,8 @@ func Test_ProcessAvatarInvalidImageSize(t *testing.T) { } func Test_ProcessAvatarImage(t *testing.T) { - setting.Avatar.MaxWidth = 4096 - setting.Avatar.MaxHeight = 4096 + defer test.MockVariableValue(&setting.Avatar.MaxWidth, 4096)() + defer test.MockVariableValue(&setting.Avatar.MaxHeight, 4096)() scaledSize := DefaultAvatarSize * setting.Avatar.RenderedSizeFactor newImgData := func(size int, optHeight ...int) []byte { @@ -135,3 +149,40 @@ func Test_ProcessAvatarImage(t *testing.T) { _, err = processAvatarImage(origin, 262144) require.ErrorContains(t, err, "image width is too large: 10 > 5") } + +func safeExifJpeg(t *testing.T, jpeg []byte) { + t.Helper() + + parser := jpegstructure.NewJpegMediaParser() + mediaContext, err := parser.ParseBytes(jpeg) + require.NoError(t, err) + + sl := mediaContext.(*jpegstructure.SegmentList) + + rootIfd, _, err := sl.Exif() + require.NoError(t, err) + err = rootIfd.EnumerateTagsRecursively(func(ifd *exif.Ifd, ite *exif.IfdTagEntry) error { + assert.Equal(t, "Orientation", ite.TagName(), "only Orientation EXIF tag expected") + return nil + }) + require.NoError(t, err) +} + +func Test_ProcessAvatarExif(t *testing.T) { + t.Run("greater than max origin size", func(t *testing.T) { + data, err := os.ReadFile("testdata/exif.jpg") + require.NoError(t, err) + + processedData, err := processAvatarImage(data, 12800) + require.NoError(t, err) + safeExifJpeg(t, processedData) + }) + t.Run("smaller than max origin size", func(t *testing.T) { + data, err := os.ReadFile("testdata/exif.jpg") + require.NoError(t, err) + + processedData, err := processAvatarImage(data, 128000) + require.NoError(t, err) + safeExifJpeg(t, processedData) + }) +} diff --git a/modules/avatar/testdata/avatar.gif b/modules/avatar/testdata/avatar.gif new file mode 100644 index 0000000000..a70fbacd25 Binary files /dev/null and b/modules/avatar/testdata/avatar.gif differ diff --git a/modules/avatar/testdata/exif.jpg b/modules/avatar/testdata/exif.jpg new file mode 100644 index 0000000000..7e71d9b8f4 Binary files /dev/null and b/modules/avatar/testdata/exif.jpg differ diff --git a/modules/container/set.go b/modules/container/set.go index d3719dc552..6535d1e4bd 100644 --- a/modules/container/set.go +++ b/modules/container/set.go @@ -79,3 +79,22 @@ func (s Set[T]) Seq() iter.Seq[T] { func (s Set[T]) Clone() Set[T] { return maps.Clone(s) } + +// Computes the elements that are in this set, that aren't in the other set. +func (s Set[T]) Difference(other Set[T]) Set[T] { + result := make(Set[T]) + for key := range s { + if !other.Contains(key) { + result.Add(key) + } + } + return result +} + +func (s Set[T]) Slice() []T { + retval := make([]T, 0, len(s)) + for key := range s { + retval = append(retval, key) + } + return retval +} diff --git a/modules/git/commit_reader.go b/modules/git/commit_reader.go index ec8989f5a7..37b0c8c606 100644 --- a/modules/git/commit_reader.go +++ b/modules/git/commit_reader.go @@ -24,6 +24,7 @@ func CommitFromReader(gitRepo *Repository, objectID ObjectID, reader io.Reader) payloadSB := new(strings.Builder) signatureSB := new(strings.Builder) messageSB := new(strings.Builder) + firstLine := true message := false pgpsig := false @@ -83,21 +84,25 @@ readLoop: commit.Committer = &Signature{} commit.Committer.Decode(data) _, _ = payloadSB.Write(line) - case "encoding": - _, _ = payloadSB.Write(line) - case "change-id": // jj-vcs specific header. - _, _ = payloadSB.Write(line) case "gpgsig": fallthrough case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present. _, _ = signatureSB.Write(data) _ = signatureSB.WriteByte('\n') pgpsig = true + default: + // If the first line is not any of the known headers, then it is probably the prefix added when git cat-file is called with --batch, and that is not part of the payload + if !firstLine { + // Every subsequent header field is added to the payload + _, _ = payloadSB.Write(line) + } } } else { _, _ = messageSB.Write(line) _, _ = payloadSB.Write(line) } + + firstLine = false } commit.CommitMessage = messageSB.String() commit.Signature = &ObjectSignature{ diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go index ee57a735e6..cd6b17d0d7 100644 --- a/modules/git/commit_test.go +++ b/modules/git/commit_test.go @@ -239,6 +239,77 @@ January where the year starts on a Monday :)`, commitFromReader.Signature.Payloa assert.Equal(t, "Nicole Patricia Mazzuca ", commitFromReader.Author.String()) } +func TestGitbutlerCustomHeaderFields(t *testing.T) { + // example from: https://github.com/go-gitea/gitea/issues/34529#issuecomment-2908481092 + commitString := `tree a29321bf9e3ec433ed9e47b1cbbac6906c71fc60 +parent c0d83043ade7fa3ca10659608799477e9daa670b +author Sebastian Thiel 1747920681 +0200 +committer Sebastian Thiel 1748010747 +0200 +gitbutler-headers-version 2 +gitbutler-change-id 1063f7ea-d841-43b3-903a-01747681c40d +gpgsig -----BEGIN PGP SIGNATURE----- + + iQIzBAABCAAdFiEE6vnM/NCHZAjyl8YKnLXueJXoJosFAmgwhvsACgkQnLXueJXo + JovXUxAAq0WKJILCUAxyhwh5tRdxJTB2NjiCLf+ggLfjyrWPtMWPi/YUt7iGPB2H + Wbv9U7l5t+54fPX8TQtBKZ79YaDMfYdjlfDSijmPruf8/MXB4G0rAaIajtCr0usZ + kJDOgmmYS7bVMybDe6guwFZappiuSS2dCEYgeJun+q7Y6IYsfvdAluJmGubQIkPT + rrEffqoQz3URmDYnAKW3sTRUVwCkYIJDxpl/W0Rvc0jmELdkHu7JYX7XvZBYSUDq + FWgzCPjyErtkKk8AqoeWtTCpL+9ozzNIXNRKjGCOL2LG4H/uuNFdM46HB+KW/7+B + wMGcpZk8T/zN9Cf348M+y+o09QX1OWavDS6LgvWJaDtG/swgxV96KKR5lEtdd1IU + JHuXfPUfGp4r378FIrbPK+Thu5bn9Yq8qGvdZOpTqDxHPU9/o9wLpJghcWJZ5O3X + MpK4HdN+bME2zgBd08QsOjANogbJIz9MVaMGRFlCO5iOiz2DxG+v2KkO8IRwGXaO + OKKQ7BD04fS2wFma862BaTtB9M9f9UTWV4e2mgRpSDJWTQKrj+HkJ63gAFQYFnfp + ppgqZLkmzH1Ta2U56JSMMfOoKVFgjuxRx1d+tzdC+TpQyo06NI1KkNMepK1rhFBW + p8hej6n/7Bl9LL/W+DKsNqW9jQbTYu66JqKs3Kg7xga6w/ss0iw= + =VG6I + -----END PGP SIGNATURE----- + +asdf +asdf +asdf +` + + sha := &Sha1Hash{0xe6, 0x69, 0x11, 0x91, 0x44, 0x14, 0xb0, 0xda, 0xa8, 0x5d, 0x4a, 0x42, 0x8c, 0x8d, 0x60, 0x7b, 0x9b, 0x24, 0x9a, 0x2c} + gitRepo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare")) + require.NoError(t, err) + assert.NotNil(t, gitRepo) + defer gitRepo.Close() + + commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString)) + require.NoError(t, err) + require.NotNil(t, commitFromReader) + assert.EqualValues(t, sha, commitFromReader.ID) + assert.Equal(t, `-----BEGIN PGP SIGNATURE----- + +iQIzBAABCAAdFiEE6vnM/NCHZAjyl8YKnLXueJXoJosFAmgwhvsACgkQnLXueJXo +JovXUxAAq0WKJILCUAxyhwh5tRdxJTB2NjiCLf+ggLfjyrWPtMWPi/YUt7iGPB2H +Wbv9U7l5t+54fPX8TQtBKZ79YaDMfYdjlfDSijmPruf8/MXB4G0rAaIajtCr0usZ +kJDOgmmYS7bVMybDe6guwFZappiuSS2dCEYgeJun+q7Y6IYsfvdAluJmGubQIkPT +rrEffqoQz3URmDYnAKW3sTRUVwCkYIJDxpl/W0Rvc0jmELdkHu7JYX7XvZBYSUDq +FWgzCPjyErtkKk8AqoeWtTCpL+9ozzNIXNRKjGCOL2LG4H/uuNFdM46HB+KW/7+B +wMGcpZk8T/zN9Cf348M+y+o09QX1OWavDS6LgvWJaDtG/swgxV96KKR5lEtdd1IU +JHuXfPUfGp4r378FIrbPK+Thu5bn9Yq8qGvdZOpTqDxHPU9/o9wLpJghcWJZ5O3X +MpK4HdN+bME2zgBd08QsOjANogbJIz9MVaMGRFlCO5iOiz2DxG+v2KkO8IRwGXaO +OKKQ7BD04fS2wFma862BaTtB9M9f9UTWV4e2mgRpSDJWTQKrj+HkJ63gAFQYFnfp +ppgqZLkmzH1Ta2U56JSMMfOoKVFgjuxRx1d+tzdC+TpQyo06NI1KkNMepK1rhFBW +p8hej6n/7Bl9LL/W+DKsNqW9jQbTYu66JqKs3Kg7xga6w/ss0iw= +=VG6I +-----END PGP SIGNATURE----- +`, commitFromReader.Signature.Signature) + assert.Equal(t, `tree a29321bf9e3ec433ed9e47b1cbbac6906c71fc60 +parent c0d83043ade7fa3ca10659608799477e9daa670b +author Sebastian Thiel 1747920681 +0200 +committer Sebastian Thiel 1748010747 +0200 +gitbutler-headers-version 2 +gitbutler-change-id 1063f7ea-d841-43b3-903a-01747681c40d + +asdf +asdf +asdf +`, commitFromReader.Signature.Payload) + assert.Equal(t, "Sebastian Thiel ", commitFromReader.Author.String()) +} + func TestHasPreviousCommit(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go index 94f1911c4a..98a1930ac2 100644 --- a/modules/git/repo_compare.go +++ b/modules/git/repo_compare.go @@ -30,6 +30,12 @@ type CompareInfo struct { NumFiles int } +// GetMergeBaseSimple returns the merge base of base and head. +func (repo *Repository) GetMergeBaseSimple(base, head string) (string, error) { + stdout, _, err := NewCommand(repo.Ctx, "merge-base").AddDashesAndList(base, head).RunStdString(&RunOpts{Dir: repo.Path}) + return strings.TrimSpace(stdout), err +} + // GetMergeBase checks and returns merge base of two branches and the reference used as base. func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, string, error) { if tmpRemote == "" { @@ -174,16 +180,36 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis return w.numLines, nil } -// GetDiffShortStat counts number of changed files, number of additions and deletions -func (repo *Repository) GetDiffShortStat(base, head string) (numFiles, totalAdditions, totalDeletions int, err error) { - numFiles, totalAdditions, totalDeletions, err = GetDiffShortStat(repo.Ctx, repo.Path, nil, base+"..."+head) - if err != nil && strings.Contains(err.Error(), "no merge base") { - return GetDiffShortStat(repo.Ctx, repo.Path, nil, base, head) +var ( + ErrNoMergebaseFound = errors.New("no merge base found") + ErrMultipleMergebasesFound = errors.New("multiple merge bases found") +) + +// GetShortStat returns the number of changed files, additions and deletions. If +// `useMergebase` is specified then the merge base between `base` and `head` is +// used to compare against `head`. +func (repo *Repository) GetShortStat(base, head string, useMergebase bool) (numFiles, totalAdditions, totalDeletions int, err error) { + cmd := NewCommand(repo.Ctx, "diff-tree", "--shortstat") + if useMergebase { + cmd = cmd.AddArguments("--merge-base") } - return numFiles, totalAdditions, totalDeletions, err + cmd.AddDynamicArguments(base, head) + + stdout, stderr, err := cmd.RunStdString(&RunOpts{Dir: repo.Path}) + if err != nil { + switch stderr { + case "fatal: no merge base found\n": + return 0, 0, 0, ErrNoMergebaseFound + case "fatal: multiple merge bases found\n": + return 0, 0, 0, ErrMultipleMergebasesFound + } + return 0, 0, 0, err + } + + return parseDiffStat(stdout) } -// GetCommitStat returns the number of files, total additions and total deletions the commit has. +// GetCommitShortStat returns the number of files, total additions and total deletions the commit has. func (repo *Repository) GetCommitShortStat(commitID string) (numFiles, totalAdditions, totalDeletions int, err error) { cmd := NewCommand(repo.Ctx, "diff-tree", "--shortstat", "--no-commit-id", "--root").AddDynamicArguments(commitID) stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path}) @@ -194,13 +220,13 @@ func (repo *Repository) GetCommitShortStat(commitID string) (numFiles, totalAddi return parseDiffStat(stdout) } -// GetDiffShortStat counts number of changed files, number of additions and deletions -func GetDiffShortStat(ctx context.Context, repoPath string, trustedArgs TrustedCmdArgs, dynamicArgs ...string) (numFiles, totalAdditions, totalDeletions int, err error) { - // Now if we call: - // $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875 - // we get: - // " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n" - cmd := NewCommand(ctx, "diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...) +// GetIndexShortStat returns the number of files, total additions and total +// deletions the commit has. +// +// NOTE: It uses `git-diff-index`, should only be used when working with +// temporary repository. When working on bare repositories use `GetCommitShortStat`. +func GetIndexShortStat(ctx context.Context, repoPath, commitID string) (numFiles, totalAdditions, totalDeletions int, err error) { + cmd := NewCommand(ctx, "diff-index", "--cached", "--shortstat").AddDynamicArguments(commitID) stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return 0, 0, 0, err diff --git a/modules/git/repo_compare_test.go b/modules/git/repo_compare_test.go index b1ebdf6177..fcdc256112 100644 --- a/modules/git/repo_compare_test.go +++ b/modules/git/repo_compare_test.go @@ -243,3 +243,87 @@ func TestGetCommitShortStat(t *testing.T) { assert.Equal(t, 0, totalDeletions) }) } + +func TestGetShortStat(t *testing.T) { + // https://github.com/git/git/blob/60f3f52f17cceefa5299709b189ce6fe2d181e7b/t/t4068-diff-symmetric-merge-base.sh#L10-L23 + repo, err := OpenRepository(t.Context(), filepath.Join(testReposDir, "symmetric_repo")) + require.NoError(t, err) + defer repo.Close() + + t.Run("Normal", func(t *testing.T) { + t.Run("Via merge base", func(t *testing.T) { + numFiles, totalAdditions, totalDeletions, err := repo.GetShortStat("br2", "main", true) + require.NoError(t, err) + assert.Equal(t, 1, numFiles) + assert.Equal(t, 1, totalAdditions) + assert.Zero(t, totalDeletions) + + numFiles, totalAdditions, totalDeletions, err = repo.GetShortStat("main", "br2", true) + require.NoError(t, err) + assert.Equal(t, 1, numFiles) + assert.Equal(t, 1, totalAdditions) + assert.Zero(t, totalDeletions) + }) + + t.Run("Direct compare", func(t *testing.T) { + numFiles, totalAdditions, totalDeletions, err := repo.GetShortStat("main", "br2", false) + require.NoError(t, err) + assert.Equal(t, 2, numFiles) + assert.Equal(t, 1, totalAdditions) + assert.Equal(t, 1, totalDeletions) + + numFiles, totalAdditions, totalDeletions, err = repo.GetShortStat("main", "br3", false) + require.NoError(t, err) + assert.Equal(t, 1, numFiles) + assert.Equal(t, 1, totalAdditions) + assert.Zero(t, totalDeletions) + + numFiles, totalAdditions, totalDeletions, err = repo.GetShortStat("br3", "main", false) + require.NoError(t, err) + assert.Equal(t, 1, numFiles) + assert.Zero(t, totalAdditions) + assert.Equal(t, 1, totalDeletions) + }) + }) + + t.Run("No merge base", func(t *testing.T) { + numFiles, totalAdditions, totalDeletions, err := repo.GetShortStat("main", "br3", true) + require.ErrorIs(t, err, ErrNoMergebaseFound) + assert.Zero(t, numFiles) + assert.Zero(t, totalAdditions) + assert.Zero(t, totalDeletions) + }) + + t.Run("Multiple merge base", func(t *testing.T) { + numFiles, totalAdditions, totalDeletions, err := repo.GetShortStat("main", "br1", true) + require.ErrorIs(t, err, ErrMultipleMergebasesFound) + assert.Zero(t, numFiles) + assert.Zero(t, totalAdditions) + assert.Zero(t, totalDeletions) + }) +} + +func TestGetMergeBaseSimple(t *testing.T) { + repo, err := OpenRepository(t.Context(), filepath.Join(testReposDir, "symmetric_repo")) + require.NoError(t, err) + + defer repo.Close() + + t.Run("Normal", func(t *testing.T) { + mergebase, err := repo.GetMergeBaseSimple("main", "br2") + require.NoError(t, err) + assert.Equal(t, "9d36f18c8ca14ad28c4751afd14f3e3146a785dc", mergebase) + }) + + t.Run("No mergebase", func(t *testing.T) { + mergebase, err := repo.GetMergeBaseSimple("main", "br3") + require.ErrorContains(t, err, "exit status 1") + assert.Empty(t, mergebase) + }) + + t.Run("Multiple mergebase", func(t *testing.T) { + mergebase, err := repo.GetMergeBaseSimple("main", "br1") + require.NoError(t, err) + assert.Equal(t, "9d36f18c8ca14ad28c4751afd14f3e3146a785dc", mergebase) + }) +} diff --git a/modules/git/tests/repos/symmetric_repo/HEAD b/modules/git/tests/repos/symmetric_repo/HEAD new file mode 100644 index 0000000000..992854f9fb --- /dev/null +++ b/modules/git/tests/repos/symmetric_repo/HEAD @@ -0,0 +1 @@ +ref: refs/heads/br3 diff --git a/modules/git/tests/repos/symmetric_repo/config b/modules/git/tests/repos/symmetric_repo/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/modules/git/tests/repos/symmetric_repo/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/modules/git/tests/repos/symmetric_repo/objects/01/fc2bb01605691ba1f40694c1da70501583a085 b/modules/git/tests/repos/symmetric_repo/objects/01/fc2bb01605691ba1f40694c1da70501583a085 new file mode 100644 index 0000000000..914d958d71 Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/01/fc2bb01605691ba1f40694c1da70501583a085 differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/02/84078471ec566623807297baf4450382542dcb b/modules/git/tests/repos/symmetric_repo/objects/02/84078471ec566623807297baf4450382542dcb new file mode 100644 index 0000000000..061fc8feec Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/02/84078471ec566623807297baf4450382542dcb differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/19/33da329284aca10dab8dc2fdd54213acd39be5 b/modules/git/tests/repos/symmetric_repo/objects/19/33da329284aca10dab8dc2fdd54213acd39be5 new file mode 100644 index 0000000000..7abecb8b4e Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/19/33da329284aca10dab8dc2fdd54213acd39be5 differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/3f/10b1d474ee0a63d008be715494586ccbbbc9da b/modules/git/tests/repos/symmetric_repo/objects/3f/10b1d474ee0a63d008be715494586ccbbbc9da new file mode 100644 index 0000000000..b54a2a79d7 Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/3f/10b1d474ee0a63d008be715494586ccbbbc9da differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/3f/151445f4d22fec48009aa692142c96bce6dcf6 b/modules/git/tests/repos/symmetric_repo/objects/3f/151445f4d22fec48009aa692142c96bce6dcf6 new file mode 100644 index 0000000000..82c5d29824 Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/3f/151445f4d22fec48009aa692142c96bce6dcf6 differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/modules/git/tests/repos/symmetric_repo/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 new file mode 100644 index 0000000000..adf64119a3 Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/54/93447336137e6fc28e3be3067f7049273c6909 b/modules/git/tests/repos/symmetric_repo/objects/54/93447336137e6fc28e3be3067f7049273c6909 new file mode 100644 index 0000000000..30092e4f7b Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/54/93447336137e6fc28e3be3067f7049273c6909 differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/61/780798228d17af2d34fce4cfbdf35556832472 b/modules/git/tests/repos/symmetric_repo/objects/61/780798228d17af2d34fce4cfbdf35556832472 new file mode 100644 index 0000000000..586bf17a49 Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/61/780798228d17af2d34fce4cfbdf35556832472 differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/6a/69f92020f5df77af6e8813ff1232493383b708 b/modules/git/tests/repos/symmetric_repo/objects/6a/69f92020f5df77af6e8813ff1232493383b708 new file mode 100644 index 0000000000..77148f56f4 Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/6a/69f92020f5df77af6e8813ff1232493383b708 differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/6b/e660545b31f61a82a87d2b1915f0b88bb9f16f b/modules/git/tests/repos/symmetric_repo/objects/6b/e660545b31f61a82a87d2b1915f0b88bb9f16f new file mode 100644 index 0000000000..c8bc9c3d9e Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/6b/e660545b31f61a82a87d2b1915f0b88bb9f16f differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/71/3fc38bac217f3909a1ca43479dec4f3f6e039e b/modules/git/tests/repos/symmetric_repo/objects/71/3fc38bac217f3909a1ca43479dec4f3f6e039e new file mode 100644 index 0000000000..fde71ec26c Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/71/3fc38bac217f3909a1ca43479dec4f3f6e039e differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/83/966f2fad7dfb1c567ca63bb68e9a7d38b4afc6 b/modules/git/tests/repos/symmetric_repo/objects/83/966f2fad7dfb1c567ca63bb68e9a7d38b4afc6 new file mode 100644 index 0000000000..50d57fceec Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/83/966f2fad7dfb1c567ca63bb68e9a7d38b4afc6 differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/9d/36f18c8ca14ad28c4751afd14f3e3146a785dc b/modules/git/tests/repos/symmetric_repo/objects/9d/36f18c8ca14ad28c4751afd14f3e3146a785dc new file mode 100644 index 0000000000..d1ecb2995f Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/9d/36f18c8ca14ad28c4751afd14f3e3146a785dc differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/bf/91e6a4737da117217f8b32276998b10ee55f65 b/modules/git/tests/repos/symmetric_repo/objects/bf/91e6a4737da117217f8b32276998b10ee55f65 new file mode 100644 index 0000000000..ad3f6a1f9b Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/bf/91e6a4737da117217f8b32276998b10ee55f65 differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/d6/dfac318c3dcd5f05554472666d7bb6dfd9e8db b/modules/git/tests/repos/symmetric_repo/objects/d6/dfac318c3dcd5f05554472666d7bb6dfd9e8db new file mode 100644 index 0000000000..75ea7eaed6 Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/d6/dfac318c3dcd5f05554472666d7bb6dfd9e8db differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/e4/2ba3e77f66f623836b47df796932f7e5604aec b/modules/git/tests/repos/symmetric_repo/objects/e4/2ba3e77f66f623836b47df796932f7e5604aec new file mode 100644 index 0000000000..dc832f465c Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/e4/2ba3e77f66f623836b47df796932f7e5604aec differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/f2/ad6c76f0115a6ba5b00456a849810e7ec0af20 b/modules/git/tests/repos/symmetric_repo/objects/f2/ad6c76f0115a6ba5b00456a849810e7ec0af20 new file mode 100644 index 0000000000..a36463115d Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/f2/ad6c76f0115a6ba5b00456a849810e7ec0af20 differ diff --git a/modules/git/tests/repos/symmetric_repo/objects/f4/e316302084c597f1678ff1db625b28f68194ae b/modules/git/tests/repos/symmetric_repo/objects/f4/e316302084c597f1678ff1db625b28f68194ae new file mode 100644 index 0000000000..2133e56d5c Binary files /dev/null and b/modules/git/tests/repos/symmetric_repo/objects/f4/e316302084c597f1678ff1db625b28f68194ae differ diff --git a/modules/git/tests/repos/symmetric_repo/packed-refs b/modules/git/tests/repos/symmetric_repo/packed-refs new file mode 100644 index 0000000000..0e61c448af --- /dev/null +++ b/modules/git/tests/repos/symmetric_repo/packed-refs @@ -0,0 +1,8 @@ +# pack-refs with: peeled fully-peeled sorted +01fc2bb01605691ba1f40694c1da70501583a085 refs/heads/br1 +5493447336137e6fc28e3be3067f7049273c6909 refs/heads/br2 +3f10b1d474ee0a63d008be715494586ccbbbc9da refs/heads/br3 +83966f2fad7dfb1c567ca63bb68e9a7d38b4afc6 refs/heads/main +bf91e6a4737da117217f8b32276998b10ee55f65 refs/tags/commit-C +^9d36f18c8ca14ad28c4751afd14f3e3146a785dc +0284078471ec566623807297baf4450382542dcb refs/tags/commit-D diff --git a/modules/git/tests/repos/symmetric_repo/refs/heads/.gitkeep b/modules/git/tests/repos/symmetric_repo/refs/heads/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/git/tests/repos/symmetric_repo/refs/tags/.gitkeep b/modules/git/tests/repos/symmetric_repo/refs/tags/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/git/url/url_test.go b/modules/git/url/url_test.go index 54655633bf..68ad8bd7c5 100644 --- a/modules/git/url/url_test.go +++ b/modules/git/url/url_test.go @@ -29,12 +29,12 @@ func TestParseGitURLs(t *testing.T) { }, }, { - kase: "git@[fe80:14fc:cec5:c174:d88%2510]:go-gitea/gitea.git", + kase: "git@[fe80::14fc:cec5:c174:d88%2510]:go-gitea/gitea.git", expected: &GitURL{ URL: &url.URL{ Scheme: "ssh", User: url.User("git"), - Host: "[fe80:14fc:cec5:c174:d88%10]", + Host: "[fe80::14fc:cec5:c174:d88%10]", Path: "go-gitea/gitea.git", }, extraMark: 1, @@ -132,11 +132,11 @@ func TestParseGitURLs(t *testing.T) { }, }, { - kase: "https://[fe80:14fc:cec5:c174:d88%2510]:20/go-gitea/gitea.git", + kase: "https://[fe80::14fc:cec5:c174:d88%2510]:20/go-gitea/gitea.git", expected: &GitURL{ URL: &url.URL{ Scheme: "https", - Host: "[fe80:14fc:cec5:c174:d88%10]:20", + Host: "[fe80::14fc:cec5:c174:d88%10]:20", Path: "/go-gitea/gitea.git", }, extraMark: 0, diff --git a/modules/lfs/main_test.go b/modules/lfs/main_test.go new file mode 100644 index 0000000000..47a0c8138a --- /dev/null +++ b/modules/lfs/main_test.go @@ -0,0 +1,37 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package lfs + +import ( + "context" + "fmt" + "os" + "testing" + + "forgejo.org/modules/git" + "forgejo.org/modules/setting" + "forgejo.org/modules/util" +) + +const testReposDir = "tests/repos/" + +func TestMain(m *testing.M) { + gitHomePath, err := os.MkdirTemp(os.TempDir(), "git-home") + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Test failed: unable to create temp dir: %v", err) + os.Exit(1) + } + + setting.Git.HomePath = gitHomePath + + if err = git.InitFull(context.Background()); err != nil { + util.RemoveAll(gitHomePath) + _, _ = fmt.Fprintf(os.Stderr, "Test failed: failed to call git.InitFull: %v", err) + os.Exit(1) + } + + exitCode := m.Run() + util.RemoveAll(gitHomePath) + os.Exit(exitCode) +} diff --git a/modules/lfs/pointer_scanner_test.go b/modules/lfs/pointer_scanner_test.go new file mode 100644 index 0000000000..a82c22e66c --- /dev/null +++ b/modules/lfs/pointer_scanner_test.go @@ -0,0 +1,65 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package lfs + +import ( + "cmp" + "path/filepath" + "slices" + "testing" + + "forgejo.org/modules/git" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSearchPointerBlobs(t *testing.T) { + repo, err := git.OpenRepository(t.Context(), filepath.Join(testReposDir, "simple-lfs")) + require.NoError(t, err) + + pointerChan := make(chan PointerBlob) + errChan := make(chan error, 1) + + go SearchPointerBlobs(t.Context(), repo, pointerChan, errChan) + + // Collect all found pointer blobs. + var pointerBlobs []PointerBlob + for pointerBlob := range pointerChan { + pointerBlobs = append(pointerBlobs, pointerBlob) + } + + // Check that no errors were reported. + errChanClosed := false + select { + case err, ok := <-errChan: + if ok { + require.NoError(t, err) + } else { + errChanClosed = true + } + default: + } + assert.True(t, errChanClosed) + + // Sort them, they might arrive in any order + slices.SortFunc(pointerBlobs, func(a, b PointerBlob) int { + return cmp.Compare(a.Oid, b.Oid) + }) + + // Assert the values of the found pointer blobs. + if assert.Len(t, pointerBlobs, 3) { + assert.Equal(t, "31b9a6a709729b8ae48bde8176caf2990c0d7121", pointerBlobs[0].Hash) + assert.Equal(t, "2f91b6326743db344ca96b1be86e3ed34abf04262255b4d04db8a961a2a72545", pointerBlobs[0].Oid) + assert.EqualValues(t, 43789, pointerBlobs[0].Size) + + assert.Equal(t, "b53619fbbc3d2bfa85a26787238264cdbf551f19", pointerBlobs[1].Hash) + assert.Equal(t, "36dae031efb96625cda973c11508617b750665933a36bd52dfcfef586c4fd85c", pointerBlobs[1].Oid) + assert.EqualValues(t, 101800, pointerBlobs[1].Size) + + assert.Equal(t, "513d4c000f63ee9fd6a805e9a518206b860ce38a", pointerBlobs[2].Hash) + assert.Equal(t, "4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393", pointerBlobs[2].Oid) + assert.EqualValues(t, 1234, pointerBlobs[2].Size) + } +} diff --git a/modules/lfs/tests/repos/simple-lfs/HEAD b/modules/lfs/tests/repos/simple-lfs/HEAD new file mode 100644 index 0000000000..b870d82622 --- /dev/null +++ b/modules/lfs/tests/repos/simple-lfs/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/modules/lfs/tests/repos/simple-lfs/config b/modules/lfs/tests/repos/simple-lfs/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/modules/lfs/tests/repos/simple-lfs/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/modules/lfs/tests/repos/simple-lfs/info/exclude b/modules/lfs/tests/repos/simple-lfs/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/modules/lfs/tests/repos/simple-lfs/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/modules/lfs/tests/repos/simple-lfs/logs/HEAD b/modules/lfs/tests/repos/simple-lfs/logs/HEAD new file mode 100644 index 0000000000..18126e38d6 --- /dev/null +++ b/modules/lfs/tests/repos/simple-lfs/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 ece720b78753aca2c27fd69c42f5578f5bcfbe0e Gusted 1759469736 +0200 diff --git a/modules/lfs/tests/repos/simple-lfs/objects/31/b9a6a709729b8ae48bde8176caf2990c0d7121 b/modules/lfs/tests/repos/simple-lfs/objects/31/b9a6a709729b8ae48bde8176caf2990c0d7121 new file mode 100644 index 0000000000..c95ecbafdc Binary files /dev/null and b/modules/lfs/tests/repos/simple-lfs/objects/31/b9a6a709729b8ae48bde8176caf2990c0d7121 differ diff --git a/modules/lfs/tests/repos/simple-lfs/objects/33/9e3c3ddbf4cf6655ed335eb820f4f2912d8063 b/modules/lfs/tests/repos/simple-lfs/objects/33/9e3c3ddbf4cf6655ed335eb820f4f2912d8063 new file mode 100644 index 0000000000..dd7eada553 Binary files /dev/null and b/modules/lfs/tests/repos/simple-lfs/objects/33/9e3c3ddbf4cf6655ed335eb820f4f2912d8063 differ diff --git a/modules/lfs/tests/repos/simple-lfs/objects/51/3d4c000f63ee9fd6a805e9a518206b860ce38a b/modules/lfs/tests/repos/simple-lfs/objects/51/3d4c000f63ee9fd6a805e9a518206b860ce38a new file mode 100644 index 0000000000..1bd67e2a35 Binary files /dev/null and b/modules/lfs/tests/repos/simple-lfs/objects/51/3d4c000f63ee9fd6a805e9a518206b860ce38a differ diff --git a/modules/lfs/tests/repos/simple-lfs/objects/72/78f80aa39386476ffbdd79083808887f99fbc8 b/modules/lfs/tests/repos/simple-lfs/objects/72/78f80aa39386476ffbdd79083808887f99fbc8 new file mode 100644 index 0000000000..60d37fc752 Binary files /dev/null and b/modules/lfs/tests/repos/simple-lfs/objects/72/78f80aa39386476ffbdd79083808887f99fbc8 differ diff --git a/modules/lfs/tests/repos/simple-lfs/objects/8d/ebd861c03718cc43154f5317308b88ab5447d2 b/modules/lfs/tests/repos/simple-lfs/objects/8d/ebd861c03718cc43154f5317308b88ab5447d2 new file mode 100644 index 0000000000..22ce593d43 Binary files /dev/null and b/modules/lfs/tests/repos/simple-lfs/objects/8d/ebd861c03718cc43154f5317308b88ab5447d2 differ diff --git a/modules/lfs/tests/repos/simple-lfs/objects/9b/af9f95266ceb6043894591f73d97c7ef4cb546 b/modules/lfs/tests/repos/simple-lfs/objects/9b/af9f95266ceb6043894591f73d97c7ef4cb546 new file mode 100644 index 0000000000..e9dd5226ce Binary files /dev/null and b/modules/lfs/tests/repos/simple-lfs/objects/9b/af9f95266ceb6043894591f73d97c7ef4cb546 differ diff --git a/modules/lfs/tests/repos/simple-lfs/objects/b5/3619fbbc3d2bfa85a26787238264cdbf551f19 b/modules/lfs/tests/repos/simple-lfs/objects/b5/3619fbbc3d2bfa85a26787238264cdbf551f19 new file mode 100644 index 0000000000..33bd13e08f Binary files /dev/null and b/modules/lfs/tests/repos/simple-lfs/objects/b5/3619fbbc3d2bfa85a26787238264cdbf551f19 differ diff --git a/modules/lfs/tests/repos/simple-lfs/objects/c7/5c2ef6293177b3bcd7f23f5f49e2727fe7dbb6 b/modules/lfs/tests/repos/simple-lfs/objects/c7/5c2ef6293177b3bcd7f23f5f49e2727fe7dbb6 new file mode 100644 index 0000000000..e0c154b400 Binary files /dev/null and b/modules/lfs/tests/repos/simple-lfs/objects/c7/5c2ef6293177b3bcd7f23f5f49e2727fe7dbb6 differ diff --git a/modules/lfs/tests/repos/simple-lfs/objects/eb/e522c4e7b48a1d939281654589366a63120444 b/modules/lfs/tests/repos/simple-lfs/objects/eb/e522c4e7b48a1d939281654589366a63120444 new file mode 100644 index 0000000000..fa39fd57c1 Binary files /dev/null and b/modules/lfs/tests/repos/simple-lfs/objects/eb/e522c4e7b48a1d939281654589366a63120444 differ diff --git a/modules/lfs/tests/repos/simple-lfs/objects/ec/e720b78753aca2c27fd69c42f5578f5bcfbe0e b/modules/lfs/tests/repos/simple-lfs/objects/ec/e720b78753aca2c27fd69c42f5578f5bcfbe0e new file mode 100644 index 0000000000..e23129ab0d Binary files /dev/null and b/modules/lfs/tests/repos/simple-lfs/objects/ec/e720b78753aca2c27fd69c42f5578f5bcfbe0e differ diff --git a/modules/lfs/tests/repos/simple-lfs/packed-refs b/modules/lfs/tests/repos/simple-lfs/packed-refs new file mode 100644 index 0000000000..08e7d087a5 --- /dev/null +++ b/modules/lfs/tests/repos/simple-lfs/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +ece720b78753aca2c27fd69c42f5578f5bcfbe0e refs/heads/main diff --git a/modules/lfs/tests/repos/simple-lfs/refs/heads/.gitkeep b/modules/lfs/tests/repos/simple-lfs/refs/heads/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/lfs/tests/repos/simple-lfs/refs/tags/.gitkeep b/modules/lfs/tests/repos/simple-lfs/refs/tags/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/markup/html.go b/modules/markup/html.go index 83ce87aea4..2fe0caa5b6 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -55,10 +55,13 @@ var ( shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`) // anyHashPattern splits url containing SHA into parts - anyHashPattern = regexp.MustCompile(`https?://(?:(?:\S+/){3,4}(?:commit|tree|blob)/)([0-9a-f]{7,64})(/[-+~_%.a-zA-Z0-9/]+)?(\?[-+~_%\.a-zA-Z0-9=&]+)?(#[-+~_%.a-zA-Z0-9]+)?`) + anyHashPattern = regexp.MustCompile(`https?://[^\s/]+/(\S+/(?:commit|tree|blob))/([0-9a-f]{7,64})(/[-+~_%.a-zA-Z0-9/]+)?(\?[-+~_%\.a-zA-Z0-9=&]+)?(#[-+~_%.a-zA-Z0-9]+)?`) // comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash" - comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?`) + comparePattern = regexp.MustCompile(`https?://[^\s/]+/(?:\S+/)?([^\s/]+/[^\s/]+)/compare/([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(\?[-+~_%\.a-zA-Z0-9=&/]+)?(#[-+~_%.a-zA-Z0-9]+)?`) + + // pullReviewCommitPattern matches "https://domain.tld////pulls//commits/" + pullReviewCommitPattern = regexp.MustCompile(`https?://[^\s/]+/(?:\S+/)?([^\s/]+/[^\s/]+)/pulls/(\d+)/commits/([0-9a-f]{7,64})(#[-+~_%.a-zA-Z0-9]+)?`) validLinksPattern = regexp.MustCompile(`^[a-z][\w-]+://`) @@ -147,6 +150,7 @@ func (p *postProcessError) Error() string { type processor func(ctx *RenderContext, node *html.Node) var defaultProcessors = []processor{ + pullReviewCommitPatternProcessor, fullIssuePatternProcessor, comparePatternProcessor, filePreviewPatternProcessor, @@ -177,6 +181,7 @@ func PostProcess( } var commitMessageProcessors = []processor{ + pullReviewCommitPatternProcessor, fullIssuePatternProcessor, comparePatternProcessor, fullHashPatternProcessor, @@ -209,6 +214,7 @@ func RenderCommitMessage( } var commitMessageSubjectProcessors = []processor{ + pullReviewCommitPatternProcessor, fullIssuePatternProcessor, comparePatternProcessor, fullHashPatternProcessor, @@ -796,6 +802,64 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { } } +// pullReviewCommitPatternProcessor creates links to pull review commits. +func pullReviewCommitPatternProcessor(ctx *RenderContext, node *html.Node) { + next := node.NextSibling + for node != nil && node != next { + m := pullReviewCommitPattern.FindStringSubmatchIndex(node.Data) + if m == nil { + return + } + + urlFull := node.Data[m[0]:m[1]] + repoSlug := node.Data[m[2]:m[3]] + id := node.Data[m[4]:m[5]] + sha := base.ShortSha(node.Data[m[6]:m[7]]) + + // Create an `` node with a text of + // `!123 (commit abcdef1234)` + aNode := &html.Node{ + Type: html.ElementNode, + Data: atom.A.String(), + Attr: []html.Attribute{{Key: "href", Val: urlFull}, {Key: "class", Val: "commit"}}, + } + + text := "!" + id + " (commit " + + baseURLEnd := strings.Index(urlFull, repoSlug) + len(repoSlug) + if len(ctx.Links.Base) > 0 && !strings.HasPrefix(ctx.Links.Base, urlFull[:baseURLEnd]) { + text = repoSlug + "@" + text + } + + aNode.AppendChild(&html.Node{ + Type: html.TextNode, + Data: text, + }) + + textNode := &html.Node{ + Type: html.TextNode, + Data: sha, + } + + codeNode := &html.Node{ + Type: html.ElementNode, + Data: atom.Code.String(), + Attr: []html.Attribute{{Key: "class", Val: "nohighlight"}}, + } + + codeNode.AppendChild(textNode) + aNode.AppendChild(codeNode) + + aNode.AppendChild(&html.Node{ + Type: html.TextNode, + Data: ")", + }) + + replaceContent(node, m[0], m[1], aNode) + node = node.NextSibling.NextSibling + } +} + func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) { if ctx.Metas == nil { return @@ -952,7 +1016,7 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) { } } -// fullHashPatternProcessor renders SHA containing URLs +// fullHashPatternProcessor renders URLs that contain a SHA func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) { if ctx.Metas == nil { return @@ -966,37 +1030,103 @@ func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) { } urlFull := node.Data[m[0]:m[1]] - text := base.ShortSha(node.Data[m[2]:m[3]]) - // 3rd capture group matches a optional path - subpath := "" - if m[5] > 0 { - subpath = node.Data[m[4]:m[5]] + // In most cases, the URL will look like this: + // `https://domain.tld////`. + // The amount of components in `` is variable, but that alone is doable with regexp. + // + // However, Forgejo also allows being hosted on a sub path, i.e. + // `https://domain.tld/////`. + // And this sub path can also have any amount of components. But fishing out a section + // between two variable length matches is not something regular grammars are capable of. + // + // Instead, the regexp extracts the entire path section before the SHA + // (i.e. `///`), and we find the components we need by counting. + // `` is unknown, but the possible values for `` are defined by us + // (see `router/web/web.go`). So we count from the back. + subPath := node.Data[m[2]:m[3]] + + components := strings.Split(subPath, "/") + componentCount := len(components) + + // In most cases, the `` component is right at the start of the path. + ownerIndex := 0 + + // But if there are more than three components, this could be `` or an app route + // with two components. Or both. + if componentCount > 3 { + // As mentioned, we count from the back. We decrement for the `` component, and the one + // component from the app route that's guaranteed to be there. + // We also adjust this to be an array index, so we subtract one more. + ownerIndex = componentCount - 3 + + // We then check for known app routes that use two components. + // Currently, this checks for: + // - `src/commit` + // - `commits/commit` + // + // This does have one scenario where we cannot figure things out reliably: + // If there is a sub path, and the repository is named like one of the known app routes + // (e.g. `src`), we cannot distinguish between the repo and the app route. + // We assume that naming a repository like that is uncommon, and prioritize the case where its + // part of the app route. + if components[componentCount-1] == "commit" && + (components[componentCount-2] == "src" || components[componentCount-2] == "commits") { + ownerIndex-- + } + } + + repoSlug := components[ownerIndex] + "/" + components[ownerIndex+1] + + text := base.ShortSha(node.Data[m[4]:m[5]]) + + // We need to figure out the base of the provided URL, which is up to and including the + // `/` slug. + // With that we can determine if it matches the current repo, or if the slug should be shown. + baseURLEnd := strings.Index(urlFull, repoSlug) + len(repoSlug) + if len(ctx.Links.Base) > 0 && !strings.HasPrefix(ctx.Links.Base, urlFull[:baseURLEnd]) { + text = repoSlug + "@" + text + } + + // 3rd capture group matches an optional file path after the SHA + filePath := "" + if m[7] > 0 { + filePath = node.Data[m[6]:m[7]] } // 5th capture group matches a optional url hash hash := "" - if m[9] > 0 { - hash = node.Data[m[8]:m[9]][1:] + if m[11] > 0 { + hash = node.Data[m[10]:m[11]][1:] + + // Truncate long diff IDs + if len(hash) > 15 && strings.HasPrefix(hash, "diff-") { + hash = hash[:15] + } } start := m[0] end := m[1] - // If url ends in '.', it's very likely that it is not part of the - // actual url but used to finish a sentence. + // If the URL ends in '.', it's very likely that it is not part of the + // actual URL but used to finish a sentence. if strings.HasSuffix(urlFull, ".") { end-- urlFull = urlFull[:len(urlFull)-1] if hash != "" { hash = hash[:len(hash)-1] - } else if subpath != "" { - subpath = subpath[:len(subpath)-1] + } else if filePath != "" { + filePath = filePath[:len(filePath)-1] } } - if subpath != "" { - text += subpath + if filePath != "" { + decoded, err := url.QueryUnescape(filePath) + if err != nil { + text += decoded + } else { + text += filePath + } } if hash != "" { @@ -1019,41 +1149,71 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) { return } - // Ensure that every group (m[0]...m[7]) has a match - for i := 0; i < 8; i++ { + // Ensure that every group (m[0]...m[9]) has a match + for i := 0; i < 10; i++ { if m[i] == -1 { return } } urlFull := node.Data[m[0]:m[1]] - text1 := base.ShortSha(node.Data[m[2]:m[3]]) - textDots := base.ShortSha(node.Data[m[4]:m[5]]) - text2 := base.ShortSha(node.Data[m[6]:m[7]]) + repoSlug := node.Data[m[2]:m[3]] + text1 := base.ShortSha(node.Data[m[4]:m[5]]) + textDots := base.ShortSha(node.Data[m[6]:m[7]]) + text2 := base.ShortSha(node.Data[m[8]:m[9]]) + + query := "" + if m[11] > 0 { + query = node.Data[m[10]:m[11]][1:] + } hash := "" - if m[9] > 0 { - hash = node.Data[m[8]:m[9]][1:] + if m[13] > 0 { + hash = node.Data[m[12]:m[13]][1:] } start := m[0] end := m[1] - // If url ends in '.', it's very likely that it is not part of the - // actual url but used to finish a sentence. + // If the URL ends in '.', it's very likely that it is not part of the + // actual URL but used to finish a sentence. if strings.HasSuffix(urlFull, ".") { end-- urlFull = urlFull[:len(urlFull)-1] if hash != "" { hash = hash[:len(hash)-1] + } else if query != "" { + query = query[:len(query)-1] } else if text2 != "" { text2 = text2[:len(text2)-1] } } text := text1 + textDots + text2 + + baseURLEnd := strings.Index(urlFull, repoSlug) + len(repoSlug) + if len(ctx.Links.Base) > 0 && !strings.HasPrefix(ctx.Links.Base, urlFull[:baseURLEnd]) { + text = repoSlug + "@" + text + } + + extra := "" + if query != "" { + query, err := url.ParseQuery(query) + if err == nil && query.Has("files") { + extra = query.Get("files") + } + } + if hash != "" { - text += " (" + hash + ")" + if extra != "" { + extra += "#" + } + + extra += hash + } + + if extra != "" { + text += " (" + extra + ")" } replaceContent(node, start, end, createCodeLink(urlFull, text, "compare")) node = node.NextSibling.NextSibling @@ -1094,7 +1254,7 @@ func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) { // Specialized version of replaceContent, so the parent paragraph element is not destroyed from our div before := node.Data[:(preview.start - offset)] after := node.Data[(preview.end - offset):] - afterNode := &html.Node{ + afterTextNode := &html.Node{ Type: html.TextNode, Data: after, } @@ -1103,22 +1263,20 @@ func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) { case "div", "li", "td", "th", "details": nextSibling := node.NextSibling node.Parent.InsertBefore(previewNode, nextSibling) - node.Parent.InsertBefore(afterNode, nextSibling) + node.Parent.InsertBefore(afterTextNode, nextSibling) case "p", "span", "em", "strong": - nextSibling := node.Parent.NextSibling - node.Parent.Parent.InsertBefore(previewNode, nextSibling) - afterPNode := &html.Node{ + nextParentSibling := node.Parent.NextSibling + node.Parent.Parent.InsertBefore(previewNode, nextParentSibling) + afterNode := &html.Node{ Type: html.ElementNode, Data: node.Parent.Data, Attr: node.Parent.Attr, } - afterPNode.AppendChild(afterNode) - node.Parent.Parent.InsertBefore(afterPNode, nextSibling) - siblingNode := node.NextSibling - if siblingNode != nil { - node.NextSibling = nil - siblingNode.PrevSibling = nil - afterPNode.AppendChild(siblingNode) + afterNode.AppendChild(afterTextNode) + node.Parent.Parent.InsertBefore(afterNode, nextParentSibling) + for sibling := node.NextSibling; sibling != nil; sibling = node.NextSibling { + sibling.Parent.RemoveChild(sibling) + afterNode.AppendChild(sibling) } default: matched = false @@ -1126,7 +1284,7 @@ func filePreviewPatternProcessor(ctx *RenderContext, node *html.Node) { if matched { offset = preview.end node.Data = before - node = afterNode + node = afterTextNode } } node = node.NextSibling diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 08b1fed505..1f717a78ed 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -303,12 +303,12 @@ func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *Rend func TestRender_AutoLink(t *testing.T) { setting.AppURL = TestAppURL - test := func(input, expected string) { + test := func(input, expected, base string) { var buffer strings.Builder err := PostProcess(&RenderContext{ Ctx: git.DefaultContext, Links: Links{ - Base: TestRepoURL, + Base: base, }, Metas: localMetas, }, strings.NewReader(input), &buffer) @@ -319,7 +319,7 @@ func TestRender_AutoLink(t *testing.T) { err = PostProcess(&RenderContext{ Ctx: git.DefaultContext, Links: Links{ - Base: TestRepoURL, + Base: base, }, Metas: localMetas, IsWiki: true, @@ -328,19 +328,87 @@ func TestRender_AutoLink(t *testing.T) { assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) } - // render valid issue URLs - test(util.URLJoin(TestRepoURL, "issues", "3333"), - numericIssueLink(util.URLJoin(TestRepoURL, "issues"), "ref-issue", 3333, "#")) + t.Run("Issue", func(t *testing.T) { + // render valid issue URLs + test(util.URLJoin(TestRepoURL, "issues", "3333"), + numericIssueLink(util.URLJoin(TestRepoURL, "issues"), "ref-issue", 3333, "#"), + TestRepoURL) + }) - // render valid commit URLs - tmp := util.URLJoin(TestRepoURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae") - test(tmp, "d8a994ef24") - tmp += "#diff-2" - test(tmp, "d8a994ef24 (diff-2)") + t.Run("Commit", func(t *testing.T) { + // render valid commit URLs + tmp := util.URLJoin(TestRepoURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae") + test(tmp, "d8a994ef24", TestRepoURL) + test(tmp, ""+TestOrgRepo+"@d8a994ef24", "https://localhost/forgejo/forgejo") + test( + tmp+"#diff-2", + "d8a994ef24 (diff-2)", + TestRepoURL, + ) + test( + tmp+"#diff-953bb4f01b7c77fa18f0cd54211255051e647dbc", + "d8a994ef24 (diff-953bb4f01b)", + TestRepoURL, + ) - // render other commit URLs - tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2" - test(tmp, "d8a994ef24 (diff-2)") + // render other commit URLs + tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2" + test(tmp, "d8a994ef24 (diff-2)", "https://external-link.gitea.io/go-gitea/gitea") + test(tmp, "go-gitea/gitea@d8a994ef24 (diff-2)", TestRepoURL) + + tmp = "http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" + test(tmp, "190d949293", "http://localhost:3000/gogits/gogs") + test(tmp, "gogits/gogs@190d949293", "https://external-link.gitea.io/go-gitea/gitea") + + tmp = "http://localhost:3000/sub/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" + test(tmp, "190d949293", "http://localhost:3000/sub/gogits/gogs") + test(tmp, "gogits/gogs@190d949293", "http://localhost:3000/gogits/gogs") + test(tmp, "gogits/gogs@190d949293", "https://external-link.gitea.io/go-gitea/gitea") + + tmp = "http://localhost:3000/sub1/sub2/sub3/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" + test(tmp, "190d949293", "http://localhost:3000/sub1/sub2/sub3/gogits/gogs") + test(tmp, "gogits/gogs@190d949293", "http://localhost:3000/sub1/gogits/gogs") + test(tmp, "gogits/gogs@190d949293", "https://external-link.gitea.io/go-gitea/gitea") + + // if the repository happens to be named like one of the known app routes (e.g. `src`), + // we can parse the URL correctly, if there is no sub path + tmp = "http://localhost:3000/gogits/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" + test(tmp, "gogits/src@190d949293", TestRepoURL) + tmp = "http://localhost:3000/gogits/src/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" + test(tmp, "gogits/src@190d949293", TestRepoURL) + // but if there is a sub path, we cannot reliably distinguish the repo name from the app route + tmp = "http://localhost:3000/sub/gogits/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" + test(tmp, "sub/gogits@190d949293", TestRepoURL) + }) + + t.Run("Compare", func(t *testing.T) { + tmp := util.URLJoin(TestRepoURL, "compare", "d8a994ef243349f321568f9e36d5c3f444b99cae..190d9492934af498c3f669d6a2431dc5459e5b20") + test(tmp, "d8a994ef24..190d949293", TestRepoURL) + test(tmp, ""+TestOrgRepo+"@d8a994ef24..190d949293", "https://localhost/forgejo/forgejo") + + tmp = "http://localhost:3000/sub/gogits/gogs/compare/190d9492934af498c3f669d6a2431dc5459e5b20..d8a994ef243349f321568f9e36d5c3f444b99cae" + test(tmp, "190d949293..d8a994ef24", "http://localhost:3000/sub/gogits/gogs") + test(tmp, "gogits/gogs@190d949293..d8a994ef24", "http://localhost:3000/gogits/gogs") + test(tmp, "gogits/gogs@190d949293..d8a994ef24", "https://external-link.gitea.io/go-gitea/gitea") + + tmp = "http://localhost:3000/sub1/sub2/sub3/gogits/gogs/compare/190d9492934af498c3f669d6a2431dc5459e5b20..d8a994ef243349f321568f9e36d5c3f444b99cae" + test(tmp, "190d949293..d8a994ef24", "http://localhost:3000/sub1/sub2/sub3/gogits/gogs") + test(tmp, "gogits/gogs@190d949293..d8a994ef24", "http://localhost:3000/sub1/gogits/gogs") + test(tmp, "gogits/gogs@190d949293..d8a994ef24", "https://external-link.gitea.io/go-gitea/gitea") + + tmp = "https://codeberg.org/forgejo/forgejo/compare/8bbac4c679bea930c74849c355a60ed3c52f8eb5...e2278e5a38187a1dc84dc41d583ec8b44e7257c1?files=options/locale/locale_fi-FI.ini" + test(tmp, "8bbac4c679...e2278e5a38 (options/locale/locale_fi-FI.ini)", "https://codeberg.org/forgejo/forgejo") + test(tmp, "forgejo/forgejo@8bbac4c679...e2278e5a38 (options/locale/locale_fi-FI.ini)", TestRepoURL) + test(tmp+".", "forgejo/forgejo@8bbac4c679...e2278e5a38 (options/locale/locale_fi-FI.ini).", TestRepoURL) + + tmp = "https://codeberg.org/forgejo/forgejo/compare/8bbac4c679bea930c74849c355a60ed3c52f8eb5...e2278e5a38187a1dc84dc41d583ec8b44e7257c1?files=options/locale/locale_fi-FI.ini#L2" + test(tmp, "8bbac4c679...e2278e5a38 (options/locale/locale_fi-FI.ini#L2)", "https://codeberg.org/forgejo/forgejo") + }) + + t.Run("Invalid URLs", func(t *testing.T) { + tmp := "https://local host/gogits/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20" + test(tmp, "https://local host/gogits/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20", TestRepoURL) + }) } func TestRender_IssueIndexPatternRef(t *testing.T) { @@ -429,45 +497,79 @@ func TestRegExp_hashCurrentPattern(t *testing.T) { func TestRegExp_anySHA1Pattern(t *testing.T) { testCases := map[string][]string{ "https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js#L2703": { + "jquery/jquery/blob", "a644101ed04d0beacea864ce805e0c4f86ba1cd1", "/test/unit/event.js", "", "#L2703", }, "https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js": { + "jquery/jquery/blob", "a644101ed04d0beacea864ce805e0c4f86ba1cd1", "/test/unit/event.js", "", "", }, "https://github.com/jquery/jquery/commit/0705be475092aede1eddae01319ec931fb9c65fc": { + "jquery/jquery/commit", "0705be475092aede1eddae01319ec931fb9c65fc", "", "", "", }, "https://github.com/jquery/jquery/tree/0705be475092aede1eddae01319ec931fb9c65fc/src": { + "jquery/jquery/tree", "0705be475092aede1eddae01319ec931fb9c65fc", "/src", "", "", }, "https://try.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2": { + "gogs/gogs/commit", "d8a994ef243349f321568f9e36d5c3f444b99cae", "", "", "#diff-2", }, "https://codeberg.org/forgejo/forgejo/src/commit/949ab9a5c4cac742f84ae5a9fa186f8d6eb2cdc0/RELEASE-NOTES.md?display=source&w=1#L7-L9": { + "forgejo/forgejo/src/commit", "949ab9a5c4cac742f84ae5a9fa186f8d6eb2cdc0", "/RELEASE-NOTES.md", "?display=source&w=1", "#L7-L9", }, + "http://localhost:3000/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3": { + "gogits/gogs/src/commit", + "190d9492934af498c3f669d6a2431dc5459e5b20", + "/path/to/file.go", + "", + "#L2-L3", + }, + "http://localhost:3000/sub/gogits/gogs/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3": { + "sub/gogits/gogs/commit", + "190d9492934af498c3f669d6a2431dc5459e5b20", + "/path/to/file.go", + "", + "#L2-L3", + }, + "http://localhost:3000/sub/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3": { + "sub/gogits/gogs/src/commit", + "190d9492934af498c3f669d6a2431dc5459e5b20", + "/path/to/file.go", + "", + "#L2-L3", + }, + "http://localhost:3000/sub1/sub2/sub3/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20/path/to/file.go#L2-L3": { + "sub1/sub2/sub3/gogits/gogs/src/commit", + "190d9492934af498c3f669d6a2431dc5459e5b20", + "/path/to/file.go", + "", + "#L2-L3", + }, } for k, v := range testCases { - assert.Equal(t, anyHashPattern.FindStringSubmatch(k)[1:], v) + assert.Equal(t, v, anyHashPattern.FindStringSubmatch(k)[1:]) } for _, v := range []string{"https://codeberg.org/forgejo/forgejo/attachments/774421a1-b0ae-4501-8fba-983874b76811"} { diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index fa3855ed34..c7c53e2678 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -241,6 +241,45 @@ func TestRender_links(t *testing.T) { markup.CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes) } +func TestRender_PullReviewCommitLink(t *testing.T) { + setting.AppURL = markup.TestAppURL + + sha := "190d9492934af498c3f669d6a2431dc5459e5b20" + prCommitLink := util.URLJoin(markup.TestRepoURL, "pulls", "1", "commits", sha) + + test := func(input, expected, base string) { + buffer, err := markup.RenderString(&markup.RenderContext{ + Ctx: git.DefaultContext, + RelativePath: ".md", + Links: markup.Links{ + AbsolutePrefix: true, + Base: base, + }, + Metas: localMetas, + }, input) + require.NoError(t, err) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) + } + + test(prCommitLink, `

!1 (commit `+sha[0:10]+`)

`, markup.TestRepoURL) + + prCommitLink = util.URLJoin(markup.TestAppURL, "sub1", "sub2", markup.TestOrgRepo, "pulls", "1", "commits", sha) + test( + prCommitLink, + `

!1 (commit `+sha[0:10]+`)

`, + util.URLJoin(markup.TestAppURL, "sub1", "sub2", markup.TestOrgRepo), + ) + test( + prCommitLink, + `

`+markup.TestOrgRepo+`@!1 (commit `+sha[0:10]+`)

`, + markup.TestRepoURL, + ) + + prCommitLink = "https://codeberg.org/forgejo/forgejo/pulls/7979/commits/4d968c08e0a8d24bd2f3fb2a3a48b37e6d84a327#diff-7649acfa98a9ee3faf0d28b488bbff428317fc72" + test(prCommitLink, `

!7979 (commit 4d968c08e0)

`, "https://codeberg.org/forgejo/forgejo") + test(prCommitLink, `

forgejo/forgejo@!7979 (commit 4d968c08e0)

`, markup.TestRepoURL) +} + func TestRender_email(t *testing.T) { setting.AppURL = markup.TestAppURL @@ -686,6 +725,9 @@ func TestIssue18471(t *testing.T) { err := markup.PostProcess(&markup.RenderContext{ Ctx: git.DefaultContext, Metas: localMetas, + Links: markup.Links{ + Base: "http://domain/org/repo", + }, }, strings.NewReader(data), &res) require.NoError(t, err) @@ -723,6 +765,9 @@ func TestRender_FilePreview(t *testing.T) { Ctx: git.DefaultContext, RelativePath: ".md", Metas: metas, + Links: markup.Links{ + Base: markup.TestRepoURL, + }, }, input) require.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -835,7 +880,7 @@ func TestRender_FilePreview(t *testing.T) { testRender( urlWithSub, - `

190d949293/path/to/file.go (L2-L3)

`, + `

gogits/gogs@190d949293/path/to/file.go (L2-L3)

`, localMetas, ) @@ -1253,4 +1298,20 @@ func TestRender_FilePreview(t *testing.T) { localMetas, ) }) + + t.Run("file previews followed by new line", func(t *testing.T) { + testRender( + commitFileURLFirstLine+"\nand\n"+commitFileURLFirstLine, + "

"+filePreviewBox+"


\nand
\n

"+filePreviewBox+"

", + localMetas, + ) + }) + + t.Run("file previews followed by new line in div", func(t *testing.T) { + testRender( + "
"+commitFileURLFirstLine+"\nand\n"+commitFileURLFirstLine+"
", + "
"+filePreviewBox+"\nand\n"+filePreviewBox+"
", + localMetas, + ) + }) } diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index 08502e12ab..05dd512815 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -17,6 +17,7 @@ import ( "forgejo.org/modules/git" "forgejo.org/modules/setting" "forgejo.org/modules/util" + "forgejo.org/modules/util/donotpanic" "github.com/yuin/goldmark/ast" ) @@ -267,6 +268,15 @@ sandbox="allow-scripts" return err } +func postProcessOrCopy(ctx *RenderContext, renderer Renderer, reader io.Reader, writer io.Writer) (err error) { + if r, ok := renderer.(PostProcessRenderer); ok && r.NeedPostProcess() { + err = PostProcess(ctx, reader, writer) + } else { + _, err = io.Copy(writer, reader) + } + return err +} + func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error { var wg sync.WaitGroup var err error @@ -293,7 +303,7 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr wg.Add(1) go func() { - err = SanitizeReader(pr2, renderer.Name(), output) + err = donotpanic.SafeFuncWithError(func() error { return SanitizeReader(pr2, renderer.Name(), output) }) _ = pr2.Close() wg.Done() }() @@ -303,11 +313,7 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr wg.Add(1) go func() { - if r, ok := renderer.(PostProcessRenderer); ok && r.NeedPostProcess() { - err = PostProcess(ctx, pr, pw2) - } else { - _, err = io.Copy(pw2, pr) - } + err = donotpanic.SafeFuncWithError(func() error { return postProcessOrCopy(ctx, renderer, pr, pw2) }) _ = pr.Close() _ = pw2.Close() wg.Done() @@ -320,7 +326,7 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr if r, ok := renderer.(ExternalRenderer); ok && r.DisplayInIFrame() { // Append a short script to the iframe's contents, which will communicate the scroll height of the embedded document via postMessage, either once loaded (in case the containing page loads first) in response to a postMessage from external.js, in case the iframe loads first // We use '*' as a target origin for postMessage, because can be certain we are embedded on the same domain, due to X-Frame-Options configured elsewhere. (Plus, the offsetHeight of an embedded document is likely not sensitive data anyway.) - _, _ = pw.Write([]byte("")) + _, _ = pw.Write([]byte("")) } _ = pw.Close() diff --git a/modules/markup/renderer_test.go b/modules/markup/renderer_test.go index 0791081f94..13890d4361 100644 --- a/modules/markup/renderer_test.go +++ b/modules/markup/renderer_test.go @@ -1,4 +1,49 @@ -// Copyright 2017 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 -package markup_test +package markup + +import ( + "bytes" + "errors" + "strings" + "testing" + + "forgejo.org/modules/test" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type failReader struct{} + +func (*failReader) Read(p []byte) (n int, err error) { + return 0, errors.New("FAIL") +} + +func TestRender_postProcessOrCopy(t *testing.T) { + renderContext := &RenderContext{Ctx: t.Context()} + + t.Run("CopyOK", func(t *testing.T) { + input := "SOMETHING" + output := &bytes.Buffer{} + require.NoError(t, postProcessOrCopy(renderContext, nil, strings.NewReader(input), output)) + assert.Equal(t, input, output.String()) + }) + + renderer := GetRendererByType("markdown") + + t.Run("PostProcessOK", func(t *testing.T) { + input := "SOMETHING" + output := &bytes.Buffer{} + defer test.MockVariableValue(&defaultProcessors, []processor{})() + require.NoError(t, postProcessOrCopy(renderContext, renderer, strings.NewReader(input), output)) + assert.Equal(t, input, output.String()) + }) + + t.Run("PostProcessError", func(t *testing.T) { + input := &failReader{} + defer test.MockVariableValue(&defaultProcessors, []processor{})() + assert.ErrorContains(t, postProcessOrCopy(renderContext, renderer, input, &bytes.Buffer{}), "FAIL") + }) +} diff --git a/modules/setting/actions.go b/modules/setting/actions.go index 52a3ad5309..303fd6363b 100644 --- a/modules/setting/actions.go +++ b/modules/setting/actions.go @@ -12,23 +12,25 @@ import ( // Actions settings var ( Actions = struct { - Enabled bool - LogStorage *Storage // how the created logs should be stored - LogRetentionDays int64 `ini:"LOG_RETENTION_DAYS"` - LogCompression logCompression `ini:"LOG_COMPRESSION"` - ArtifactStorage *Storage // how the created artifacts should be stored - ArtifactRetentionDays int64 `ini:"ARTIFACT_RETENTION_DAYS"` - DefaultActionsURL defaultActionsURL `ini:"DEFAULT_ACTIONS_URL"` - ZombieTaskTimeout time.Duration `ini:"ZOMBIE_TASK_TIMEOUT"` - EndlessTaskTimeout time.Duration `ini:"ENDLESS_TASK_TIMEOUT"` - AbandonedJobTimeout time.Duration `ini:"ABANDONED_JOB_TIMEOUT"` - SkipWorkflowStrings []string `ìni:"SKIP_WORKFLOW_STRINGS"` - LimitDispatchInputs int64 `ini:"LIMIT_DISPATCH_INPUTS"` + Enabled bool + LogStorage *Storage // how the created logs should be stored + LogRetentionDays int64 `ini:"LOG_RETENTION_DAYS"` + LogCompression logCompression `ini:"LOG_COMPRESSION"` + ArtifactStorage *Storage // how the created artifacts should be stored + ArtifactRetentionDays int64 `ini:"ARTIFACT_RETENTION_DAYS"` + DefaultActionsURL defaultActionsURL `ini:"DEFAULT_ACTIONS_URL"` + ZombieTaskTimeout time.Duration `ini:"ZOMBIE_TASK_TIMEOUT"` + EndlessTaskTimeout time.Duration `ini:"ENDLESS_TASK_TIMEOUT"` + AbandonedJobTimeout time.Duration `ini:"ABANDONED_JOB_TIMEOUT"` + SkipWorkflowStrings []string `ìni:"SKIP_WORKFLOW_STRINGS"` + LimitDispatchInputs int64 `ini:"LIMIT_DISPATCH_INPUTS"` + ConcurrencyGroupQueueEnabled bool `ini:"CONCURRENCY_GROUP_QUEUE_ENABLED"` }{ - Enabled: true, - DefaultActionsURL: defaultActionsURLForgejo, - SkipWorkflowStrings: []string{"[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"}, - LimitDispatchInputs: 10, + Enabled: true, + DefaultActionsURL: defaultActionsURLForgejo, + SkipWorkflowStrings: []string{"[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"}, + LimitDispatchInputs: 10, + ConcurrencyGroupQueueEnabled: true, } ) diff --git a/modules/setting/indexer_test.go b/modules/setting/indexer_test.go index 8f0437be8a..498f8752a2 100644 --- a/modules/setting/indexer_test.go +++ b/modules/setting/indexer_test.go @@ -65,7 +65,7 @@ func checkGlobMatch(t *testing.T, globstr string, list []indexerMatchList) { } } if !found { - assert.Equal(t, m.position, -1, "Test string `%s` doesn't match `%s` anywhere; expected @%d", m.value, globstr, m.position) + assert.Equal(t, -1, m.position, "Test string `%s` doesn't match `%s` anywhere; expected @%d", m.value, globstr, m.position) } } } diff --git a/modules/setting/ui.go b/modules/setting/ui.go index 9dafe350eb..61378e0596 100644 --- a/modules/setting/ui.go +++ b/modules/setting/ui.go @@ -7,35 +7,33 @@ import ( "time" "forgejo.org/modules/container" - "forgejo.org/modules/log" ) // UI settings var UI = struct { - ExplorePagingNum int - SitemapPagingNum int - IssuePagingNum int - RepoSearchPagingNum int - MembersPagingNum int - FeedMaxCommitNum int - FeedPagingNum int - PackagesPagingNum int - GraphMaxCommitNum int - CodeCommentLines int - ReactionMaxUserNum int - MaxDisplayFileSize int64 - ShowUserEmail bool - DefaultShowFullName bool - DefaultTheme string - Themes []string - Reactions []string - ReactionsLookup container.Set[string] `ini:"-"` - CustomEmojis []string - CustomEmojisLookup container.Set[string] `ini:"-"` - SearchRepoDescription bool - OnlyShowRelevantRepos bool - ExploreDefaultSort string `ini:"EXPLORE_PAGING_DEFAULT_SORT"` - PreferredTimestampTense string + ExplorePagingNum int + SitemapPagingNum int + IssuePagingNum int + RepoSearchPagingNum int + MembersPagingNum int + FeedMaxCommitNum int + FeedPagingNum int + PackagesPagingNum int + GraphMaxCommitNum int + CodeCommentLines int + ReactionMaxUserNum int + MaxDisplayFileSize int64 + ShowUserEmail bool + DefaultShowFullName bool + DefaultTheme string + Themes []string + Reactions []string + ReactionsLookup container.Set[string] `ini:"-"` + CustomEmojis []string + CustomEmojisLookup container.Set[string] `ini:"-"` + SearchRepoDescription bool + OnlyShowRelevantRepos bool + ExploreDefaultSort string `ini:"EXPLORE_PAGING_DEFAULT_SORT"` AmbiguousUnicodeDetection bool SkipEscapeContexts []string @@ -71,24 +69,23 @@ var UI = struct { Keywords string } `ini:"ui.meta"` }{ - ExplorePagingNum: 20, - SitemapPagingNum: 20, - IssuePagingNum: 20, - RepoSearchPagingNum: 20, - MembersPagingNum: 20, - FeedMaxCommitNum: 5, - FeedPagingNum: 20, - PackagesPagingNum: 20, - GraphMaxCommitNum: 100, - CodeCommentLines: 4, - ReactionMaxUserNum: 10, - MaxDisplayFileSize: 8388608, - DefaultTheme: `forgejo-auto`, - Themes: []string{`forgejo-auto`, `forgejo-light`, `forgejo-dark`, `gitea-auto`, `gitea-light`, `gitea-dark`, `forgejo-auto-deuteranopia-protanopia`, `forgejo-light-deuteranopia-protanopia`, `forgejo-dark-deuteranopia-protanopia`, `forgejo-auto-tritanopia`, `forgejo-light-tritanopia`, `forgejo-dark-tritanopia`}, - Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, - CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`, `forgejo`}, - ExploreDefaultSort: "recentupdate", - PreferredTimestampTense: "mixed", + ExplorePagingNum: 20, + SitemapPagingNum: 20, + IssuePagingNum: 20, + RepoSearchPagingNum: 20, + MembersPagingNum: 20, + FeedMaxCommitNum: 5, + FeedPagingNum: 20, + PackagesPagingNum: 20, + GraphMaxCommitNum: 100, + CodeCommentLines: 4, + ReactionMaxUserNum: 10, + MaxDisplayFileSize: 8388608, + DefaultTheme: `forgejo-auto`, + Themes: []string{`forgejo-auto`, `forgejo-light`, `forgejo-dark`, `gitea-auto`, `gitea-light`, `gitea-dark`, `forgejo-auto-deuteranopia-protanopia`, `forgejo-light-deuteranopia-protanopia`, `forgejo-dark-deuteranopia-protanopia`, `forgejo-auto-tritanopia`, `forgejo-light-tritanopia`, `forgejo-dark-tritanopia`}, + Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, + CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`, `forgejo`}, + ExploreDefaultSort: "recentupdate", AmbiguousUnicodeDetection: true, SkipEscapeContexts: []string{}, @@ -150,10 +147,6 @@ func loadUIFrom(rootCfg ConfigProvider) { UI.DefaultShowFullName = sec.Key("DEFAULT_SHOW_FULL_NAME").MustBool(false) UI.SearchRepoDescription = sec.Key("SEARCH_REPO_DESCRIPTION").MustBool(true) - if UI.PreferredTimestampTense != "mixed" && UI.PreferredTimestampTense != "absolute" { - log.Fatal("ui.PREFERRED_TIMESTAMP_TENSE must be either 'mixed' or 'absolute'") - } - // OnlyShowRelevantRepos=false is important for many private/enterprise instances, // because many private repositories do not have "description/topic", users just want to search by their names. UI.OnlyShowRelevantRepos = sec.Key("ONLY_SHOW_RELEVANT_REPOS").MustBool(false) diff --git a/modules/structs/admin_user.go b/modules/structs/admin_user.go index 8a4d066d72..a7bd5643e9 100644 --- a/modules/structs/admin_user.go +++ b/modules/structs/admin_user.go @@ -50,4 +50,5 @@ type EditUserOption struct { AllowCreateOrganization *bool `json:"allow_create_organization"` Restricted *bool `json:"restricted"` Visibility string `json:"visibility" binding:"In(,public,limited,private)"` + HideEmail *bool `json:"hide_email"` } diff --git a/modules/structs/commit_status.go b/modules/structs/commit_status.go index dc880ef5eb..2a5dfb7546 100644 --- a/modules/structs/commit_status.go +++ b/modules/structs/commit_status.go @@ -4,7 +4,7 @@ package structs // CommitStatusState holds the state of a CommitStatus -// It can be "pending", "success", "error" and "failure" +// It can be "pending", "success", "error", "failure" and "warning" type CommitStatusState string const ( diff --git a/modules/structs/repo_actions.go b/modules/structs/repo_actions.go index b13f344738..eada2db09f 100644 --- a/modules/structs/repo_actions.go +++ b/modules/structs/repo_actions.go @@ -32,3 +32,27 @@ type ActionTaskResponse struct { Entries []*ActionTask `json:"workflow_runs"` TotalCount int64 `json:"total_count"` } + +// ActionRunnerLabel represents a Runner Label +type ActionRunnerLabel struct { + ID int64 `json:"id"` + Name string `json:"name"` + Type string `json:"type"` +} + +// ActionRunner represents a Runner +type ActionRunner struct { + ID int64 `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Busy bool `json:"busy"` + // currently unused as forgejo does not support ephemeral runners, but they are defined in gh api spec + Ephemeral bool `json:"ephemeral"` + Labels []*ActionRunnerLabel `json:"labels"` +} + +// ActionRunnersResponse returns Runners +type ActionRunnersResponse struct { + Entries []*ActionRunner `json:"runners"` + TotalCount int64 `json:"total_count"` +} diff --git a/modules/templates/util_date.go b/modules/templates/util_date.go index bb83bf692a..839d2701ef 100644 --- a/modules/templates/util_date.go +++ b/modules/templates/util_date.go @@ -144,8 +144,5 @@ func timeSinceTo(then any, now time.Time) template.HTML { // TimeSince renders relative time HTML given a time func TimeSince(then any) template.HTML { - if setting.UI.PreferredTimestampTense == "absolute" { - return dateTimeFormat("full", then) - } return timeSinceTo(then, time.Now()) } diff --git a/modules/util/donotpanic/donotpanic.go b/modules/util/donotpanic/donotpanic.go new file mode 100644 index 0000000000..e283ec9040 --- /dev/null +++ b/modules/util/donotpanic/donotpanic.go @@ -0,0 +1,28 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package donotpanic + +import ( + "fmt" + + "forgejo.org/modules/log" +) + +type FuncWithError func() error + +func SafeFuncWithError(fun FuncWithError) (err error) { + defer func() { + if r := recover(); r != nil { + log.Error("PANIC recovered: %v\nStacktrace: %s", r, log.Stack(2)) + rErr, ok := r.(error) + if ok { + err = fmt.Errorf("PANIC recover with error: %w", rErr) + } else { + err = fmt.Errorf("PANIC recover: %v", r) + } + } + }() + + return fun() +} diff --git a/modules/util/donotpanic/donotpanic_test.go b/modules/util/donotpanic/donotpanic_test.go new file mode 100644 index 0000000000..6ddeafe06a --- /dev/null +++ b/modules/util/donotpanic/donotpanic_test.go @@ -0,0 +1,28 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package donotpanic + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDoNotPanic_SafeFuncWithError(t *testing.T) { + t.Run("OK", func(t *testing.T) { + assert.NoError(t, SafeFuncWithError(func() error { return nil })) + }) + + t.Run("PanickString", func(t *testing.T) { + errorMessage := "ERROR MESSAGE" + assert.ErrorContains(t, SafeFuncWithError(func() error { panic(errorMessage) }), fmt.Sprintf("recover: %s", errorMessage)) + }) + + t.Run("PanickError", func(t *testing.T) { + errorMessage := "ERROR MESSAGE" + assert.ErrorContains(t, SafeFuncWithError(func() error { panic(errors.New(errorMessage)) }), fmt.Sprintf("recover with error: %s", errorMessage)) + }) +} diff --git a/options/locale/locale_ar.ini b/options/locale/locale_ar.ini index a60fd7d5de..256692c080 100644 --- a/options/locale/locale_ar.ini +++ b/options/locale/locale_ar.ini @@ -1174,8 +1174,6 @@ activity.period.quarterly = ثلاثة أشهر activity.period.semiyearly = ستة أشهر activity.period.yearly = عام واحد activity.overview = نظرة عامة -activity.active_prs_count_1 = %d طلب دمج حالي -activity.active_prs_count_n = %d طلب دمج حالي activity.merged_prs_count_1 = طلب دمج مقبول activity.merged_prs_count_n = طلب دمج مقبول activity.opened_prs_count_1 = طلب دمج مقترح @@ -1188,8 +1186,6 @@ activity.title.prs_merged_by = %s دمجها %s activity.title.prs_opened_by = %s اقترحها %s activity.merged_prs_label = مقبول activity.opened_prs_label = مقترح -activity.active_issues_count_1 = %d مسألة حالية -activity.active_issues_count_n = %d مسألة حالية activity.closed_issues_count_1 = مسألة تامة activity.closed_issues_count_n = مسألة تامة activity.title.issues_1 = %d مسألة @@ -2106,10 +2102,6 @@ organizations = المنظمات relevant_repositories_tooltip = تم أخفاء المستودعات التي هي مشتقات وأيضاً التي ليس لها موضوع، ولا أيقونة، ولا يوجد وصف. relevant_repositories = يتم اظهار المستودعات المتعلقة فقط. أظهر النتائج غير المصفاة. code_last_indexed_at = فُهرس آخر مرة %s -stars_few = %d نجوم -forks_one = %d نسخة -forks_few = %d نُسَخ -stars_one = %d نجمة [actions] variables.none = لا توجد متغيرات بعد. diff --git a/options/locale/locale_bg.ini b/options/locale/locale_bg.ini index 720c4fa1e5..b8c11a6807 100644 --- a/options/locale/locale_bg.ini +++ b/options/locale/locale_bg.ini @@ -761,8 +761,6 @@ activity.title.user_n = %d потребители activity.title.prs_n = %d заявки за сливане activity.merged_prs_count_1 = Слята заявка за сливане activity.merged_prs_count_n = Слети заявки за сливане -activity.active_issues_count_1 = %d активна задача -activity.active_issues_count_n = %d активни задачи activity.closed_issues_count_1 = Затворена задача activity.closed_issues_count_n = Затворени задачи activity.title.issues_1 = %d задача @@ -774,13 +772,11 @@ wiki.save_page = Запазване на страницата activity.git_stats_author_n = %d автори wiki.delete_page_button = Изтриване на страницата activity.title.prs_1 = %d заявка за сливане -activity.active_prs_count_n = %d активни заявки за сливане activity.period.filter_label = Период: activity.period.daily = 1 ден activity.period.halfweekly = 3 дни activity.period.weekly = 1 седмица activity.period.yearly = 1 година -activity.active_prs_count_1 = %d активна заявка за сливане wiki.page_title = Заглавие на страницата wiki.page_content = Съдържание на страницата wiki.filter_page = Филтриране на страница @@ -1370,8 +1366,6 @@ issues.review.comment = рецензира %s branch.deleted_by = Изтрит от %s branch.restore = Възстановяване на клона „%s“ archive.title_date = Това хранилище е архивирано на %s. Можете да преглеждате файлове и да го клонирате, но не можете да правите промени в състоянието му, като изтласкване и създаване на нови задачи, заявки за сливане или коментари. -release.download_count_one = %s изтегляне -release.download_count_few = %s изтегляния branch.restore_success = Клонът „%s“ е възстановен. tag.create_tag_from = Създаване на нов маркер от „%s“ branch.create_new_branch = Създаване на клон от клон: @@ -1887,7 +1881,7 @@ org_full_name_holder = Пълно име на организацията teams = Екипи lower_members = участници lower_repositories = хранилища -settings.repoadminchangeteam = Админ. на хранилището да може да добавя и премахва достъп за екипи +settings.repoadminchangeteam = Админ. на хранилище да може да добавя и премахва достъп за екипи settings.email = Ел. поща за връзка settings.delete_account = Изтриване на тази организация settings.delete_org_title = Изтриване на организацията @@ -1953,6 +1947,8 @@ teams.admin_access = Администраторски достъп settings.change_orgname_redirect_prompt.with_cooldown.one = Старото име на организацията ще бъде достъпно за всички след период на изчакване от %[1]d ден. Все още можете да си върнете старото име по време на периода на изчакване. teams.add_nonexistent_repo = Хранилището, което се опитвате да добавите, не съществува, моля, първо го създайте. teams.invite.by = Поканен от %s +teams.owners_permission_desc = Притежателите имат пълен достъп до всички хранилища и имат администраторски достъп до организацията. +teams.can_create_org_repo_helper = Участниците могат да създават нови хранилища в организацията. Създателят ще получи администраторски достъп до новото хранилище. [install] admin_password = Парола @@ -2068,7 +2064,7 @@ projects = Проекти code = Код overview = Обзор watched = Наблюдавани хранилища -unfollow = Прекратяване на следването +unfollow = Отследване block = Блокиране settings = Потребителски настройки starred = Отбелязани хранилища @@ -2409,10 +2405,6 @@ users = Потребители code = Код organizations = Организации code_last_indexed_at = Последно индексиран %s -stars_one = %d звезда -stars_few = %d звезди -forks_one = %d разклонение -forks_few = %d разклонения relevant_repositories = Показани са само подходящи хранилища, показване на нефилтрирани резултати. relevant_repositories_tooltip = Хранилищата, които са разклонения или нямат тема, икона или описание, са скрити. diff --git a/options/locale/locale_ca.ini b/options/locale/locale_ca.ini index de6d692410..e1ebee1b00 100644 --- a/options/locale/locale_ca.ini +++ b/options/locale/locale_ca.ini @@ -324,12 +324,8 @@ relevant_repositories = Només és mostren repositoris rellevants, repos = Repositoris organizations = Organitzacions code = Codi -stars_few = %d estrelles -forks_one = %d fork -forks_few = %d forks go_to = Ves a users = Usuaris -stars_one = %d estrella [auth] disable_register_prompt = El registre està deshabilitat. Si us plau contacti l'administrador del lloc. @@ -484,13 +480,13 @@ team_invite.subject = %[1]s us convida a unir-vos a l'organització %[2]s release.title = Títol: %s release.downloads = Baixades: release.download.zip = Codi font (ZIP) -repo.transfer.subject_to_you = %s us vol transferir el repositori "%s" +repo.transfer.subject_to_you = %s us vol transferir el repositori «%s» repo.collaborator.added.subject = %s us ha afegit a %s com a col·laborador repo.collaborator.added.text = Us han afegit com a col·laborador al repositori: issue_assigned.issue = @%[1]s us ha assignat l'incidència %[2]s del repositori %[3]s. issue.x_mentioned_you = @%s us ha mencionat: issue.action.new = @%[1]s ha creat #%[2]d. -repo.transfer.subject_to = %s vol transferir el repositori "%s" a %s +repo.transfer.subject_to = %s vol transferir el repositori «%s» a %s repo.transfer.to_you = tu register_notify.text_3 = Si algú altre us ha fet aquest compte, necessitareu configurar la vostra contrasenya abans. password_change.subject = S'ha canviat la vostra contrasenya @@ -507,6 +503,24 @@ issue.action.reopen = @%[1]s ha reobert #%[2]s. issue.action.approve = @%[1]s ha aprovat aquesta sol·licitud d'extracció. issue.action.reject = @%[1]s ha sol·licitat canvis en aquesta sol·licitud d'extracció. register_notify.text_2 = Podeu iniciar sessió al vostre compte fent servir el vostre nom d'usuari: %s +register_notify.text_1 = aquest és el vostre correu electrònic de confirmació pel registre de %s! +password_change.text_1 = S'acaba de canviar la contrasenya pel vostre compte. +issue.action.review = @%[1]s ha deixat un comentari a la vostra sol·licitud d'extracció. +issue.action.review_dismissed = @%[1]s ha rebutjat l'última revisió de %[2]s per aquesta sol·licitud d'extracció. +primary_mail_change.text_1 = S'ha canviat la vostra adreça de correu electrònic principal a %[1]s. Això vol dir que aquesta adreça de correu electrònic no rebrà més notificacions de correu pel vostre compte. +totp_disabled.text_1 = S'han desactivat les contrasenyes d'un sol ús basades en el temps (TOTP) pel vostre compte. +totp_disabled.no_2fa = Ja no hi ha altres mètodes d'autenticació de doble factor configurats, per la qual cosa ja no és necessari iniciar sessió al vostre compte mitjançat autenticació de doble factor. +removed_security_key.no_2fa = Ja no hi ha altres mètodes d'autenticació de doble factor configurats, per la qual cosa ja no és necessari iniciar sessió al vostre compte mitjançat autenticació de doble factor. +reset_password = Recupereu el vostre compte +reset_password.text = Si heu sigut vós, cliqueu el següent enllaç per recuperar el vostre compte abans de %s: +totp_enrolled.text_1.has_webauthn = Heu habilitat el TOTP per al vostre compte. Això vol dir que, per a totes les futures connexions al vostre compte, podreu utilitzar el TOTP com a mètode d'autenticació en dos passos (2FA) o bé utilitzar qualsevol de les vostres claus de seguretat. +issue.action.force_push = %[1]s ha realitzat un «force push» de %[2]s des de %[3]s fins a %[4]s. +issue.action.ready_for_review = @%[1]s ha marcat aquesta «pull request» com a preparada per a la revisió. +issue.in_tree_path = A %s: +issue.action.push_1 = @%[1]s ha pujat $[3]d commit a %[2]s +issue.action.push_n = @%[1]s ha pujat $[3]d commits a %[2]s +release.note = Nota: +release.new.text = @%[1]s ha publicat %[2]s a %[3]s [modal] yes = Sí @@ -579,6 +593,18 @@ require_error = ` no pot estar buit.` min_size_error = ` ha de contenir %s caràcters com a mínim.` max_size_error = ` ha de contenir %s caràcters com a màxim.` email_domain_is_not_allowed = El domini de l'adreça de correu electrònic %s de l'usuari entra en conflicte amb EMAIL_DOMAIN_ALLOWLIST o EMAIL_DOMAIN_BLOCKLIST. Assegureu-vos d'haver introduït l'adreça de correu electrònic correctament. +Website = Lloc web +Location = Ubicació +AdminEmail = Correu electrònic de l'administrador +AccessToken = Testimoni d'accés +alpha_dash_error = ` hauria de contenir només caràcters alfanumèrics, guions ("-") i barres baixes ("_").` +alpha_dash_dot_error = ` hauria de contenir només caràcters alfanumèrics, guions ("-"), barres baixes ("_") i punts (".").` +regex_pattern_error = ` el patró d'expressió regular no és vàlid: %s.` +required_prefix = L'entrada ha de començar amb "%s" +CommitSummary = Resum del commit +CommitMessage = Missatge del commit +CommitChoice = Selecció de commit +TreeName = Camí del fitxer [settings] pronouns = Pronoms @@ -737,7 +763,106 @@ generate_token = Generar el testimoni generate_token_success = S'ha generat el vostre nou testimoni. Copieu-lo ara, ja que no es tornarà a mostrar. generate_token_name_duplicate = Ja s'ha usat %s com a nom d'aplicació. Si us plau, useu-ne un de nou. delete_token = Eliminar -access_token_deletion = +access_token_deletion = +delete_email = Eliminar +biography_placeholder = Explica una mica sobre tu als altres! (Compatible amb Markdown) +add_new_email = Afegir una adreça de correu electrònic +add_new_openid = Afegir una nova URI d'OpenID +add_email = Afegir una adreça de correu electrònic +add_openid = Afegir una URI d'OpenID +keep_email_private = Oculta l'adreça de correu electrònic +delete_key = Eliminar +added_on = Afegit el %s +last_used = Usat per últim cop el +can_read_info = Llegir +can_write_info = Escriure +hide_openid = Ocultar del perfil +show_openid = Mostrar al perfil +ssh_disabled = SSH està deshabilitat +access_token_deletion_desc = Eliminar un testimoni revocarà l'accés al vostre compte de les aplicacions que l'estiguin fent servir. Aquesta acció no es pot desfer. Voleu continuar? +delete_token_success = S'ha eliminat el testimoni. Les aplicacions que l'estiguin fent servir ja no tenen accés al vostre compte. +regenerate_token = Regenerar +access_token_regeneration = Regenerar un testimoni d'accés +access_token_regeneration_desc = Regenerar un testimoni revocarà l'accés al vostre compte de les aplicacions que l'estiguin fent servir. Aquesta acció no es pot desfer. Voleu continuar? +regenerate_token_success = S'ha regenerat el testimoni. Les aplicacions que el fan servir ja no tenen accés al vostre compte i s'han actualitzar al nou testimoni. +permissions_public_only = Només públic +permissions_access_all = Tot (públic, privat i limitat) +select_permissions = Selecció de permisos +permission_no_access = Sense accés +permission_read = Lectura +permission_write = Lectura i escriptura +at_least_one_permission = Heu de seleccionar un permís com a mínim per crear un testimoni +permissions_list = Permisos: +remove_oauth2_application = Eliminar aplicació OAuth2 +edit_oauth2_application = Modificar aplicació OAuth2 +manage_oauth2_applications = Gestionar aplicacions OAuth2 +remove_oauth2_application_success = S'ha eliminat l'aplicació. +create_oauth2_application = Crear una nova aplicació OAuth2 +create_oauth2_application_button = Crear aplicació +create_oauth2_application_success = Heu creat amb èxit una nova aplicació OAuth2. +update_oauth2_application_success = Heu actualitzat amb èxit l'aplicació OAuth2. +oauth2_application_name = Nom de l'aplicació +oauth2_redirect_uris = URIs de redirecció. Si us plau, poseu cada URI en una línia nova. +save_application = Guardar +oauth2_client_id = ID del client +oauth2_client_secret = Secret del client +oauth2_regenerate_secret = Regenerar secret +oauth2_regenerate_secret_hint = Heu perdut el vostre secret? +oauth2_application_edit = Modificar +authorized_oauth2_applications = Aplicacions OAuth2 autoritzades +authorized_oauth2_applications_description = Heu permès accés al vostre compte personal de Forgejo a aquestes aplicacions de tercers. Si us plau, revoqueu l'accés de les aplicacions que ja no feu servir. +revoke_key = Revocar +revoke_oauth2_grant = Revocar l'accés +revoke_oauth2_grant_description = Revocar l'accés d'aquesta aplicació de tercers impedirà que accedeixi a la vostra informació. N'esteu segurs? +revoke_oauth2_grant_success = L'accés s'ha revocat amb èxit. +twofa_desc = Per protegir el vostre compte del robatori de contrasenyes, podeu fer servir un telèfon intel·ligent o un altre dispositiu per rebre contrasenyes d'un sol ús basades en el temps ("TOTP"). +twofa_recovery_tip = Si perdeu el vostre dispositiu, podreu fer servir una clau de recuperació d'un sol ús per recuperar l'accés al vostre compte. +twofa_disable = Desactivar l'autenticació de doble factor +twofa_scratch_token_regenerate = Regenerar la clau de recuperació d'un sol ús +twofa_scratch_token_regenerated = La vostra clau de recuperació d'un sol ús ara és %s. Emmagatzemeu-la en un lloc segur, ja que no es tornarà a mostrar. +twofa_disable_note = L'autenticació de doble factor es pot desactivar si és necessari. +twofa_disable_desc = Desactivar l'autenticació de doble factor farà que el vostre compte sigui menys segur. Voleu continuar? +regenerate_scratch_token_desc = Si heu perdur la vostra clau de recuperació o ja l'heu fet servir per iniciar sessió, la podeu reiniciar aquí. +twofa_disabled = S'ha desactivat l'autenticació de doble factor. +scan_this_image = Escanegeu aquesta imatge amb la vostra aplicació d'autenticació: +or_enter_secret = O introduïu el secret: %s +then_enter_passcode = I introduïu el codi d'accés que es mostra a l'aplicació: +passcode_invalid = El codi d'accés és incorrecte. Intenteu-ho de nou. +twofa_failed_get_secret = No s'ha pogut obtenir el secret. +webauthn_desc = Les claus de seguretat són dispositius que contenen claus criptogràfiques. Es poden fer servir per l'autenticació de doble factor. Les claus de seguretat han de ser compatibles amb l'estàndard WebAuthn Authenticator. +webauthn_register_key = Afegir una clau de seguretat +webauthn_nickname = Sobrenom +webauthn_delete_key_desc = Si elimineu una clau de seguretat no la podreu fer servir per iniciar sessió. Voleu continuar? +webauthn_key_loss_warning = Si perdeu les vostres claus de seguretat, perdreu també l'accés al vostre compte. +manage_account_links = Comptes vinculats +manage_account_links_desc = Aquests comptes externs estan vinculats al vostre compte de Forgejo. +link_account = Vincular un compte +remove_account_link_desc = Eliminar un compte vinculat revocarà el seu accés al vostre compte de Forgejo. Voleu continuar? +remove_account_link_success = S'ha eliminat el compte vinculat. +orgs_none = No sou membres de cap organització. +repos_none = No sou propietaris de cap repositori. +blocked_users_none = No hi ha cap usuari bloquejat. +delete_account = Eliminar el vostre compte +delete_prompt = Aquest acció eliminarà permanentment el vostre compte. Aquesta acció no es pot desfer. +confirm_delete_account = Confirmar l'eliminació +delete_account_title = Eliminar el compte d'usuari +delete_account_desc = Esteu segurs de voler eliminar permanentment aquest compte d'usuari? +email_notifications.enable = Activar les notificacions per correu electrònic +email_notifications.disable = Desactivar les notificacions per correu electrònic +visibility.public_tooltip = Visible per a tothom +visibility.limited_tooltip = Visible només pels usuaris que hagin iniciat sessió +visibility.private_tooltip = Visible només pels membres de les organitzacions a les quals us hàgiu unit +user_unblock_success = S'ha desbloquejat l'usuari amb èxit. +user_block_success = S'ha bloquejat l'usuari amb èxit. +quota.sizes.repos.all = Repositoris +quota.sizes.repos.public = Repositoris públics +quota.sizes.repos.private = Repositoris privats +quota.sizes.git.all = Contingut de Git +quota.sizes.git.lfs = Git LFS +quota.sizes.assets.attachments.all = Fitxers adjunts +quota.sizes.assets.attachments.issues = Fitxers adjunts a incidències +quota.sizes.assets.artifacts = Artefactes +quota.sizes.assets.packages.all = Paquets [repo] settings.basic_settings = Configuració bàsica @@ -951,4 +1076,6 @@ public_activity.visibility_hint.self_public = La vostra activitat és visible pe public_activity.visibility_hint.admin_public = Aquesta activitat és visible per tothom, però com a administrador també podeu veure interaccions en espais privats. public_activity.visibility_hint.self_private = La vostra activitat és visible només per vós i pels administradors de la instància. Configurar. public_activity.visibility_hint.admin_private = Aquesta activitat és visible per vós perquè sou un administrador, però l'usuari vol que romangui privada. -public_activity.visibility_hint.self_private_profile = La vostra activitat és visible només per vós i pels administradors de la instància perquè el vostre perfil és privat. Configurar. \ No newline at end of file +public_activity.visibility_hint.self_private_profile = La vostra activitat és visible només per vós i pels administradors de la instància perquè el vostre perfil és privat. Configurar. +change_avatar = Canvieu el vostre avatar… +joined_on = S'ha unit el %s \ No newline at end of file diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 7ca734930b..f94459652a 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -374,10 +374,6 @@ code=Kód code_last_indexed_at=Naposledy indexováno %s relevant_repositories_tooltip=Repozitáře, které jsou forky nebo nemají žádné téma, žádnou ikonu a žádný popis, jsou skryty. relevant_repositories=Zobrazují se pouze relevantní repositáře, zobrazit nefiltrované výsledky. -forks_one = %d fork -forks_few = %d forků -stars_one = %d hvězda -stars_few = %d hvězd [auth] create_new_account=Registrovat účet @@ -1968,8 +1964,6 @@ activity.period.quarterly=3 měsíce activity.period.semiyearly=6 měsíců activity.period.yearly=1 rok activity.overview=Přehled -activity.active_prs_count_1=%d aktivní žádost o sloučení -activity.active_prs_count_n=%d aktivních žádostí o sloučení activity.merged_prs_count_1=Sloučená žádost activity.merged_prs_count_n=Sloučené žádosti activity.opened_prs_count_1=Navrhovaná žádost o sloučení @@ -1982,8 +1976,6 @@ activity.title.prs_merged_by=%s sloučil %s activity.title.prs_opened_by=%s navrhl %s activity.merged_prs_label=Sloučený activity.opened_prs_label=Navrhovaný -activity.active_issues_count_1=%d aktivní problém -activity.active_issues_count_n=%d aktivních problémů activity.closed_issues_count_1=Uzavřený problém activity.closed_issues_count_n=Uzavřené problémy activity.title.issues_1=%d problémy @@ -2667,8 +2659,6 @@ settings.enforce_on_admins_desc = Správci repozitáře nemohou obejít toto pra issues.num_participants_one = %d účastník size_format = %[1]s: %[2]s, %[3]s: %[4]s issues.archived_label_description = (Archivován) %s -release.download_count_one = %s stažení -release.download_count_few = %s stažení release.system_generated = Tato příloha byla automaticky vygenerována. settings.add_webhook.invalid_path = Cesta nesmí obsahovat část, která je „.“ nebo „..“ nebo prázdný řetězec. Nesmí začínat ani končit lomítkem. settings.web_hook_name_sourcehut_builds = Sestavení SourceHut diff --git a/options/locale/locale_da.ini b/options/locale/locale_da.ini index 129c831bef..f9db1298e0 100644 --- a/options/locale/locale_da.ini +++ b/options/locale/locale_da.ini @@ -373,10 +373,6 @@ show_both_private_public = Viser både offentlige og private [explore] repos = Depoter users = Brugere -stars_one = %d stjerne -stars_few = %d stjerner -forks_one = %d fork -forks_few = %d forks organizations = Organisationer code = Kode code_last_indexed_at = Sidst indekseret %s @@ -2070,7 +2066,6 @@ settings.federation_settings = Føderationsindstillinger settings.hooks = Webhooks activity.title.issues_created_by = %s oprettet af %s activity.new_issue_label = Åbnet -activity.active_issues_count_n = %d aktive problemer activity.closed_issue_label = Lukket activity.commit = Commit aktivitet settings.collaboration = Samarbejdspartnere @@ -2102,7 +2097,6 @@ activity.merged_prs_label = Flettet activity.merged_prs_count_n = Flettede pull-anmodninger activity.title.prs_n = %d pull-anmodninger activity.opened_prs_label = Foreslået -activity.active_issues_count_1 = %d aktivt problem activity.new_issues_count_1 = Nyt problem activity.new_issues_count_n = Nye problemer activity.git_stats_commit_1 = %d commit @@ -2125,8 +2119,6 @@ settings.mirror_settings.docs.disabled_push_mirror.info = Push-spejle er blevet settings.mirror_settings.docs.no_new_mirrors = Dit depot spejler ændringer til eller fra et andet depot. Husk på, at du ikke kan oprette nye spejle på nuværende tidspunkt. settings.mirror_settings.docs.can_still_use = Selvom du ikke kan ændre eksisterende spejle eller oprette nye, kan du stadig bruge dit eksisterende spejle. settings.mirror_settings.docs.doc_link_title = Hvordan spejler jeg depoter? -activity.active_prs_count_1 = %d aktiv pull-anmodning -activity.active_prs_count_n = %d aktive pull-anmodninger activity.merged_prs_count_1 = Sammenflettet pull-anmodning activity.opened_prs_count_1 = Foreslået pull anmodning activity.opened_prs_count_n = Foreslåede pull-anmodninger @@ -2668,8 +2660,6 @@ diff.file_suppressed_line_too_long = Filforskellen er undertrykt, fordi en eller diff.review = Afslut gennemgangen diff.review.header = Send anmeldelse diff.review.placeholder = Gennemgå kommentar -release.download_count_one = %s download -release.download_count_few = %s downloads topic.format_prompt = Emner skal starte med et bogstav eller tal, kan indeholde bindestreger ("-") og prikker ("."), kan være op til 35 tegn lange. Bogstaver skal være små. branch.warning_rename_default_branch = Du omdøber standardgrenen. topic.done = Færdig diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 7ca3e4de71..06444a8782 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -375,10 +375,6 @@ code=Code code_last_indexed_at=Zuletzt indiziert %s relevant_repositories_tooltip=Repositorys, die Forks sind oder die kein Thema, kein Symbol und keine Beschreibung haben, werden ausgeblendet. relevant_repositories=Es werden nur relevante Repositorys angezeigt, ungefilterte Ergebnisse anzeigen. -stars_one = %d Favorisierung -stars_few = %d Favorisierungen -forks_one = %d Fork -forks_few = %d Forks [auth] create_new_account=Konto registrieren @@ -793,7 +789,7 @@ add_email_success=Die neue E-Mail-Addresse wurde hinzugefügt. email_preference_set_success=E-Mail-Einstellungen wurden erfolgreich aktualisiert. add_openid_success=Die neue OpenID-Adresse wurde hinzugefügt. keep_email_private=E-Mail-Adresse verbergen -keep_email_private_popup=Deine Mailadresse wird nicht in deinem Profil angezeigt und wird nicht der Standard für Commits über das Webinterface sein, wie zum Beispiel Dateiuploads, Bearbeitungen, und Merge-Commits. Stattdessen kann eine besondere Adresse %s benutzt werden, um Commits mit deinem Account zu verbinden. Diese Option wirkt sich nicht auf bestehende Commits aus. +keep_email_private_popup=Die E-Mail-Adresse wird nicht im Profil angezeigt und wird nicht der Standard für Commits über das Webinterface sein, wie zum Beispiel Dateiuploads, Bearbeitungen, und Merge-Commits. Stattdessen kann eine besondere Adresse %s benutzt werden, um Commits mit dem Benutzeraccount zu verbinden. Diese Option wirkt sich nicht auf bestehende Commits aus. openid_desc=Mit OpenID kannst du dich über einen Drittanbieter authentifizieren. manage_ssh_keys=SSH-Schlüssel verwalten @@ -1653,8 +1649,8 @@ issues.add_time=Zeit manuell hinzufügen issues.del_time=Diese Zeiterfassung löschen issues.add_time_short=Zeit hinzufügen issues.add_time_cancel=Abbrechen -issues.add_time_history=`hat %s den Zeitaufwand hinzugefügt` -issues.del_time_history=`hat %s den Zeitaufwand gelöscht` +issues.add_time_history=`hat den Zeitaufwand von %s hinzugefügt` +issues.del_time_history=`hat den Zeitaufwand von %s gelöscht` issues.add_time_hours=Stunden issues.add_time_minutes=Minuten issues.add_time_sum_to_small=Es wurde keine Zeit eingegeben. @@ -1961,8 +1957,6 @@ activity.period.quarterly=3 Monate activity.period.semiyearly=6 Monate activity.period.yearly=1 Jahr activity.overview=Übersicht -activity.active_prs_count_1=%d aktiver Pull-Request -activity.active_prs_count_n=%d aktive Pull-Requests activity.merged_prs_count_1=Zusammengeführter Pull-Request activity.merged_prs_count_n=Zusammengeführte Pull-Requests activity.opened_prs_count_1=Vorgeschlagener Pull-Request @@ -1975,8 +1969,6 @@ activity.title.prs_merged_by=%s durch %s zusammengeführt activity.title.prs_opened_by=%s von %s vorgeschlagen activity.merged_prs_label=Zusammengeführt activity.opened_prs_label=Vorgeschlagen -activity.active_issues_count_1=%d aktives Issue -activity.active_issues_count_n=%d aktive Issues activity.closed_issues_count_1=Geschlossenes Issue activity.closed_issues_count_n=Geschlossene Issues activity.title.issues_1=%d Issue @@ -2668,8 +2660,6 @@ settings.enforce_on_admins = Erzwinge diese Regel für alle Repositoryadministra settings.event_pull_request_enforcement = Erzwingung size_format = %[1]s: %[2]s, %[3]s: %[4]s issues.archived_label_description = (Archiviert) %s -release.download_count_one = %s Download -release.download_count_few = %s Downloads release.system_generated = Dieser Anhang wurde automatisch generiert. settings.add_webhook.invalid_path = Der Pfad darf kein „.“ oder „..“ enhalten und darf auch nicht leer sein. Er darf auch nicht mit einem Schrägstrich anfangen oder enden. settings.sourcehut_builds.manifest_path = Build-Manifest-Pfad diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 890e7e2db7..fa31d3a2df 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -375,10 +375,6 @@ code=Κώδικας code_last_indexed_at=Τελευταία δημιουργία ευρετηρίου: %s relevant_repositories_tooltip=Τα αποθετήρια που είναι forks ή που δεν έχουν θέμα, εικονίδιο και περιγραφή είναι κρυμμένα. relevant_repositories=Εμφανίζονται μόνο αποθετήρια που θεωρούμε «σχετικά» για εσάς. Εμφάνιση αποτελεσμάτων χωρίς φίλτρο. -stars_one = %d αστέρι -stars_few = %d αστέρια -forks_one = %d fork -forks_few = %d forks [auth] create_new_account=Δημιουργία λογαριασμού @@ -1969,8 +1965,6 @@ activity.period.quarterly=3 μήνες activity.period.semiyearly=6 μήνες activity.period.yearly=1 έτος activity.overview=Επισκόπηση -activity.active_prs_count_1=%d ενεργό pull request -activity.active_prs_count_n=%d ενεργά pull request activity.merged_prs_count_1=Συγχωνευμένο pull request activity.merged_prs_count_n=Συγχωνευμένα pull request activity.opened_prs_count_1=Νέα pull request @@ -1983,8 +1977,6 @@ activity.title.prs_merged_by=%s συγχωνεύθηκε από %s activity.title.prs_opened_by=%s προτάθηκε από %s activity.merged_prs_label=Συγχωνευμένο activity.opened_prs_label=Προτεινόμενα -activity.active_issues_count_1=%d ενεργό ζήτημα -activity.active_issues_count_n=%d ενεργά ζητήματα activity.closed_issues_count_1=Κλεισμένα ζητήματα activity.closed_issues_count_n=Κλειστά ζητήματα activity.title.issues_1=%d ζήτημα @@ -2659,8 +2651,6 @@ size_format = %[1]s: %[2]s, %[3]s: %[4]s issues.archived_label_description = (Αρχειοθετημένη) %s vendored = Εξωτερικό open_with_editor = Άνοιγμα με %s -release.download_count_one = %s λήψη -release.download_count_few = %s λήψεις release.system_generated = Αυτό το αρχείο παράγεται αυτόματα. pulls.ready_for_review = Έτοιμο για αξιολόγηση; settings.rename_branch_failed_protected = Δεν είναι δυνατή η μετονομασία του κλάδου %s, επειδή είναι προστατευόμενος. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 0378160338..118a0ee540 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -393,10 +393,6 @@ issues.in_your_repos = In your repositories [explore] repos = Repositories users = Users -stars_one = %d star -stars_few = %d stars -forks_one = %d fork -forks_few = %d forks organizations = Organizations go_to = Go to code = Code @@ -440,7 +436,6 @@ reset_password_wrong_user = You are signed in as %s, but the account recovery li password_too_short = Password length cannot be less than %d characters. non_local_account = Non-local users cannot update their password through the Forgejo web interface. verify = Verify -;As https://codeberg.org/forgejo/forgejo/issues/2809 progresses, please update this error message if possible unauthorized_credentials = Credentials are incorrect or have expired. Retry your command or see %s for more information scratch_code = Scratch code use_scratch_code = Use a scratch code @@ -837,7 +832,7 @@ add_email_success = The new email address has been added. email_preference_set_success = Email preference has been set successfully. add_openid_success = The new OpenID address has been added. keep_email_private = Hide email address -keep_email_private_popup = Your email address will not be shown on your profile and will not be the default for commits made via the web interface, like file uploads, edits, and merge commits. Instead, a special address %s can be used to link commits to your account. This option will not affect existing commits. +keep_email_private_popup = Email address will not be shown on the profile page and will not be the default for commits made via the web interface, like file uploads, edits, and merge commits. Instead, a special address %s can be used to link commits to the user account. This option will not affect existing commits. keep_pronouns_private = Only show pronouns to authenticated users keep_pronouns_private.description = This will hide your pronouns from visitors that are not logged in. openid_desc = OpenID lets you delegate authentication to an external provider. @@ -1579,6 +1574,7 @@ issues.filter_poster = Author issues.filter_poster_no_select = All authors issues.filter_type = Type issues.filter_type.all_pull_requests = All pull requests +issues.filter_type.all_issues = All issues 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 @@ -1722,8 +1718,8 @@ issues.lock.notice_3 = - You can always unlock this issue again in the future. issues.unlock.notice_1 = - Everyone would be able to comment on this issue once more. issues.unlock.notice_2 = - You can always lock this issue again in the future. issues.lock.reason = Reason for locking -issues.lock.title = Lock conversation on this issue. -issues.unlock.title = Unlock conversation on this issue. +issues.lock.title = Lock conversation +issues.unlock.title = Unlock conversation issues.comment_on_locked = You cannot comment on a locked issue. issues.delete = Delete issues.delete.title = Delete this issue? @@ -1823,7 +1819,6 @@ issues.review.resolve_conversation = Resolve conversation issues.review.un_resolve_conversation = Unresolve conversation issues.review.resolved_by = marked this conversation as resolved issues.reference_issue.body = Body -issues.content_history.deleted = deleted issues.content_history.edited = edited issues.content_history.created = created issues.content_history.delete_from_history = Delete from history @@ -2090,8 +2085,6 @@ activity.period.quarterly = 3 months activity.period.semiyearly = 6 months activity.period.yearly = 1 year activity.overview = Overview -activity.active_prs_count_1 = %d active pull request -activity.active_prs_count_n = %d active pull requests activity.merged_prs_count_1 = Merged pull request activity.merged_prs_count_n = Merged pull requests activity.opened_prs_count_1 = Proposed pull request @@ -2104,8 +2097,6 @@ activity.title.prs_merged_by = %s merged by %s activity.title.prs_opened_by = %s proposed by %s activity.merged_prs_label = Merged activity.opened_prs_label = Proposed -activity.active_issues_count_1 = %d active issue -activity.active_issues_count_n = %d active issues activity.closed_issues_count_1 = Closed issue activity.closed_issues_count_n = Closed issues activity.title.issues_1 = %d issue @@ -2721,8 +2712,6 @@ release.tag_name_already_exist = A release with this tag name already exists. release.tag_name_invalid = The tag name is not valid. release.tag_name_protected = The tag name is protected. release.downloads = Downloads -release.download_count_one = %s download -release.download_count_few = %s downloads release.add_tag_msg = Use the title and content of release as tag message. release.hide_archive_links = Hide automatically generated archives release.hide_archive_links_helper = Hide automatically generated source code archives for this release. For example, if you are uploading your own. @@ -3111,7 +3100,7 @@ emails.not_updated = Failed to update the requested email address: %v emails.duplicate_active = This email address is already active for a different user. emails.change_email_header = Update email properties emails.change_email_text = Are you sure you want to update this email address? -emails.delete = Delete Email +emails.delete = Delete email address emails.delete_desc = Are you sure you want to delete this email address? emails.deletion_success = The email address has been deleted. emails.delete_primary_email_error = You can not delete the primary email. @@ -3854,7 +3843,6 @@ type-3.display_name = Organization project [git.filemode] changed_filemode = %[1]s → %[2]s -; Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", … directory = Directory normal_file = Normal file executable_file = Executable file diff --git a/options/locale/locale_eo.ini b/options/locale/locale_eo.ini index df40c8eb93..45aaf379c9 100644 --- a/options/locale/locale_eo.ini +++ b/options/locale/locale_eo.ini @@ -98,7 +98,7 @@ ok = Bone download_logs = Elsuti protokolojn unknown = Nekonata issues = Eraroj -error404 = Aŭ tiu ĉi paĝo ne ekzistas, estis forigitavi ne rajtas vidi ĝin. +error404 = Aŭ tiu ĉi paĝo ne ekzistas, estas forigitavi ne rajtas vidi ĝin. retry = Reprovi activities = Aktivecoj confirm_delete_selected = Konfirmi forigon de ĉiu elektito? @@ -141,10 +141,11 @@ new_migrate.link = Novan migrigon new_org.link = Novan organizaĵon error413 = Vi plenkonsumis vian kvoton. twofa_scratch = Sukuranta kodo por duobla aŭtentikigo +copy_path = Kopii dosiervojon [editor] buttons.list.ordered.tooltip = Aldoni nombran liston -buttons.bold.tooltip = Aldoni grasan tekston +buttons.bold.tooltip = Aldoni grasan tekston (Ctrl+B / ⌘B) buttons.quote.tooltip = Citi tekston buttons.code.tooltip = Aldoni kodtekston buttons.list.unordered.tooltip = Aldoni punktan liston @@ -154,7 +155,7 @@ buttons.ref.tooltip = Citi eraron aŭ tirpeton buttons.list.task.tooltip = Aldoni liston de taskoj buttons.enable_monospace_font = Ŝalti egallarĝan signoformaron buttons.mention.tooltip = Mencii uzanton aŭ grupon -buttons.italic.tooltip = Aldoni oblikvan tekston +buttons.italic.tooltip = Aldoni oblikvan tekston (Ctrl+I / ⌘I) buttons.link.tooltip = Aldoni ligilon buttons.disable_monospace_font = Malsalti egallarĝan signoformaron buttons.indent.tooltip = Krommarĝeni erojn je unu nivelo @@ -166,6 +167,9 @@ table_modal.placeholder.content = Enhavo table_modal.label.rows = Horizontaloj table_modal.label.columns = Vertikaloj link_modal.description = Priskribo +link_modal.header = Aldoni ligilon +link_modal.url = Url +link_modal.paste_reminder = Aludo: kun URL en via tondujo, vi povas alglui senpere al la redaktilo por krei ligilon. [aria] navbar = Esplora breto @@ -202,6 +206,7 @@ install_desc = Simple aŭ Forgejon! Aliĝu kaj helpu nin plibonigi la projekton. Ne timu kontribui! +platform_desc = Forgejo estas konfirmita ruli sur liberaj operaciumoj kiel Linukso kaj FreeBSD, kaj malsamaj arkitekturprocesoroj. Elektu tion, kion vi preferas. [install] title = Komenca agordado @@ -311,7 +316,7 @@ enable_update_checker = Aktivigi novversian kontrolanton password_algorithm = Pasvorthaketiga algoritmo env_config_keys = Mediagordoj invalid_password_algorithm = Malvalida pasvorthakeita algoritmo -password_algorithm_helper = Agordas la pasvorthaketigan algoritmon. Algoritmoj havas malsamajn postulojn kaj efikecojn. La algoritmo argon2 sufiĉe sekuras, sed postulas multan memoron kaj eble ne taŭgas por nepotencaj serviloj. +password_algorithm_helper = Agordu la pasvorthaketigan algoritmon. Algoritmoj havas malsamajn postulojn kaj efikecojn. La algoritmo argon2 sufiĉe sekuras, sed postulas multan memoron kaj eble ne taŭgas por nepotencaj serviloj. internal_token_failed = Malsukcesis krei internan ĵetonon: %v smtp_from_invalid = La «Sendu retleterojn kiel» adreso malvalidas allow_only_external_registration = Permesi registriĝon nur per fremdaj servoj @@ -351,10 +356,6 @@ organizations = Organizaĵoj relevant_repositories_tooltip = Deponejoj disbranĉiĝintaj, sentemaj, senemblemaj, kaj senpriskribaj estas kaŝitaj. relevant_repositories = Sole montras aktualajn deponejojn, montri senfiltrajn rezultojn. code_last_indexed_at = Plejfreŝe esplorita je %s -forks_few = %d disbranĉigoj -stars_one = %d stelo -forks_one = %d disbranĉigo -stars_few = %d steloj [auth] disable_register_mail = Retpoŝta konfirmado dum registriĝo estas malaktivigita. @@ -398,7 +399,7 @@ openid_register_title = Krei novan konton email_domain_blacklisted = Vi ne povas registriĝi per via retpoŝtadreso. verify = Konfirmi oauth_signup_submit = Finfari konton -prohibit_login_desc = Via konto estas suspendita kaj ne povas interagi kun la instanco. Bonvolu kontakti vian retejestron por regajni aliron. +prohibit_login_desc = Via konto estas suspendita kaj ne povas interagi kun la instanco. Bonvolu kontakti la retejestron por regajni aliron. openid_connect_desc = La elektita OpenID URI estas nekonata. Ligu ĝin al nova konto ĉi tie. oauth.signin.error = Eraris traktante aprobpeton. Se plu eraros, bonvolu kunparoli la retejestron. invalid_code = Via konfirmkodo malvalidas aŭ eksdatiĝis. @@ -428,7 +429,7 @@ hint_login = Ĉu vi jam havas konton? Salutu nun! hint_register = Ĉu vi bezonas konton? Reĝistriĝi nun. sign_up_button = Reĝistriĝi nun. sign_in_openid = Daŭrigi kun OpenID -back_to_sign_in = Reen en la saluton +back_to_sign_in = Reen en la ensaluton use_onetime_code = Uzi unufojan kodon [mail] @@ -484,13 +485,19 @@ password_change.text_1 = La pasvorto de via konto ĵus ŝanĝiĝis. primary_mail_change.subject = Via ĉefa retpoŝtadreso ŝanĝiĝis totp_disabled.text_1 = La tempobazita unufoja pasvorto (TOTP) en via konto ĵus malaktiviĝis. admin.new_user.text = Bonvolu klaki ĉi tie por konduki ĉi tiun uzanton el la administranta agordilo. -removed_security_key.subject = Sekureca ŝlosilo estas forigita +removed_security_key.subject = Sekurŝlosilo estas forigita removed_security_key.text_1 = La sekureca ŝlosilo "%[1]s" ĵus estas forigita de via konton. totp_enrolled.text_1.has_webauthn = Vi ĵus aktivigis TOTP-n por via konto. Tio volas diri ke por ĉiuj venontaj salutoj al via konto, vi povus uzi TOTP-n kiel 2FA metodo aŭ ajnan sekurecan ŝlosilon. totp_enrolled.text_1.no_webauthn = Vi ĵus aktivigis TOTP-n por via konto. Tio volas diri ke por ĉiuj venontaj salutoj al via konto, vi devos uzi TOTP-n kiel 2FA metodo. removed_security_key.no_2fa = Ne estas aliaj 2FA agorditaj metodoj, tio estas ke ne plus necesas uzi 2FA-n por saluti. totp_disabled.no_2fa = Ne estas plu aliaj 2FA agorditaj metodoj, tio estas ke ne plus necesas uzi 2FA-n por saluti. account_security_caution.text_1 = Se tio estis vi, vi povas sekure ignori ĉi tiun retmesaĝon. +account_security_caution.text_2 = Se ne estis vi, via konto estas kompromitata. Bonvolu kontakti la administrantojn de la retpaĝaro. +issue_assigned.pull = @%[1]s asignis al vi la tirpeton %[2]s en la deponejo %[3]s. +issue.action.review_dismissed = @%[1]s maldungis la lastan revizion de %[2]s por ĉi tiu tirpeto. +repo.transfer.subject_to_you = %s volas reposedigi la deponejon "%s" al vi +totp_enrolled.subject = Vi aktivigis TOTP-n kiel 2FA metodo +issue_assigned.issue = @%[1]s asignis al vi ĉi tiun eraron %[2]s en la deponejo %[3]s. [form] TeamName = Gruponomo @@ -559,6 +566,15 @@ password_not_match = La pasvortoj ne samas. last_org_owner = Vi ne povas forigi la lastan uzanton de la «posendantoj» grupo. Organizaĵo bezonas almenaŭ unu posedanton. still_has_org = "Via konto anas de almenaŭ unu organizaĵoj, forlasu ilin unue." invalid_ssh_key = Ne povis konfirmi vian SSH-ŝlosilon: %s +FullName = Plena nomo +Description = Priskribo +Pronouns = Pronomoj +Biography = Biografio +Website = Retpaĝaro +Location = Kieo +To = Branĉonomo +AccessToken = Atingoĵetono +required_prefix = La enigaĵo devas komenciĝi per "%s" [modal] confirm = Konfirmi @@ -727,6 +743,90 @@ permissions_list = Permesoj: permission_write = Lega kaj Skriba key_content = Enhavo key_signature_gpg_placeholder = Komenciĝas per «-----BEGIN PGP SIGNATURE-----» +storage_overview = Stokada superrigardo +quota = Kvoto +pronouns = Pronomoj +pronouns_unspecified = Nespecifitaj +change_username_redirect_prompt.with_cooldown.one = La malnova uzantnomo disponeblos al ĉiuj post atendoperiodo de %[1]d tago. Vi povas ankoraŭ reakiri la malnovan uzantnomon dum ĉi tiu periodo. +change_username_redirect_prompt.with_cooldown.few = La malnova uzantnomo disponeblos al ĉiuj post atendoperiodo de %[1]d tagoj. Vi povas ankoraŭ reakiri la malnovan uzantnomon dum ĉi tiu periodo. +language.title = Defaŭlta lingvo +language.description = Ĉi tiu lingvo estos konservota en via konto kaj estos uzota kiel defaŭlta post kiam vi ensalutos. +language.localization_project = Helpu nin traduki Forgejo-n en via lingvo! Lerni plu. +hints = Sugestoj +update_hints = Ĝisdatigi la sugestojn +update_hints_success = La sugestoj ĝisdatiĝis. +hidden_comment_types.ref_tooltip = Komentoj, kie ĉi tiu eraro estas referencita de alia eraro/enmeto/… +comment_type_group_reference = Referenco +comment_type_group_milestone = Celo +comment_type_group_assignee = Asignito +comment_type_group_lock = Rigli staton +comment_type_group_review_request = Revizia peto +comment_type_group_issue_ref = Referenco de la eraro +keep_activity_private = Kaŝi la aktivecon de la profilpaĝo +keep_activity_private.description = Via publika aktiveco nur videblos al vi kaj la instancaj administrantoj. +enable_custom_avatar = Uzi propran profilbildon +change_password = Ŝanĝi pasvorton +keep_pronouns_private = Montri pronomojn nur al la aŭtentikigitaj uzantoj +keep_pronouns_private.description = Tio maskos viajn pronomojn kontraŭ neaŭtentikigitaj vizitantoj. +add_new_principal = +gpg_token_required = Vi devas disponigi signaturon por la malsupran ĵetono +gpg_token = Ĵetono +gpg_token_help = Vi povas generi signaturon uzante: +ssh_token_required = Vi devas disponigi signaturon for la malsupran ĵetono +ssh_token = Ĵetono +ssh_token_help = Vi povas generi signaturon uzante: +no_activity = Neniu ĵusa aktiveco +token_state_desc = Ĉi tiu ĵetono estis uzata dum la 7 lastaj tagoj +manage_access_token = Atingoĵetonoj +generate_new_token = Generi novan ĵetonon +token_name = Ĵetono-nomo +generate_token = Generi ĵetonon +generate_token_success = Via nova ĵetono estas generita. Kopiu ĝin nun, ĉar ĝi ne estos montrata ree. +access_token_deletion = Forigi atingoĵetonon +delete_token_success = La ĵetono estas forigita. Aplikaĵoj uzantaj ĝin ne atingos plu vian konton. +regenerate_token = Regeneri +access_token_regeneration = Regeneri atingoĵetonon +at_least_one_permission = Vi devas selekti almenaŭ unu permeson por krei ĵetonon +remove_oauth2_application = Forigi OAuth2-aplikaĵon +remove_oauth2_application_success = La aplikaĵo estas forigita. +create_oauth2_application = Krei novan OAuth2-aplikaĵon +create_oauth2_application_button = Krei aplikaĵon +oauth2_application_name = Aplikaĵonomo +save_application = Konservi +oauth2_client_secret = Klienta sekreto +oauth2_client_id = Klienta ID +oauth2_regenerate_secret = Regeneri sekreton +oauth2_regenerate_secret_hint = Ĉu vi perdis vian sekreton? +oauth2_application_edit = Redakti +authorized_oauth2_applications = Permesitaj OAuth2-aplikaĵoj +create_oauth2_application_success = Vi sukcese kreis novan OAuth2 aplikaĵon. +update_oauth2_application_success = Vi sukcese ĝisdatigis la OAuth2 aplikaĵon. +visibility = Uzanta videbleco +visibility.public = Publika +visibility.public_tooltip = Videbla al ĉiuj +visibility.limited = Limigata +visibility.limited_tooltip = Videbla nur al ensalutitaj uzantoj +visibility.private = Privata +visibility.private_tooltip = Videbla nur al membroj de organizaĵoj, kiujn vi aliĝis +blocked_since = Blokata ekde %s +user_unblock_success = La uzanto sukcese estas malblokita. +user_block_success = La uzanto sukcese estas blokita. +user_block_yourself = Vi ne povas bloki vin mem. +quota.applies_to_user = La sekvantaj kvotaj reguloj aplikiĝas al via konto +quota.applies_to_org = La sekvantaj kvotaj reguloj aplikiĝas al ĉi tiu organizaĵo +quota.rule.exceeded = Superita +quota.sizes.all = Ĉio +quota.sizes.repos.all = Deponejoj +quota.sizes.repos.public = Publikaj deponejoj +quota.sizes.repos.private = Privataj deponejoj +quota.sizes.git.all = Git-enhavo +quota.sizes.git.lfs = Git LFS +quota.sizes.assets.attachments.all = Kunsendaĵoj +quota.sizes.assets.attachments.issues = Kunsendaĵoj de eraro +quota.sizes.assets.attachments.releases = Kunsendaĵoj de eldono +quota.sizes.assets.artifacts = Artfaritaĵoj +quota.sizes.assets.packages.all = Pakaĵoj +quota.sizes.wiki = Vikio [user] form.name_reserved = La uzantonomo «%s» estas protektita. @@ -754,6 +854,21 @@ followers_few = %d abonantoj block_user.detail_2 = La uzanto ne povos interagi viajn deponejojn, erarojn, kaj komentojn. block_user = Bloki uzanton change_avatar = Ŝanĝi vian profilbildon… +activity = Publika aktiveco +followers.title.one = Sekvanto +followers.title.few = Sekvantoj +following.title.one = Sekvata +following.title.few = Sekvataj +followers_one = %d sekvanto +following_one = %d sekvataj +overview = Superrigardo +disabled_public_activity = Ĉi tiu uzanto malaktivigis la publikan videblecon de sia aktiveco. +public_activity.visibility_hint.self_public = Via aktiveco videblas al ĉiuj, escepte de interagoj en privataj spacoj. Agordi. +public_activity.visibility_hint.admin_public = Ĉi tiu aktiveco videblas al ĉiuj, sed kiel administranto vi povas ankaŭ vidi interagojn en privataj spacoj. +public_activity.visibility_hint.self_private = Via aktiveco nur videblas al vi kaj la instancaj administrantoj. Agordi. +public_activity.visibility_hint.admin_private = Ĉi tiu aktiveco videblas al vi ĉar vi estas administranto, sed la uzanto volas ke ĝi restu privata. +public_activity.visibility_hint.self_private_profile = Via aktiveco nur videblas al vi kaj la instancaj administrantoj, ĉar via profilo estas privata. Agordi. +form.name_pattern_not_allowed = La ŝablono "%s" ne estas permesata en uzantnomo. [repo] @@ -802,6 +917,7 @@ activity.active_prs_count_n = %d aktivaj tirpetoj settings.archive.text = Arĥivigi la deponejon igus ĝin sole legebla. Ĝi kaŝiĝos de la labortablo. Neniu (ne eĉ vi!) povos fari enmetojn, raportojn, kaj tirpetojn. migrate_items_releases = Eldonoj commits.commits = Enmetoj +rss.must_be_on_branch = Vi devas esti en branĉo por havi RSS-fluon. [org] code = Fontkodo @@ -837,4 +953,27 @@ regexp_tooltip = Interpretas la serĉoterminoj kiel regulesprimo fuzzy = Svaga branch_kind = Serĉi disbranĉigojn… runner_kind = Serĉi rulantojn… -pull_kind = Serĉi tirpetojn… \ No newline at end of file +pull_kind = Serĉi tirpetojn… + +[markup] +filepreview.truncated = La superrigardo estas mallongigita +filepreview.line = Linio %[1]d en %[2]s +filepreview.lines = Linioj %[1]d ĝis %[2]d en %[3]s + +[actions] +variables.creation.success = La variablo "%s" estas aldonita. +variables.update.failed = Ne eblas redakti la variablon. +variables.update.success = La variablo estas redaktita. + +[projects] +deleted.display_name = Forigita projekto +type-1.display_name = Persona projekto +type-2.display_name = Deponejoprojekto + +[git.filemode] +changed_filemode = %[1]s → %[2]s +directory = Dosierujo +normal_file = Normala dosiero +executable_file = Rulebla dosiero +symbolic_link = Simbola ligilo +submodule = Submodulo \ No newline at end of file diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 0a029655c2..224864c24a 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -375,10 +375,6 @@ code=Código code_last_indexed_at=Indexado por última vez %s relevant_repositories_tooltip=Repositorios que son bifurcaciones o que no tienen ningún tema, ningún icono, y ninguna descripción están ocultos. relevant_repositories=Solo se muestran repositorios relevantes, mostrar resultados sin filtrar. -forks_few = %d bifurcaciones -forks_one = %d bifurcación -stars_few = %d estrellas -stars_one = %d estrella [auth] create_new_account=Registrar cuenta @@ -1960,8 +1956,6 @@ activity.period.quarterly=3 meses activity.period.semiyearly=6 meses activity.period.yearly=1 año activity.overview=Resumen -activity.active_prs_count_1=%d pull request activa -activity.active_prs_count_n=%d pull requests activas activity.merged_prs_count_1=Pull request fusionado activity.merged_prs_count_n=Pull requests fusionados activity.opened_prs_count_1=Pull request propuesta @@ -1974,8 +1968,6 @@ activity.title.prs_merged_by=%s fusionado por %s activity.title.prs_opened_by=%s propuesto por %s activity.merged_prs_label=Fusionado activity.opened_prs_label=Propuesto -activity.active_issues_count_1=%d incidencia activa -activity.active_issues_count_n=%d incidencias activas activity.closed_issues_count_1=Incidencia cerrada activity.closed_issues_count_n=Incidencias cerradas activity.title.issues_1=%d incidencia @@ -2705,10 +2697,8 @@ release.hide_archive_links = Ocultar archivos generados automaticamente settings.mirror_settings.pushed_repository = Repositorio pusheado settings.rename_branch_failed_protected = No se puede renombrar la rama %a porque es un rama protegida. release.invalid_external_url = URL externa inválida: %s -release.download_count_one = %s descarga diff.git-notes.add = Añadir nota diff.git-notes.remove-header = Eliminar nota -release.download_count_few = %s descargas diff.git-notes.remove-body = Esta nota será eliminada. editor.add_tmpl.filename = nombre de fichero issues.reaction.add = Añadir reacción diff --git a/options/locale/locale_et.ini b/options/locale/locale_et.ini index ca52962b8e..2c457058cd 100644 --- a/options/locale/locale_et.ini +++ b/options/locale/locale_et.ini @@ -404,8 +404,6 @@ auths.bind_password = Seo salasõna [explore] users = Kasutajad -stars_few = %d tärni -stars_one = %d tärn [user] starred = Tärniga märgitud hoidlad \ No newline at end of file diff --git a/options/locale/locale_eu.ini b/options/locale/locale_eu.ini index 7df55f2761..a2dc60dfe6 100644 --- a/options/locale/locale_eu.ini +++ b/options/locale/locale_eu.ini @@ -1 +1,2 @@ [common] +home = Etxea diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index da60974680..16c6b0228b 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -1478,8 +1478,6 @@ activity.period.quarterly=سه ماه activity.period.semiyearly=6 ماه activity.period.yearly=1 سال activity.overview=مرور -activity.active_prs_count_1=%d تقاضای واکشی فعال -activity.active_prs_count_n=%d تقاضاهای واکشی فعال activity.merged_prs_count_1=تقاضای واکشی ادغام شد activity.merged_prs_count_n=تقاضاهای واکشی ادغام شد activity.opened_prs_count_1=تقاضای واکشی پیشنهاد شده @@ -1492,8 +1490,6 @@ activity.title.prs_merged_by=%s ادغام شده توسط %s activity.title.prs_opened_by=%s پیشنهاد شده توسط %s activity.merged_prs_label=ادغام شده activity.opened_prs_label=پیشنهاد شده -activity.active_issues_count_1=%d مسئله فعال -activity.active_issues_count_n=%d مسئله فعال activity.closed_issues_count_1=مسئله حل شده activity.closed_issues_count_n=مسائل حل شده activity.title.issues_1=%d مسئله diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 5c6665148e..e537169249 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -40,7 +40,7 @@ webauthn_use_twofa=Käytä kaksivaihesta todennusta puhelimestasi webauthn_error=Turva-avainta ei voitu lukea. webauthn_unsupported_browser=Selaimesi ei tällä hetkellä tue WebAuthnia. webauthn_error_unknown=Tuntematon virhe. Yritä uudelleen. -webauthn_error_insecure=WebAuthn tukee vain suojattuja yhteyksiä. Testatessa HTTP-yhteydellä voit käyttää osoitetta "localhost" tai "127.0.0.1" +webauthn_error_insecure=WebAuthn tukee vain turvallisia yhteyksiä. Testaamista varten HTTP-välityksellä voit käyttää alkuperää "localhost" tai "127.0.0.1" webauthn_error_unable_to_process=Palvelin ei pystynyt käsittelemään pyyntöä. webauthn_error_duplicated=Turva-avainta ei ole sallittu tässä pyynnössä. Varmista, ettei avainta ole jo rekisteröity. webauthn_error_empty=Sinun täytyy asettaa nimi tälle avaimelle. @@ -372,11 +372,7 @@ users=Käyttäjät organizations=Organisaatiot code=Koodi code_last_indexed_at=Viimeksi indeksoitu %s -stars_one = %d tähti -stars_few = %d tähteä relevant_repositories = Vain asiaankuuluvat tietovarastot näytetään, näytä suodattamattomat tulokset. -forks_one = %d forkki -forks_few = %d forkkia go_to = Siirry relevant_repositories_tooltip = Tietovarastot, jotka ovat forkkeja tai joilla ei ole aihetta, kuvaketta tai kuvausta, piilotetaan. @@ -848,7 +844,7 @@ twofa_enroll=Ota kaksivaiheinen todennus käyttöön twofa_disabled=Kaksivaiheinen todennus on otettu pois käytöstä. scan_this_image=Skannaa tämä kuva todennussovelluksellasi: or_enter_secret=Tai kirjoita salainen avain: %s -twofa_enrolled=Tiliisi on otettu käyttöön kaksivaiheinen todennus. Ota kertakäyttöinen palautusavain (%s) talteen turvalliseen paikkaan, sillä se näytetään vain kerran! +twofa_enrolled=Tilisi on otettu mukaan onnistuneesti. Säilytä kertakäyttöistä palautusavainta (%s) turvallisessa paikassa, sillä sitä ei enää näytetä. webauthn_nickname=Nimimerkki @@ -1012,6 +1008,7 @@ ssh_principal_deletion_desc = SSH-varmenneprinsipaalin poistaminen kumoaa sen p ssh_principal_deletion_success = Prinsipaali on poistettu. principal_state_desc = Tätä prinsipaalia on käytetty viimeisen seitsemän päivän aikana regenerate_token_success = Poletti on luotu uudelleen. Sovellukset, jotka käyttivät polettia, eivät enää pääse tilillesi. Kyseiset sovellukset tulee päivittää uudella poletilla. +quota.sizes.assets.all = Resurssit [repo] owner=Omistaja @@ -1072,7 +1069,7 @@ migrate_items_pullrequests=Vetopyynnöt migrate_items_releases=Julkaisut migrate_repo=Suorita tietovaraston migraatio migrate.clone_address=Migraatio/kloonaus URL-osoitteesta -migrate.github_token_desc=Voit laittaa yhden tai useamman pääsypoletin pilkulla erotellen tähän nopeuttaaksesi migraatiota GitHubin rajapinnan tahtirajojen takia. VAROITUS: Tämän ominaisuuden väärinkäyttö voi rikkoa palveluntarjoajan ehtoja ja johtaa tilin estämiseen. +migrate.github_token_desc=Voit lisätä tähän yhden tai useamman tunnuksen pilkuilla erotettuna nopeuttaaksesi migraatiota kiertämällä GitHub API:n nopeusrajoituksen. Varoitus: Tämän ominaisuuden väärinkäyttö voi rikkoa palveluntarjoajan käytäntöä ja johtaa tiliesi sulkemiseen. migrate.permission_denied=Paikallisten tietovarastojen tuominen ei ole sallittua. migrate.failed=Migraatio epäonnistui: %v migrate.migrate_items_options=Lisäkohteiden migraatiota varten vaaditaan pääsypoletti @@ -1081,7 +1078,7 @@ migrate.migrating_failed=Migraatio lähteestä %s epäonnistui. migrate.migrating_git=Suoritetaan Git-datan migraatiota mirror_from=peili kohteelle -forked_from=forkattu lähteestä +forked_from=forkattu tietovarastosta unwatch=Lopeta tarkkailu watch=Tarkkaile unstar=Poista tähti @@ -1117,7 +1114,7 @@ file_permalink=Pysyväislinkki video_not_supported_in_browser=Selaimesi ei tue HTML5:n video-tagia. audio_not_supported_in_browser=Selaimesi ei tue HTML5:n audio-tagia. -blame=Blame +blame=Syyllistynyt download_file=Lataa tiedosto normal_view=Normaali näkymä line=rivi @@ -1180,7 +1177,7 @@ projects.open=Avaa projects.close=Sulje issues.desc=Ongelmien, tehtävien ja merkkipaalujen hallinta. -issues.filter_assignees=Suodata käyttäjiä +issues.filter_assignees=Suodata vastuuhenkilö issues.filter_milestones=Suodata merkkipaalu issues.new=Uusi ongelma issues.new.labels=Nimilaput @@ -1215,7 +1212,7 @@ issues.self_assign_at=`itse otti tämän käsittelyyn %s` issues.change_title_at=`muutti otsikon %s otsikoksi %s %s` issues.delete_branch_at=`poisti haaran %s %s` issues.filter_label=Nimilappu -issues.filter_label_exclude=`Käytä alt + napsautus/rivinvaihto poissulkeaksesi nimilappuja` +issues.filter_label_exclude=Käytä Alt + Click-näppäinyhdistelmää nimiöiden poissulkemiseksi issues.filter_label_no_select=Kaikki nimilaput issues.filter_milestone=Merkkipaalu issues.filter_project=Projekti @@ -1264,9 +1261,9 @@ issues.close_comment_issue=Kommentoi ja sulje issues.reopen_issue=Avaa uudelleen issues.reopen_comment_issue=Kommentoi ja avaa uudelleen issues.create_comment=Kommentoi -issues.closed_at=`sulki tämän ongelman %s` -issues.reopened_at=`uudelleenavasi tämän ongelman %s` -issues.commit_ref_at=`viittasi tähän ongelmaan kommitissa %s` +issues.closed_at=`sulki tämän tukipyynnön %s` +issues.reopened_at=`avasi tämän tukipyynnön uudelleen %s` +issues.commit_ref_at=`viittasi tähän tukipyyntöön sitoumuksesta %s` issues.author=Tekijä issues.role.owner=Omistaja issues.role.member=Jäsen @@ -1417,11 +1414,7 @@ activity.period.quarterly=3 kuukautta activity.period.semiyearly=6 kuukautta activity.period.yearly=1 vuosi activity.overview=Yleiskatsaus -activity.active_prs_count_1=%d aktiivinen vetopyyntö -activity.active_prs_count_n=%d aktiivista vetopyyntöä activity.merged_prs_label=Yhdistetty -activity.active_issues_count_1=%d aktiivinen ongelma -activity.active_issues_count_n=%d aktiivista ongelmaa activity.closed_issues_count_1=suljettu ongelma activity.closed_issues_count_n=suljettua ongelmaa activity.title.issues_created_by=%s luonut %s @@ -1547,7 +1540,7 @@ settings.web_hook_name_larksuite_only =Lark Suite settings.web_hook_name_packagist=Packagist settings.deploy_keys=Toimitusavaimet settings.add_deploy_key=Lisää toimitusavain -settings.deploy_key_desc=Toimitusavaimilla on pelkkä lukuoikeus tietovarastoon. +settings.deploy_key_desc=Käyttöönottoavaimilla voi olla vain luku- tai luku-kirjoitusoikeudet tietovarastoon. settings.is_writable_info=Salli tämän toimitusavaimen työntää tietovarastoon. settings.no_deploy_keys=Toimitusavaimia ei ole käytössä vielä. settings.title=Otsikko @@ -1586,7 +1579,7 @@ settings.archive.header=Arkistoi tämä tietovarasto settings.archive.tagsettings_unavailable=Tagi-asetukset eivät ole käytettävissä arkistoiduissa tietovarastoissa. settings.lfs=LFS settings.lfs_filelist=Tähän tietovarastoon tallennetut LFS-tiedostot -settings.lfs_no_lfs_files=LFS-tiedostoja ei ole tallennettu tähän tietovarastoon. +settings.lfs_no_lfs_files=Tähän tietovarastoon ei ole tallennettu LFS-tiedostoja settings.lfs_findcommits=Etsi kommitteja settings.lfs_lfs_file_no_commits=Tälle LFS-tiedostolle ei löytynyt kommitteja settings.lfs_noattribute=Tällä polulla ei ole lukittavaa attribuuttia oletushaarassa @@ -1801,7 +1794,6 @@ wiki.cancel = Peruuta issues.dependency.remove_header = Poista riippuvuus issues.dependency.issue_remove_text = Riippuvuus poistetaan tästä ongelmasta. Jatketaanko? issues.dependency.pr_remove_text = Riippuvuus poistetaan tästä vetopyynnöstä. Jatketaanko? -release.download_count_few = %s latausta diff.data_not_available = Diff-sisältö ei ole saatavilla diff.image.side_by_side = Rinnakkain release.ahead.target = haaraan %s tämän julkaisun jälkeen @@ -1970,7 +1962,6 @@ branch.create_new_branch = Luo haara haarasta: settings.archive.error_ismirror = Et voi arkistoida peilattua tietovarastoa. branch.warning_rename_default_branch = Olet nimeämässä oletushaaran uudelleen. settings.web_hook_name_msteams = Microsoft Teams -release.download_count_one = %s lataus settings.update_hook_success = Webkoukku on päivitetty. diff.file_before = Ennen diff.file_after = Jälkeen @@ -2148,7 +2139,7 @@ issues.add_label = lisäsi nimilapun %s %s issues.due_date_added = lisäsi eräpäivän %s %s issues.review.add_review_request = pyysi katselmointia käyttäjältä %[1]s %[2]s issues.ref_pull_from = `viittasi tähän vetopyyntöön %[3]s %[1]s` -pulls.commit_ref_at = `viittasi tähän vetopyyntöön kommitista %s` +pulls.commit_ref_at = `viittasi tähän vetopyyntöön sitoumuksesta %s` issues.review.comment = katselmoi %s issues.add_labels = lisäsi nimilaput %s %s issues.review.add_review_requests = pyysi katselmointeja käyttäjiltä %[1]s %[2]s @@ -2329,9 +2320,9 @@ wiki.page_name_desc = Kirjoita tämän wikisivun nimi. Joitain erikoisnimiä ova pulls.blocked_by_changed_protected_files_1 = Tämä vetopyyntö sisältää suojatun tiedoston ja on siksi estetty: pulls.status_checks_warning = Jotkin tarkistukset raportoivat varoituksia pulls.status_checks_error = Jotkin tarkistukset raportoivat virheitä -pulls.reopened_at = `avasi uudelleen tämän vetopyynnön %s` +pulls.reopened_at = `avasi tämän vetopyynnön uudelleen %s` pulls.auto_merge_when_succeed = Yhdistä automaatisesti kun kaikki tarkistukset onnistuvat -signing.wont_sign.error = Tapahtui virhe tarkistaessa voiko kommitin allekirjoittaa. +signing.wont_sign.error = Tapahtui virhe tarkistettaessa, voiko sitoumus allekirjoittaa. signing.wont_sign.twofa = Sinulla tulee olla kaksivaiheinen todennus käytössä, jotta kommitit voi allekirjoittaa. pulls.data_broken = Tämä vetopyyntö on rikki johtuen puuttuvasta forkkitiedosta. pulls.files_conflicted = Tämä vetopyyntö sisältää muutoksia, jotka ovat ristiriidassa kohdehaaran kanssa. @@ -3051,7 +3042,7 @@ auths.attribute_username_placeholder = Jätä tyhjäksi käyttääksesi Forgejo: auths.oauth2_authURL = Valtuutuksen URL-osoite auths.new_success = Todennus "%s" on lisätty. users.still_own_repo = Tämä käyttäjä omistaa yhden tai useamman tietovaraston. Poista tai siirrä nämä tietovarastot ensin. -dashboard.cleanup_hook_task_table = Siivoa hook_task-taulu +dashboard.cleanup_hook_task_table = Siivoa koukku -_tehtävätaulukko dashboard.delete_old_actions = Poista kaikki vanhat aktiviteetit tietokannasta auths.attribute_mail = Sähköpostiosoitteen attribuutti auths.attribute_ssh_public_key = Julkisen SSH-avaimen attribuutti @@ -3119,7 +3110,7 @@ dashboard.resync_all_sshprincipals = Päivitä ".ssh/authorized_principals"-tied create_repo=loi tietovaraston %s rename_repo=asetti tietovaraston %[1]s uudeksi nimeksi %[3]s transfer_repo=siirsi tietovaraston %s käyttäjälle %s -push_tag=työnsi tagin %[3]s kohteeseen %[4]s +push_tag=työnsi tagin %[3]s tietovarastoon %[4]s delete_tag=poisti tagin %[2]s kohteesta %[3]s compare_commits_general=Vertaa kommitteja create_branch=loi haaran %[3]s tietovarastossa %[4]s @@ -3142,6 +3133,7 @@ watched_repo = aloitti tietovaraston %[2]s tarkkailun approve_pull_request = `hyväksyi %[3]s#%[2]s` starred_repo = lisäsi tähden tietovarastolle %[2]s reject_pull_request = `ehdotti muutoksia kohteeseen %[3]s#%[2]s` +publish_release = `julkaisi %[4]s tietovarastossa %[3]s` [tool] now=nyt @@ -3233,7 +3225,7 @@ helm.install = Asenna paketti komennolla: owner.settings.chef.keypair = Luo avainpari settings.delete.error = Paketin poistaminen epäonnistui. requirements = Vaatimukset -published_by_in = Julkaistu %[1]s, julkaisija %[3]s projektissa %[5]s +published_by_in = Julkaistu %[1]s, julkaisija %[3]s tietovarastossa %[5]s pypi.requires = Vaatii Pythonin alpine.install = Asenna paketti seuraavalla komennolla: debian.repository.components = Komponentit @@ -3355,6 +3347,7 @@ debian.repository = Tietovaraston tiedot conda.registry = Määritä tämä rekisteri Conda-tietovarastoksi .condarc-tiedostossa: container.labels = Nimilaput settings.link.description = Jos linkität paketin tietovarastoon, paketti listataan tietovaraston pakettilistalla. +assets = Resurssit [secrets] creation.failed = Salaisuuden lisääminen epäonnistui. @@ -3523,4 +3516,4 @@ wiki.write = Kirjoita: Luo, päivitä ja poista integroidun wikin sivuja. filepreview.truncated = Esikatselu on typistetty [translation_meta] -test = This is a test string. It is not displayed in Forgejo UI but is used for testing purposes. Feel free to enter "ok" to save time (or a fun fact of your choice) to hit that sweet 100% completion mark :) :) :) \ No newline at end of file +test = Tämä on testimerkkijono. Sitä ei näytetä Forgejo-käyttöliittymässä, mutta sitä käytetään testaustarkoituksiin. Voit vapaasti kirjoittaa "selvä" säästääksesi aikaa (tai käyttää hauskaa faktaa) ja saavuttaaksesi sen makean 100 %:n valmistumisrajan :) \ No newline at end of file diff --git a/options/locale/locale_fil.ini b/options/locale/locale_fil.ini index d5a583861e..c161d6853b 100644 --- a/options/locale/locale_fil.ini +++ b/options/locale/locale_fil.ini @@ -171,10 +171,6 @@ code = Code code_last_indexed_at = Huling na-index %s relevant_repositories_tooltip = Mga repositoryo na isang fork o walang topic, icon, at deskripsyon ay nakatago. relevant_repositories = Ang mga kaugnay na repositoryo ay pinapakita, ipakita ang hindi naka-filter na resulta. -stars_few = %d mga bitwin -forks_one = %d fork -forks_few = %d mga fork -stars_one = %d bituin [aria] footer.software = Tungkol sa software na ito @@ -1856,7 +1852,6 @@ activity.git_stats_file_n = %d mga file activity.git_stats_file_1 = %d file pulls.desc = Paganahin ang mga hiling sa paghila at mga pagsuri sa code. activity.git_stats_exclude_merges = Maliban sa mga pagsali, -activity.active_prs_count_n = %d aktibong mga hiling sa paghila issues.author.tooltip.issue = May-akda ng iysung ito ang user. issues.author.tooltip.pr = May-akda ng hiling sa paghila na ito ang user na ito. issues.dependency.add_error_dep_exists = Umiiral na and dependency. @@ -1925,13 +1920,11 @@ pulls.collapse_files = I-collapse ang lahat ng mga file pulls.add_prefix = Magdagdag ng %s na prefix pulls.still_in_progress = Ginagawa pa? activity.title.prs_1 = %d hiling sa paghila -activity.active_issues_count_n = %d mga aktibong isyu pulls.required_status_check_missing = Nawawala ang ilang mga kinakailangang pagsusuri. pulls.required_status_check_administrator = Bilang tagapangasiwa, maaari mo pa ring isama ang hiling sa paghila na ito. pulls.blocked_by_approvals = Wala pang sapat na pag-apruba ang hiling sa paghila na ito. %d ng %d na pag-apruba ang ibinigay. settings.options = Repositoryo wiki.back_to_wiki = Bumalik sa pahina ng wiki -activity.active_issues_count_1 = %d aktibong isyu activity.closed_issues_count_1 = Saradong isyu settings.federation_apapiurl = Federation URL ng repositoryo na ito. Kopyahin at i-paste ito sa Mga Setting ng Federation ng ibang repositoryo bilang URL ng isang Sinusundan na Repositoryo. pulls.select_commit_hold_shift_for_range = Piliin ang commit. I-hold ang shift + click para pumili ng pagitan @@ -1951,7 +1944,6 @@ pulls.data_broken = Sira ang hiling sa paghila na ito dahil sa nawawalang imporm pulls.files_conflicted = May mga pagbabago ang hiling sa paghila na ito na sumasalungat sa target na branch. pulls.is_checking = Ginagawa pa ang pagsuri ng merge conflict. Subukang muli sa ilang sandali. wiki.welcome_desc = Pinapayagan ng wiki ang pagsulat at pagbahagi ng dokumentasyon sa mga katulong. -activity.active_prs_count_1 = %d aktibong hiling sa paghila settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning = Sa ngayon, magagawa lang ito sa "Bagong Paglipat" na menu. Para sa karagdagang impormasyon, mangyaring kumonsulta sa: settings.mirror_settings.docs.disabled_push_mirror.info = Na-disable ng iyong tagapangasiwa ng site ang mga push mirror. settings.mirror_settings.docs.disabled_push_mirror.instructions = I-set up ang iyong proyekto na awtomatikong hilahin ang mga commit, tag at branch mula sa isa pang repositoryo. @@ -2532,8 +2524,6 @@ release.deletion_success = Binura na ang release. release.deletion_tag_success = Nabura na ang tag na ito. release.tag_name_already_exist = Umiiral na ang release na may pangalan ng tag na ito. release.tag_already_exist = Umiiral na ang pangalan ng tag na ito. -release.download_count_one = %s download -release.download_count_few = %s mga download release.tags_for = Mga tag para sa %s release.system_generated = Awtomatikong na-generate ang attachment na ito. release.type_attachment = Attachment diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index fb79e68da6..782e116d09 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -374,10 +374,6 @@ code=Code code_last_indexed_at=Dernière indexation %s relevant_repositories_tooltip=Les dépôts qui sont des forks ou qui n'ont aucun sujet, aucune icône et aucune description sont cachés. relevant_repositories=Seuls les dépôts pertinents sont affichés, afficher les résultats non filtrés. -stars_few = %d étoiles -forks_one = %d bifurcation -stars_one = %d étoiles -forks_few = %d bifurcations [auth] create_new_account=Créer un compte @@ -1972,8 +1968,6 @@ activity.period.quarterly=3 mois activity.period.semiyearly=6 mois activity.period.yearly=1 an activity.overview=Vue d'ensemble -activity.active_prs_count_1=%d demande d'ajout active -activity.active_prs_count_n=%d demandes d'ajout actives activity.merged_prs_count_1=Demande d'ajout fusionnée activity.merged_prs_count_n=Demandes d'ajout fusionnées activity.opened_prs_count_1=Demande d'ajout proposée @@ -1986,8 +1980,6 @@ activity.title.prs_merged_by=%s fusionnée par %s activity.title.prs_opened_by=%s proposée par %s activity.merged_prs_label=Fusionnée activity.opened_prs_label=Proposée -activity.active_issues_count_1=%d ticket actif -activity.active_issues_count_n=%d tickets actifs activity.closed_issues_count_1=Ticket fermé activity.closed_issues_count_n=Tickets fermés activity.title.issues_1=%d ticket @@ -2666,8 +2658,6 @@ size_format = %[1]s : %[2]s, %[3]s : %[4]s ; %[3]s : %[4]s settings.sourcehut_builds.visibility = Visibilité du job settings.sourcehut_builds.secrets = Secrets settings.sourcehut_builds.manifest_path = Chemin du manifest de build -release.download_count_one = %s téléchargement -release.download_count_few = %s téléchargements release.system_generated = Cet attachement a été généré automatiquement. settings.enforce_on_admins_desc = Les administrateurs du dépôt ne peuvent pas passer outre cette règle. settings.web_hook_name_sourcehut_builds = Builds SourceHut diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index ddf766aa71..41a4f51227 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -1541,8 +1541,6 @@ activity.period.quarterly = 3 mhí activity.period.semiyearly = 6 mhí activity.period.yearly = 1 bhliain activity.overview = Forbhreathnú -activity.active_prs_count_1 = %d Iarratas Tarraingthe Gníomhach -activity.active_prs_count_n = %d Iarratais Tharraing Ghníomhach activity.merged_prs_count_1 = Iarratas Tarraing Cumaisc activity.merged_prs_count_n = Iarratais Tharraing Chomhcheangail activity.opened_prs_count_1 = Iarratas Tarraing Beartaithe @@ -1555,8 +1553,6 @@ activity.title.prs_merged_by = %s a chumasc ag %s activity.title.prs_opened_by = %s arna mholadh ag %s activity.merged_prs_label = Cumaiscthe activity.opened_prs_label = Molta -activity.active_issues_count_1 = %d Eagrán Gníomhach -activity.active_issues_count_n = %d Ceisteanna Gníomhacha activity.closed_issues_count_1 = Saincheist Dúnta activity.closed_issues_count_n = Saincheisteanna Dúnta activity.title.issues_1 = Saincheist %d diff --git a/options/locale/locale_he.ini b/options/locale/locale_he.ini index b9f85bd8af..9a2aabcac9 100644 --- a/options/locale/locale_he.ini +++ b/options/locale/locale_he.ini @@ -334,16 +334,12 @@ issues.in_your_repos = בקרפיפיך [explore] repos = קרפיפים -stars_one = כוכב אחד -stars_few = %d כוכבים code = קוד code_last_indexed_at = אונדקס לאחרונה %s users = אנשים -forks_few = %d מזלוגים organizations = ארגונים relevant_repositories_tooltip = מזלוגים וקרפיפים ללא תיאור, נושא, וסמל לא מוצגים. relevant_repositories = רק קרפיפים רלוונטים מוצגים; אפשר להציג תוצאות לא מסוננות. -forks_one = מזלוג אחד [auth] change_unconfirmed_email_error = שינוי כתובת האימייל כשל: %v @@ -466,7 +462,7 @@ uploaded_avatar_not_a_image = הקובץ שהועלה לא תמונה. [repo] new_advanced = הגדרות מתקדמות -new_advanced_expand = +new_advanced_expand = owner = בעלים repo_name = שם הקרפיף repo_name_helper = שמות קרפיפים טובים הם זכירים, קצרים וייחודיים. diff --git a/options/locale/locale_hi.ini b/options/locale/locale_hi.ini index 37115162e7..297f131b87 100644 --- a/options/locale/locale_hi.ini +++ b/options/locale/locale_hi.ini @@ -224,4 +224,95 @@ confirm_password = पासवर्ड पक्का करें admin_email = ईमेल एड्रेस config_location_hint = ये सञ्चालन के तरीके यहाँ सेव होंगे : install_btn_confirm = इनस्टॉल फॉरगेजो -test_git_failed = git कमांड टेस्ट नहीं हुई: %v \ No newline at end of file +test_git_failed = git कमांड टेस्ट नहीं हुई: %v +sqlite3_not_available = इस फॉरगेजो के वर्शन में SQLite३ नहीं है। कृपया डाउनलोड करें बाइनरी वर्शन यहाँ से %s (“gobuild” वर्शन नहीं करें)। +invalid_db_setting = ये डेटाबेस सेटिंग्स मान्य नहीं हैं %v +invalid_db_table = डेटाबेस टेबल “%s” मान्य नहीं: %v +invalid_repo_path = इस रिपॉजिटरी की मूल पथ मान्य नहीं है: %v +invalid_app_data_path = एप्प की डाटा पथ अमान्य है: %v +run_user_not_match = “यूजर को ऐसे चलाएं” उसरनाम अभी का उसरनाम नहीं है: %s -> %s +internal_token_failed = अंदरूनी टोकन बना नहीं पाए: %v +secret_key_failed = गुप्त चाभी बना नहीं पाए: %v +save_config_failed = संरूपण को सेव नहीं कर पाए: %v +enable_update_checker_helper_forgejo = ये समय-समय पर चेक करेगा फॉरगेजो वर्शन नया है की नहीं TXT DNS रिकॉर्ड से यहाँ release.forgejo.org। +invalid_admin_setting = प्रशासक अकाउंट सेटिंग अमान्य है: %v +invalid_log_root_path = लॉग का रास्ता अमान्य है: %v +no_reply_address = गुप्त ईमेल डोमेन +no_reply_address_helper = डोमेन का नाम उसेर्स के गुप्त ईमेल पते से हैं। जैसे की, जब यूजर “जोई” लॉग करेगा Git पे “जोई@noreply.example.org” अगर गुप्त ईमेल पता पड़ा है “noreply.example.org”। +password_algorithm = पासवर्ड का हैश कलन विधि (algorithm) +invalid_password_algorithm = अमान्य पासवर्ड का हैश कलन विधि (algorithm) +password_algorithm_helper = पासवर्ड हैश अल्गोरिथम सेट करें। अल्गोरिथ्म्स की अलग-अलग ज़रूरतें और मज़बूतियाँ हैं। आर्गन2 अल्गोरिथम मज़बूत है पर मेमोरी ज़्यादा लेता है और छोटे सिस्टम्स के लिए सही नहीं होता। +enable_update_checker = अपडेट चेकर चालू करें +env_config_keys = पर्यावरण संरूपण +env_config_keys_prompt = पर्यावरण वेरिएबल्स को संरूपण फाइल पर भी लागू किया जायेगा: + +[home] +uname_holder = उसरनाम या ईमेल एड्रेस +switch_dashboard_context = डैशबोर्ड का सन्दर्भ बदलें +my_repos = रेपोसिटोरिएस +my_orgs = संस्थाएं +view_home = व्यू: %s +filter = बाकी फिल्टर्स +filter_by_team_repositories = फ़िल्टर करें टीम रेपोसिट्रीज़ से +feed_of = फीड इसकी "%s" +show_archived = संग्रहीत +show_both_archived_unarchived = संग्रहीत और असंग्रहीत देखिए +show_only_archived = सिर्फ संग्रहीत देखिए +show_only_unarchived = सिर्फ असंग्रहीत देखिए +show_private = निजी +show_both_private_public = निजी और सार्वजनिक देखिए +show_only_private = सिर्फ निजी देखिए +show_only_public = सिर्फ सार्वजनिक देखिए +issues.in_your_repos = आपकी रेपोसिटोरिस में + +[explore] +repos = रेपोसिटोरिस +users = उसेर्स +organizations = संस्थाएं +go_to = जाएं इधर +code = कोड +code_last_indexed_at = आखरी संस्थापित %s +relevant_repositories_tooltip = रेपोसिटोरिस जो की फोर्क्स या जिनका शीर्षक नहीं है, आइकॉन नहीं और विश्लेषण नहीं गुप्त हैं। +relevant_repositories = सिर्फ इस काम की रेपोस्टिरिस दिखाई जा रहीं हैं. बिना फ़िल्टर के रिजल्ट दिखाएं। + +[auth] +create_new_account = अकाउंट रजिस्टर करें +disable_register_prompt = रजिस्ट्रेशन खुला नहीं। कृपया साइट प्रशासक से बात करें। +disable_register_mail = ईमेल से रजिस्ट्रेशन पक्का करना बंद हैं अभी। +manual_activation_only = अपने साइट प्रशासक बात करें एक्टिवेशन पूरा करने के लिए। +remember_me = इस डिवाइस को याद रखें +forgot_password_title = पासवर्ड भूल गए +forgot_password = पासवर्ड भूल गए? +hint_login = पहले से अकाउंट है? अभी सिग्न इन करें! +hint_register = अकाउंट चाहिए? रजिस्टर करें +sign_up_button = अभी रजिस्टर करें। +sign_up_successful = अकाउंट बन गया है। स्वागत! +back_to_sign_in = sign in में फिर से जाएं +sign_in_openid = OpenID के साथ आगे बढ़ें + +[mail] +link_not_working_do_paste = क्या कड़ी चल नहीं पाई? उसे कॉपी करके URL बार में जोड़ें। +hi_user_x = नमस्ते %s, +activate_account = कृपया अपना खाता चालू करें +activate_account.text_1 = नमस्ते %[1]s, संपादित करने के लिए शुक्रिया यहां %[2]s! +activate_account.text_2 = कृपया यहां की कड़ी को क्लिक करें अपने अकाउंट को चालू करने के लिए %s: +activate_email = अपना ईमेल एड्रेस पुख्ता करें +activate_email.text = कृपया इस कड़ी को क्लिक करें अपना अकाउंट पुख्ता करने के लिए %s: +admin.new_user.subject = नया यूजर %s अभी sign up हुआ +admin.new_user.user_info = यूजर की जानकारी +admin.new_user.text = कृपया क्लिक करें यहाँ अपने यूजर को प्रशासक पैनल से चलाने के लिए। +register_notify = स्वागत है %s +register_notify.text_1 = ये आपका रजिस्टर्ड ईमेल पुख्ता करने के लिए है %s! +register_notify.text_2 = आप अपने अकाउंट में हस्ताक्षर कर सकते हैं इस उसर-नाम के साथ: %s +register_notify.text_3 = अगर किसी और ने आपका अकाउंट बनाया है, तो आपको चाहिए पासवर्ड संरक्षित करें पहले। +reset_password = अपना अकाउंट निकाल पाएं +reset_password.text = अगर ये आप थे, कृपया इस कड़ी को क्लिक करें अपने अकाउंट को फिर से चालू करने को %s: +password_change.subject = आपका पासवर्ड बदला गया +password_change.text_1 = आपके अकाउंट का पासवर्ड बदला गया है। +primary_mail_change.subject = आपका प्राथमिक ईमेल बदला गया है +primary_mail_change.text_1 = आपका प्राथमिक ईमेल बदला गया है इससे %[1]s. यानी इस ईमेल एड्रेस से आप ईमेल अधिसूचना प्राप्त नहीं कर पाएंगे। +totp_disabled.subject = टीओटीपी रोक दिया गया +totp_disabled.text_1 = समय निर्धारित एक बार के पासवर्ड (टीओटीपी) आपके अकाउंट पर रोक दिया गया। +totp_disabled.no_2fa = और कोई 2FA के तरीके संचालित नहीं हैं, यानी आपके अकाउंट पर 2FA से लॉगिन की ज़रुरत नहीं होगी। +removed_security_key.subject = सुरक्षक चाभी हटाई गई +removed_security_key.text_1 = सुरक्षक चाभी "%[1]s" अभी-अभी आपके अकाउंट से हटाई गई। \ No newline at end of file diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index 3d73336fc4..2bf82ad025 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -1050,8 +1050,6 @@ activity.period.quarterly=3 hónap activity.period.semiyearly=6 hónap activity.period.yearly=1 év activity.overview=Áttekintés -activity.active_prs_count_1=%d Aktív Egyesítési Kérés -activity.active_prs_count_n=%d Aktív Egyesítési Kérések activity.merged_prs_count_1=Végrehajtott Egyesítési Kérés activity.merged_prs_count_n=Végrehajtott egyesítési kérések activity.opened_prs_count_1=Javasolt Egyesítési Kérés @@ -1064,8 +1062,6 @@ activity.title.prs_merged_by=%s egyesítette %s activity.title.prs_opened_by=%s javasolta %s activity.merged_prs_label=Egyesítve activity.opened_prs_label=Javasolta -activity.active_issues_count_1=%d Aktív hibajegy -activity.active_issues_count_n=%d Aktív hibajegy activity.closed_issues_count_1=Lezárt Hibajegy activity.closed_issues_count_n=Lezárt hibajegyek activity.title.issues_1=%d Hibajegy diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index c8ad014ac8..fe89ddd715 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -845,8 +845,6 @@ activity.period.weekly=1 minggu activity.period.monthly=1 bulan activity.period.yearly=1 tahun activity.overview=Tinjauan -activity.active_prs_count_1=%d Tarik permintaan aktif -activity.active_prs_count_n=%d Tarik permintaan aktif activity.merged_prs_count_1=Mengabungkan Permintaan Tarik activity.merged_prs_count_n=Menggabungkan permintaan tarik activity.opened_prs_count_1=Meminta tarik usulan @@ -859,8 +857,6 @@ activity.title.prs_merged_by=%s dibuat oleh %s activity.title.prs_opened_by=%s Dikemukakan oleh %s activity.merged_prs_label=Bergabung activity.opened_prs_label=Dikemukakan -activity.active_issues_count_1=%d Masalah Aktif -activity.active_issues_count_n=%d Masalah aktif activity.closed_issues_count_1=Masalah tertutup activity.closed_issues_count_n=Masalah tertutup activity.title.issues_1=%d Masalah diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 5ee1f63223..d6a0acf702 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -375,10 +375,6 @@ code_last_indexed_at=Ultimo indicizzato %s go_to = Vai a relevant_repositories_tooltip = I repositori che sono biforcazioni o che non hanno argomento, icona, né descrizione sono nascosti. relevant_repositories = Sono visibili solo i repositori pertinenti, mostra risultati non filtrati. -stars_few = %d stelle -forks_one = %d biforcazioni -forks_few = %d biforcazioni -stars_one = %d stella [auth] create_new_account=Registra un account @@ -1792,8 +1788,6 @@ activity.period.quarterly=3 mesi activity.period.semiyearly=6 mesi activity.period.yearly=1 anno activity.overview=Riepilogo -activity.active_prs_count_1=%d richiesta di modifica attiva -activity.active_prs_count_n=%d richieste di modifiche attive activity.merged_prs_count_1=Richiesta di modifica fusa activity.merged_prs_count_n=Richieste di modifica fuse activity.opened_prs_count_1=Richiesta di modifica proposta @@ -1806,8 +1800,6 @@ activity.title.prs_merged_by=%s unita da %s activity.title.prs_opened_by=%s proposta da %s activity.merged_prs_label=Unite activity.opened_prs_label=Proposta -activity.active_issues_count_1=%d segnalazione attiva -activity.active_issues_count_n=%d segnalazioni attive activity.closed_issues_count_1=Segnalazione chiusa activity.closed_issues_count_n=Segnalazioni chiuse activity.title.issues_1=%d segnalazione @@ -2668,8 +2660,6 @@ settings.sourcehut_builds.secrets_helper = Fornisci l'accesso ai segreti della b settings.add_webhook.invalid_path = Il percorso non deve contenere dei componenti quali ".", "..", o una stringa vuota. Non può iniziare o finire con uno slash. n_commit_one = %s commit settings.enforce_on_admins = Imponi questa regola agli amministratori del repository -release.download_count_one = %s download -release.download_count_few = %s downloads release.system_generated = Questo allegato è stato generato automaticamente. pulls.ready_for_review = Pronto alla revisione? editor.commit_id_not_matching = L'ID del commit non combacia con quello del commit che stavi modificando. Conferma le tue modifiche su un nuovo ramo, poi fondilo col ramo desiderato. diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 7ef4703b5f..da6c055645 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -365,10 +365,6 @@ code=コード code_last_indexed_at=最終取得 %s relevant_repositories_tooltip=フォークリポジトリや、トピック、アイコン、説明のいずれも無いリポジトリは表示されません。 relevant_repositories=妥当と思われるリポジトリのみを表示しています。 フィルタリングしない結果を表示。 -stars_few = %d のスター -stars_one = %d のスター -forks_one = %d のフォーク -forks_few = %d のフォーク [auth] create_new_account=アカウントを登録 @@ -1929,8 +1925,6 @@ activity.period.quarterly=3ヶ月 activity.period.semiyearly=6ヶ月 activity.period.yearly=1年 activity.overview=概要 -activity.active_prs_count_1=%d件のアクティブなプルリクエスト -activity.active_prs_count_n=%d件のアクティブなプルリクエスト activity.merged_prs_count_1=マージされたプルリクエスト activity.merged_prs_count_n=マージされたプルリクエスト activity.opened_prs_count_1=提案されたプルリクエスト @@ -1943,8 +1937,6 @@ activity.title.prs_merged_by=%sが%sによってマージされました activity.title.prs_opened_by=%sが%sによって提案されました activity.merged_prs_label=マージ済み activity.opened_prs_label=提案中 -activity.active_issues_count_1=%d件のアクティブなイシュー -activity.active_issues_count_n=%d件のアクティブなイシュー activity.closed_issues_count_1=クローズされたイシュー activity.closed_issues_count_n=クローズされたイシュー activity.title.issues_1=%d件のイシュー @@ -2607,8 +2599,6 @@ activity.navbar.code_frequency = コードの更新頻度 settings.wiki_branch_rename_failure = リポジトリ wiki のブランチ名を正規化できませんでした。 settings.ignore_stale_approvals = 古い承認を無視する open_with_editor = %s で開く -release.download_count_one = %s のダウンロード -release.download_count_few = %s のダウンロード release.system_generated = この添付ファイルは自動的に生成されます。 settings.archive.mirrors_unavailable = リポジトリがアーカイブされている場合、ミラーは利用できません。 settings.rename_branch_failed_protected = 保護されたブランチのため、ブランチ %s の名前を変更できません。 diff --git a/options/locale/locale_kab.ini b/options/locale/locale_kab.ini index fb815015bd..1425ad361f 100644 --- a/options/locale/locale_kab.ini +++ b/options/locale/locale_kab.ini @@ -43,7 +43,7 @@ captcha = CAPČA passcode = Angal n wadduf settings = Iɣewwaren your_profile = Amaɣnu -your_settings = Iɣewwaren +your_settings = Tawila activities = Irmad ok = Ih view = Wali @@ -55,6 +55,12 @@ never = Weṛǧin concept_system_global = Amatu concept_user_organization = Tuddsa filter = Imsizdeg +your_starred = S yitran +issues = Uguren +preview = Taskant +loading = Aɛebbi… +new_repo.title = Akufi amaynut +new_repo.link = Akufi amaynut [user] code = Tangalt @@ -68,9 +74,12 @@ followers.title.few = Imeḍfaṛen following.title.one = Yeṭṭafaṛ following.title.few = Yeṭṭafaṛ unfollow = Ur ṭṭafar ara +overview = Tamuɣli s umata [org] code = Tanglat +team_desc = Aglam +org_desc = Aglam [repo] release.source_code = Tangalt taɣbalut @@ -124,6 +133,82 @@ projects.description_placeholder = Aglam projects.title = Azwel projects.type.none = Ula yiwen projects.template.desc = Tamudemt +mirror_sync = yemtawa +migrate_items_issues = Uguren +star = Rnu-yas itri +branches = Tiṣeḍwa +issues = Uguren +projects.column.edit_title = Isem +projects.column.new_title = Isem +projects.column.color = Ini +projects.open = Ldi +projects.close = Mdel +issues.new.labels = Tibzimin +issues.new.projects = Isenfaren +issues.choose.open_external_link = Ldi +issues.choose.blank = Amezwer +issues.new_label_desc_placeholder = Aglam +issues.filter_label = Tabzimt +issues.filter_project = Asenfar +issues.filter_poster = Ameskar +issues.filter_type = Tawsit +issues.filter_sort = Semyizwer +issues.action_open = Ldi +issues.action_close = Mdel +issues.action_label = Tabzimt +issues.previous = Uzwir +issues.next = Uḍfir +issues.all_title = Akk +issues.draft_title = Arewway +issues.context.edit = Ẓreg +issues.context.delete = Kkes +issues.reopen_issue = Ales tulya +issues.create_comment = Awennit +issues.author = Ameskar +issues.role.owner = Bab-is +issues.role.member = Aɛeggal +issues.role.contributor = Amɛiwen +issues.edit = Ẓreg +issues.cancel = Semmet +issues.save = Sekles +issues.label_edit = Ẓreg +issues.label_delete = Kkes +issues.label.filter_sort.alphabetically = S ugemmay +issues.delete = Kkes +issues.add_time_cancel = Semmet +issues.add_time_hours = Isragen +issues.due_date_form = yyyy-mm-dd +issues.due_date_form_edit = Ẓreg +issues.dependency.cancel = Semmet +issues.content_history.options = Tixtiṛiyin +pulls.made_using_agit = AGit +milestones.open = Ldi +milestones.close = Mdel +milestones.title = Azwel +milestones.desc = Aglam +milestones.clear = Sfeḍ +milestones.cancel = Semmet +milestones.filter_sort.name = Isem +wiki = Awiki +wiki.page = Asebter +wiki.new_page = Asebter +wiki.cancel = Semmet +wiki.edit_page_button = Ẓreg +wiki.pages = Isebtar +activity = Armud +activity.navbar.contributors = Iwiziwen +activity.overview = Tamuɣli s umata +issues.label_description = Aglam +no_desc = Ur yesɛi ara aglam +projects.description = Aglam (d afrayan) +issues.label_title = Isem +issues.label_color = Ini +settings.slack_color = Ini +repo_name = Isem n ukufi +repo_size = Tiddi n ukufi +create_repo = Snulfu-d akufi +settings.confirm_delete = Kkes akufi +settings.delete = Kkes akufi-a [install] admin_password = Awal n uɛeddi @@ -134,6 +219,7 @@ user = Isem n useqdac db_schema = Azenziɣ ssl_mode = SSL path = Abrid +db_name = Isem n taffa n yisefka [admin] notices.type_1 = Akufi @@ -141,6 +227,15 @@ packages.repository = Akufi config.db_user = Isem n useqdac users.name = Isem n useqdac emails.filter_sort.name = Isem n useqdac +notices.desc = Aglam +orgs.name = Isem +repos.name = Isem +packages.name = Isem +auths.name = Isem +config.db_name = Isem +config.mailer_name = Isem +monitor.name = Isem +monitor.queue.name = Isem [search] code_kind = Nadi tangalt… @@ -159,11 +254,14 @@ table_modal.placeholder.content = Agbur table_modal.label.columns = Tigejda link_modal.url = Url link_modal.description = Aglam +table_modal.placeholder.header = Aqeṛṛu +table_modal.label.rows = Izirigen [explore] code = Tanglat repos = Ikufan users = Iseqdacen +organizations = Tuddsiwin [form] UserName = Isem n useqdac @@ -173,6 +271,8 @@ Website = Asmel web Content = Agbur Biography = Tameddurt Location = Asun +RepoName = Isem n ukufi +TeamName = Isem n terbaɛt [aria] footer.links = Iseɣwan @@ -187,6 +287,7 @@ lightweight = D afessas [home] my_repos = Ikufan show_private = Uslig +my_orgs = Tuddsiwin [auth] openid_connect_submit = Tuqqna @@ -234,4 +335,27 @@ quota.sizes.all = Akk quota.sizes.repos.all = Ikufan quota.sizes.assets.attachments.all = Imeddayen quota.sizes.assets.packages.all = Ikemmusen -quota.sizes.wiki = Wiki \ No newline at end of file +quota.sizes.wiki = Wiki +appearance = Udem +avatar = Avataṛ +orgs = Tuddsiwin +organization = Tuddsiwin +quota = Amur +location = Asun +comment_type_group_reference = Tamsisɣelt +visibility.private = Uslig +quota.rule.no_limit = War talast +quota.sizes.assets.all = Igburen + +[packages] +arch.version.description = Aglam +alpine.repository.branches = Tiṣeḍwa +alpine.repository.repositories = Ikufan +settings.link.select = Fren akufi + +[actions] +runners.description = Aglam +runners.name = Isem + +[projects] +type-2.display_name = Asenfar n ukufi \ No newline at end of file diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index e5fb9a8757..b8ffcc190d 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -348,8 +348,6 @@ repos=저장소 users=사용자 organizations=조직 code=코드 -stars_one = %d 좋아요 -stars_few = %d 좋아요 [auth] create_new_account=계정 등록 @@ -363,7 +361,7 @@ allow_password_change=사용자에게 비밀번호 변경을 요청 (권장됨) reset_password_mail_sent_prompt=확인 메일이 %s로 전송되었습니다. 받은 편지함으로 도착한 메일을 %s 안에 확인해서 비밀번호 찾기 절차를 완료하십시오. active_your_account=계정 활성화 account_activated=계정이 활성화 되었습니다 -prohibit_login = +prohibit_login = resent_limit_prompt=활성화를 위한 이메일을 이미 전송했습니다. 3분 내로 이메일을 받지 못한 경우 재시도해주세요. has_unconfirmed_mail=안녕하세요 %s, 이메일 주소(%s)가 확인되지 않았습니다. 확인 메일을 받으시지 못하겼거나 새로운 확인 메일이 필요하다면, 아래 버튼을 클릭해 재발송하실 수 있습니다. resend_mail=여기를 눌러 확인 메일 재전송 @@ -1058,8 +1056,6 @@ activity.title.prs_merged_by=%s 가 %s 로부터 병합되었음 activity.title.prs_opened_by=%s 가 %s 로 부터 제안됨 activity.merged_prs_label=병합됨 activity.opened_prs_label=제안중 -activity.active_issues_count_1=%d 개의 활성화된 이슈 -activity.active_issues_count_n=%d 개의 활성화된 이슈 activity.closed_issues_count_1=클로즈된 이슈 activity.closed_issues_count_n=클로즈된 이슈 activity.title.issues_1=이슈 %d개 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index c65185c037..a18dc67ee4 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -375,10 +375,6 @@ code=Kods code_last_indexed_at=Pēdējo reizi indeksēts %s relevant_repositories_tooltip=Glabātavas, kas ir atzarojumi vai kam nav temata, ikonas un apraksta, ir paslēptas. relevant_repositories=Tiek rādītas tikai atbilstošās glabātavas, rādīt neatsijātu iznākumu. -stars_one = %d zvaigzne -stars_few = %d zvaignznes -forks_one = %d atzarojums -forks_few = %d atzarojumi [auth] create_new_account=Izveidot kontu @@ -1249,7 +1245,7 @@ file_history=Vēsture file_view_source=Skatīt avotu file_view_rendered=Skatīt atveidojumu file_view_raw=Apskatīt neapstrādātu -file_permalink=Patstāvīgā saite +file_permalink=Pastāvīgā saite file_too_large=Datne ir pārāk liela, lai to parādītu. invisible_runes_header=Šī datne satur neredzamas unikoda rakstzīmes invisible_runes_description=`Šī datne satur neredzamas unikoda rakstzīmes, kas ir neatšķiramas cilvēkiem, bet dators tās var apstrādāt atšķirīgi. Ja šķiet, ka tas ir ar nolūku, šo brīdinājumu var droši neņemt vērā. Jāizmanto atsoļa taustiņš (Esc), lai atklātu tās.` @@ -1921,7 +1917,7 @@ milestones.filter_sort.least_issues=Vismazāk pieteikumu signing.will_sign=Šis iesūtījums tiks parakstīts ar atslēgu "%s". signing.wont_sign.error=Atgadījās kļūda pārbaudot, vai iesūtījums var tikt parakstīts. -signing.wont_sign.nokey=Nav pieejamas atslēgas, ar ko parakstīt šo iesūtījumu. +signing.wont_sign.nokey=Šajā serverī nav atslēgas, ar ko parakstīt šo iesūtījumu. signing.wont_sign.never=Iesūtījumi nekad netiek parakstīti. signing.wont_sign.always=Iesūtījumi vienmēr tiek parakstīti. signing.wont_sign.pubkey=Iesūtījums netiks parakstīts, jo kontam nav piesaistīta publiska atslēga. @@ -1971,8 +1967,6 @@ activity.period.quarterly=3 mēneši activity.period.semiyearly=6 mēneši activity.period.yearly=1 gads activity.overview=Pārskats -activity.active_prs_count_1=%d atvērts izmaiņu pieprasījums -activity.active_prs_count_n=%d atvērti izmaiņu pieprasījumi activity.merged_prs_count_1=Iekļauts izmaiņu pieprasījums activity.merged_prs_count_n=Iekļauti izmaiņu pieprasījumi activity.opened_prs_count_1=Ierosināts izmaiņu pieprasījums @@ -1985,8 +1979,6 @@ activity.title.prs_merged_by=%s iekļāva %s activity.title.prs_opened_by=%s ierosināja %s activity.merged_prs_label=Iekļauts activity.opened_prs_label=Ierosināts -activity.active_issues_count_1=%d atvērts pieteikums -activity.active_issues_count_n=%d atvērti pieteikumi activity.closed_issues_count_1=Aizvērts pieteikums activity.closed_issues_count_n=Aizvērti pieteikumi activity.title.issues_1=%d pieteikumu @@ -2307,7 +2299,7 @@ settings.packagist_api_token=API pilnvara settings.packagist_package_url=Packagist pakotnes URL settings.deploy_keys=Izvietošanas atslēgas settings.add_deploy_key=Pievienot izvietošanas atslēgu -settings.deploy_key_desc=Izvietošanas atslēgām ir lasīšanas piekļuve glabātavai. +settings.deploy_key_desc=Izvietošanas atslēgām var būt tikai lasīšanas vai lasīšanas un rakstīšanas piekļuve glabātavai. settings.is_writable=Iespējot rakstīšanas piekļuvi settings.is_writable_info=Atļaut šai izvietošanas atslēgai aizgādāt uz glabātavu. settings.no_deploy_keys=Pagaidām nav nevienas izvietošanas atslēgas. @@ -2364,7 +2356,7 @@ settings.protect_protected_file_patterns=Aizsargāto datņu paraugs (vairākus a settings.protect_protected_file_patterns_desc=Aizsargātās datnes nav ļauts tiešā veidā mainīt, pat ja lietotājam šajā zarā ir tiesības pievienot, labot vai izdzēst datnes. Vairākus paraugus var atdalīt ar semikolu (";"). Paraugu pieraksts ir skatāms %[2]s dokumentācijā. Piemēri: .drone.yml, /docs/**/*.txt. settings.protect_unprotected_file_patterns=Neaizsargāto datņu paraugs (vairākus atdala ar semikolu ";") settings.protect_unprotected_file_patterns_desc=Neaizsargātās datnes, kuras ir ļauts izmainīt tiešā veidā, apejot aizgādāšanas ierobežojumu, ja lietotājam ir rakstīšanas piekļuve. Vairāki paraugi ir atdalāmi ar semikolu (";"). Paraugu pierakstu skatīt %[2]s dokumentācijā. Piemēri: .drone.yml, /docs/**/*.txt. -settings.update_protect_branch_success=Zara aizsargāšanas kārtula "%s" tika atjaunināta. +settings.update_protect_branch_success=Zara aizsargāšanas kārtula “%s” tika atjaunināta. settings.remove_protected_branch_success=Zara aizsargāšanas kārtula "%s" tika noņemta. settings.remove_protected_branch_failed=Zara aizsargāšanas kārtulas "%s" noņemšana neizdevās. settings.protected_branch_deletion=Izdzēst zara aizsargāšanu @@ -2659,7 +2651,6 @@ settings.transfer.modal.title = Nodot īpašumtiesības settings.mirror_settings.push_mirror.copy_public_key = Ievietot publisko atslēgu starpliktuvē pulls.agit_explanation = Izveidots ar AGit darbplūsmu. AGit ļauj līdzalībniekiem ieteikt izmaiņas ar "git push" bez atzarojuma vai jauna zara izveidošanas. settings.wiki_rename_branch_main_notices_1 = Šo darbību NEVAR atsaukt. -release.download_count_few = %s lejupielādes/žu settings.rename_branch_failed_protected = Nevar pārdēvēt zaru %s, jo tas ir aizsargāts zars. release.type_external_asset = Ārējs līdzeklis release.hide_archive_links = Paslēpt automātiski izveidotos arhīvus @@ -2671,7 +2662,6 @@ settings.ignore_stale_approvals = Neņemt vērā novecojušus apstiprinājumus release.system_generated = Šis pielikums ir izveidots automātiski. settings.ignore_stale_approvals_desc = Neskaitīt apstiprinājumus, kas tika veikti vecākiem iesūtījumiem (novecojušas izskatīšanas), kopējā izmaiņu pieprasījuma apstiprinājumu skaitā. Neattiecas, ja novecojušas izskatīšanas jau ir atmestas. settings.enforce_on_admins = Uzspiest šo kārtulu glabātavas pārvaldītājiem -release.download_count_one = %s lejupielāde release.hide_archive_links_helper = Šajā laidienā paslēpt automātiski izveidotos pirmkoda arhīvus. Piemēram, ja tiek augšupielādēts savs. diff.git-notes.add = Pievienot piezīmi diff.git-notes.remove-header = Noņemt piezīmi diff --git a/options/locale/locale_nds.ini b/options/locale/locale_nds.ini index 4d5b4772e2..4a5ae5b842 100644 --- a/options/locale/locale_nds.ini +++ b/options/locale/locale_nds.ini @@ -257,10 +257,6 @@ show_both_private_public = Wiest publik un privaat [explore] repos = Repositoriums users = Brukers -stars_one = %d Steern -stars_few = %d Steerns -forks_one = %d Gabel -forks_few = %d Gabels go_to = Gah to code = Quelltext code_last_indexed_at = Tolest indizeert %s @@ -842,7 +838,7 @@ email_deletion_desc = Deese E-Mail-Adress un daarmit verbunnen Informatioon word principal_desc = Deese SSH-Zertifikaat-Höövdmannen sünd mit dienem Konto verbunnen un geven kumpleten Togriep up diene Repositoriums. add_email_confirmation_sent = Eene Utwiesens-E-Mail is an »%s« schickt worden. Um diene E-Mail-Adress uttowiesen, kiek bidde in dienen E-Mail-Ingang un folg de Verwies daarin in de anner %s. ssh_desc = Deese publiken SSH-Slötels sünd mit dienem Konto verbunnen. De tohörig privaate Slötel gifft kumpleten Togriep up diene Repositoriums. SSH-Slötels, wat utwiest worden sünd, könen bruukt worden, um SSH-unnerschreven Git-Kommitterens uttowiesen. -keep_email_private_popup = Diene E-Mail-Adress word vun dienem Profil verbargen un is nich de Normaalweert för Kommitterens, wat du över de Internett-Schnittstee maakst, so as Datei-Upladens, Bewarkens un Tosamenföhrens-Kommitterens. In Stee daarvun kann eene besünnere Adress %s bruukt worden, um Kommitterens mit dienem Konto to verbinnen. Deese Instellen ännert keene bestahn Kommitterens. +keep_email_private_popup = De E-Mail-Adress word vun de Profil verbargen un is nich de Normaalweert för Kommitterens, wat över de Internett-Schnittstee maakt worden, so as Datei-Upladens, Bewarkens un Tosamenföhrens-Kommitterens. In Stee daarvun kann eene besünnere Adress %s bruukt worden, um Kommitterens mit de Brukerkonto to verbinnen. Deese Instellen ännert keene bestahn Kommitterens. ssh_helper = Bruukst du Hülp? Kiek de Inföhren an, wo du diene eegenen SSH-Slötels maakst of hülp gewohnten Probleemen of, över wat man mit SSH mennigmaal strukelt. access_token_desc = Utköört Teken-Verlöövnissen begrenzen dat Anmellen blots up de tohörig API-Padden. Lees de Dokumenteren för mehr Informatioonen. oauth2_confidential_client = Diskreeter Klient. Köör dat för Programmen ut, wat dat Geheemst diskreet behanneln, as Internett-Sieden. Köör dat nich för stedenwies Programmen ut, as Schrievdisk- un Telefoon-Programmens. @@ -1821,7 +1817,6 @@ activity.period.filter_label = Tied: activity.period.daily = 1 Dag activity.period.halfweekly = 3 Dagen activity.overview = Översicht -activity.active_prs_count_1 = %d aktiiv Haalvörslag activity.merged_prs_count_1 = Tosamenföhrt Haalvörslag activity.opened_prs_count_1 = Nejer Haalvörslag activity.title.user_n = %d Brukers @@ -1830,7 +1825,6 @@ activity.title.prs_merged_by = %s vun %s tosamenföhrt activity.title.prs_opened_by = %s vun %s opmaakt activity.merged_prs_label = Tosamenföhrt activity.opened_prs_label = Neei vörslagen -activity.active_issues_count_1 = %d aktiiv Gefall activity.closed_issues_count_1 = Dichtmaakt Gefall activity.title.issues_closed_from = %s vun %s dichtmaakt activity.title.issues_created_by = %s vun %s opmaakt @@ -1977,11 +1971,9 @@ settings.convert_desc = Du kannst deesen Spegel in een normaales Repositorium um settings.transfer_abort_success = Dat Repositoriums-Överdragen na %s is ofbroken worden. signing.wont_sign.error = Bi’m Nakieken, of dat Kommitteren unnerschrieven worden kann, hett dat eenen Fehler geven. signing.wont_sign.pubkey = Deeses Kommitteren word nich unnerschrieven, denn du hest in dienem Konto keenen publiken Slötel angeven. -activity.active_prs_count_n = %d aktiiv Haalvörslagen activity.merged_prs_count_n = Tosamenföhrt Haalvörslagen activity.title.user_1 = %d Bruker activity.title.prs_1 = %d Haalvörslag -activity.active_issues_count_n = %d aktiiv Gefallens activity.title.issues_n = %d Gefallens activity.title.unresolved_conv_n = %d nich lööst Snacks activity.title.releases_1 = %d Publizeren @@ -2402,8 +2394,6 @@ release.tag_name_already_exist = Een Publizeren mit deesem Mark-Naam gifft dat a release.tag_name_invalid = De Mark-Naam is nich gültig. release.tag_name_protected = De Mark-Naam is schütt. release.downloads = Runnerladens -release.download_count_one = %s maal runnerladen -release.download_count_few = %s maal runnerladen release.hide_archive_links = Automatisk maakt Archiven verbargen release.releases_for = Publizerens för %s release.tags_for = Markens för %s diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 66a0d0632b..0e561db19e 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -178,7 +178,7 @@ contributions_format = {contributions} op {month} {day}, {year} [editor] buttons.heading.tooltip = Koptekst toevoegen -buttons.bold.tooltip = Vetgedrukte tekst toevoegen +buttons.bold.tooltip = Vetgedrukte tekst toevoegen (Ctrl+B / ⌘B) buttons.quote.tooltip = Tekst citeren buttons.code.tooltip = Code toevoegen buttons.link.tooltip = Link toevoegen @@ -188,7 +188,7 @@ buttons.mention.tooltip = Vermeld een gebruiker of team buttons.ref.tooltip = Verwijs naar een issue of pull verzoek buttons.switch_to_legacy.tooltip = Gebruik in plaats daarvan de oude editor buttons.enable_monospace_font = Lettertype monospace inschakelen -buttons.italic.tooltip = Schuingedrukte tekst toevoegen +buttons.italic.tooltip = Schuingedrukte tekst toevoegen (Ctrl+I / ⌘I) buttons.list.task.tooltip = Een lijst met taken toevoegen buttons.disable_monospace_font = Lettertype monospace uitschakelen buttons.indent.tooltip = Items één niveau lager plaatsen @@ -375,10 +375,6 @@ code_last_indexed_at=Laatst geïndexeerd %s relevant_repositories = Alleen relevante repositories worden getoond, niet-gefilterde resultaten tonen. go_to = Ga naar relevant_repositories_tooltip = Repositories die forks zijn of die geen onderwerp, geen icoon en geen beschrijving hebben worden verborgen. -stars_one = %d ster -stars_few = %d sterren -forks_one = %d fork -forks_few = %d forks [auth] create_new_account=Account registreren @@ -1790,8 +1786,6 @@ activity.period.quarterly=3 maanden activity.period.semiyearly=6 maanden activity.period.yearly=1 jaar activity.overview=Overzicht -activity.active_prs_count_1=%d actieve pull request -activity.active_prs_count_n=%d actieve pull requests activity.merged_prs_count_1=Samengevoegde pull request activity.merged_prs_count_n=Samengevoegde pull requests activity.opened_prs_count_1=Voorgestelde pull request @@ -1804,8 +1798,6 @@ activity.title.prs_merged_by=%s samengevoegd door %s activity.title.prs_opened_by=%s voorgesteld door %s activity.merged_prs_label=Samengevoegd activity.opened_prs_label=Voorgesteld -activity.active_issues_count_1=%d actieve issue -activity.active_issues_count_n=%d actieve issues activity.closed_issues_count_1=Gesloten issue activity.closed_issues_count_n=Gesloten issues activity.title.issues_1=%d issue @@ -2035,7 +2027,7 @@ settings.packagist_api_token=API token settings.packagist_package_url=Packagist pakket URL settings.deploy_keys=Deploy sleutels settings.add_deploy_key=Deploy sleutel toevoegen -settings.deploy_key_desc=Deploy keys hebben alleen-lezen pull-toegang tot de repository. +settings.deploy_key_desc=Deploy sleutels kunnen alleen-lezen of alleen-lezen-schrijftoegang hebben tot de repository. settings.is_writable=Schrijftoegang inschakelen settings.is_writable_info=Sta deze deploy toets toe om te pushen naar de repository. settings.no_deploy_keys=Er zijn nog geen deploy sleutels. @@ -2663,8 +2655,6 @@ settings.enforce_on_admins = Handhaaf deze regel voor repository admins settings.event_pull_request_enforcement = Handhaving settings.enforce_on_admins_desc = Admins van deze repository kunnen deze regel niet omzeilen. issues.archived_label_description = (Gearchiveerd) %s -release.download_count_one = %s download -release.download_count_few = %s downloads release.system_generated = Deze bijlage wordt automatisch gegenereerd. settings.sourcehut_builds.secrets = Geheimen settings.web_hook_name_sourcehut_builds = SourceHut Builds diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index bbb2a1de13..daac487d30 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -370,10 +370,6 @@ code=Kod code_last_indexed_at=Ostatnio indeksowane %s go_to = Przejdź do relevant_repositories = Wyświetlane są tylko istotne repozytoria, pokaż niefiltrowane wyniki. -stars_one = %d gwiazdka -stars_few = %d gwiazdek -forks_one = %d fork -forks_few = %d forki relevant_repositories_tooltip = Repozytoria, które nie są forkami lub nie mają tematu, ikony i opisu są ukryte. [auth] @@ -1658,8 +1654,6 @@ activity.period.quarterly=3 miesiące activity.period.semiyearly=6 miesięcy activity.period.yearly=1 rok activity.overview=Przegląd -activity.active_prs_count_1=%d aktywny pull request -activity.active_prs_count_n=%d aktywne pull requesty activity.merged_prs_count_1=Scalono pull request activity.merged_prs_count_n=Scalone pull requesty activity.opened_prs_count_1=Proponowany pull request @@ -1672,8 +1666,6 @@ activity.title.prs_merged_by=%s zmergowane przez %s activity.title.prs_opened_by=%s zaproponowane przez %s activity.merged_prs_label=Scalone activity.opened_prs_label=Proponowane -activity.active_issues_count_1=%d aktywne zgłoszenie -activity.active_issues_count_n=%d aktywne zgłoszenia activity.closed_issues_count_1=Zamknięte zgłoszenie activity.closed_issues_count_n=Zamknięte zgłoszenia activity.title.issues_1=%d zgłoszenie @@ -2240,7 +2232,6 @@ settings.sourcehut_builds.secrets_helper = Uprawnij pracę do budowania sekretó vendored = Dołączone editor.add = Dodaj %s release.message = Opisz to wydanie -release.download_count_few = %s pobrania tag.create_tag_operation = Utwórz tag tag.create_success = Tag "%s" został utworzony. editor.commit_email = E-mail commitu @@ -2634,7 +2625,6 @@ branch.delete = Usuń gałąź "%s" wiki.search = Szukaj na wiki activity.commit = Aktywność commitów release.tag_helper_new = Nowy tag. Ten tag będzie utworzony z wydania docelowego. -release.download_count_one = %s pobranie branch.deletion_failed = Nie udało się usunąć gałęzi "%s". pulls.merged_by_fake = przez %[2]s został scalony %[1]s settings.admin_stats_indexer = Indekser statystyk kodu diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 6f561b93b2..4a9bac58a1 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -158,7 +158,7 @@ new_repo.link = Novo repositório new_migrate.link = Nova migração new_org.link = Nova organização test = Teste -error413 = Você esgotou sua cota. +error413 = Você exauriu sua cota. copy_path = Copiar caminho [aria] @@ -222,7 +222,7 @@ platform=Multi-plataforma lightweight=Leve e rápido lightweight_desc=Forgejo utiliza poucos recursos e consegue mesmo rodar no barato Raspberry Pi. Economize energia elétrica da sua máquina! license=Código aberto -license_desc=Está tudo no Forgejo! Contribua e torne este projeto ainda melhor. Não tenha vergonha de contribuir! +license_desc=Está tudo no Forgejo! Junte-se a nós e contribua para tornar este projeto ainda melhor. Não tenha vergonha de contribuir! install_desc = Apenas rode o binário para a sua plataforma, execute-o com Docker, ou obtenha-o empacotado. platform_desc = Forgejo roda em sistemas operacionais livres, como Linux e FreeBSD, assim como em diferentes arquiteturas de processador. Escolha o seu sistema preferido! @@ -375,10 +375,6 @@ code=Código code_last_indexed_at=Última indexação %s relevant_repositories_tooltip=Repositórios que são forks ou que não possuem tópico, nem ícone e nem descrição estão ocultos. relevant_repositories=Apenas repositórios relevantes estão sendo mostrados, mostrar resultados não filtrados. -stars_one = %d estrela -stars_few = %d estrelas -forks_one = %d fork -forks_few = %d forks [auth] create_new_account=Cadastrar conta @@ -528,7 +524,7 @@ totp_disabled.subject = A autenticação em dois fatores foi desabilitada removed_security_key.subject = Uma chave de segurança foi removida removed_security_key.text_1 = A chave de segurança "%[1]s" foi removida de sua conta. account_security_caution.text_1 = Caso tenha sido você, este e-mail pode ser ignorado. -totp_enrolled.subject = Você ativou TOTP como método 2FA +totp_enrolled.subject = Ativação de TOTP como método de A2F totp_disabled.text_1 = A senha de uso único baseada em tempo (TOTP) na sua conta foi desativada. totp_disabled.no_2fa = Já não existem mais outros métodos de autenticação em dois fatores (2FA) configurados, ou seja, não é mais necessário acessar sua conta com 2FA. removed_security_key.no_2fa = Já não existem mais outros métodos de autenticação em dois fatores (2FA) configurados, ou seja, não é mais necessário acessar sua conta com 2FA. @@ -644,7 +640,7 @@ email_domain_is_not_allowed = O domínio do endereço de email da conta %sConfigurar. -public_activity.visibility_hint.self_public = Sua atividade está visível para todos, exceto interações em espaços privados. Configurar. +public_activity.visibility_hint.self_public = Sua atividade está visível para todos, exceto as interações em espaços privados. Clique para configurar. public_activity.visibility_hint.admin_public = Sua atividade está visível para todos, mas como um administrador você também pode ver interações em espaços privados. public_activity.visibility_hint.admin_private = Essa atividade está visível para você porque você é um administrador, mas o usuário dejesa que ela seja mantida em privado. public_activity.visibility_hint.self_private_profile = Sua atividade só é visível para você e para os administradores do servidor porque seu perfil é privado. Configurar. @@ -992,7 +988,7 @@ pronouns_unspecified = Não especificado language.title = Idioma padrão additional_repo_units_hint = Sugira habilitar unidades de repositório adicionais additional_repo_units_hint_description = Exibir uma sugestão para "Habilitar mais" em repositórios que não possuem todas as unidades disponíveis habilitadas. -update_hints = Dicas de atualização +update_hints = Atualizar dicas update_hints_success = As dicas foram atualizadas. keep_activity_private.description = A sua atividade pública estará visível apenas para si e para os administradores do servidor. language.localization_project = Ajude-nos a traduzir Forgejo para o seu idioma! Mais informações. @@ -1303,14 +1299,14 @@ editor.patch=Aplicar correção editor.patching=Corrigindo: editor.fail_to_apply_patch=`Não foi possível aplicar a correção "%s"` editor.new_patch=Novo patch -editor.commit_message_desc=Adicione uma descrição detalhada (opcional)... +editor.commit_message_desc=Adicione uma descrição detalhada (opcional)… editor.signoff_desc=Adicione um assinado-por-committer no final do log do commit. editor.commit_directly_to_this_branch=Commit diretamente no branch %[1]s. editor.create_new_branch=Crie um novo branch para este commit e crie um pull request. editor.create_new_branch_np=Crie um novo branch para este commit. editor.propose_file_change=Propor alteração de arquivo editor.new_branch_name=Nome do novo branch para este commit -editor.new_branch_name_desc=Novo nome do branch... +editor.new_branch_name_desc=Nome do novo ramo… editor.cancel=Cancelar editor.filename_cannot_be_empty=Nome do arquivo não pode ser em branco. editor.filename_is_invalid=O nome do arquivo é inválido: "%s". @@ -1331,7 +1327,7 @@ editor.fail_to_update_file_summary=Mensagem de erro: editor.push_rejected_no_message=A alteração foi rejeitada pelo servidor sem uma mensagem. Por favor, verifique os Git hooks . editor.push_rejected=A alteração foi rejeitada pelo servidor. Por favor, verifique os Git hooks . editor.push_rejected_summary=Mensagem completa de rejeição: -editor.add_subdir=Adicionar um subdiretório... +editor.add_subdir=Adicionar uma pasta… editor.unable_to_upload_files=Ocorreu um erro ao enviar arquivos para "%s": %v editor.upload_file_is_locked=Arquivo "%s" está bloqueado por %s. editor.upload_files_to_dir=`Enviar arquivos para "%s"` @@ -1523,7 +1519,7 @@ issues.action_assignee_no_select=Sem responsável issues.action_check=Marcar/Desmarcar issues.action_check_all=Marcar/Desmarcar todos os itens issues.opened_by=aberto por %[3]s %[1]s -pulls.merged_by=por %[3]s foi aplicado em %[1]s +pulls.merged_by=por %[3]s foi aplicado %[1]s pulls.merged_by_fake=por %[2]s foi aplicado %[1]s issues.closed_by=por %[3]s foi fechada %[1]s issues.opened_by_fake=%[1]s abertas por %[2]s @@ -1942,8 +1938,6 @@ activity.period.quarterly=3 meses activity.period.semiyearly=6 meses activity.period.yearly=1 ano activity.overview=Visão geral -activity.active_prs_count_1=%d pull request ativo -activity.active_prs_count_n=%d pull requests ativos activity.merged_prs_count_1=Pull request com merge concluído activity.merged_prs_count_n=Pull requests com merge concluído activity.opened_prs_count_1=Pull request proposto @@ -1956,8 +1950,6 @@ activity.title.prs_merged_by=%s com merge aplicado por %s activity.title.prs_opened_by=%s proposto(s) por %s activity.merged_prs_label=Merge aplicado activity.opened_prs_label=Proposto -activity.active_issues_count_1=%d issue ativa -activity.active_issues_count_n=%d issues ativas activity.closed_issues_count_1=Issue fechada activity.closed_issues_count_n=Issues fechadas activity.title.issues_1=%d issue @@ -2258,7 +2250,7 @@ settings.packagist_api_token=Token de API settings.packagist_package_url=URL do pacote do Packagist settings.deploy_keys=Chaves de deploy settings.add_deploy_key=Adicionar chave de deploy -settings.deploy_key_desc=As chaves de deploy possuem somente acesso de leitura (pull) ao repositório. +settings.deploy_key_desc=As chaves de deploy podem ter acesso ao repositório somente para leitura ou para leitura e escrita. settings.is_writable=Habilitar acesso de escrita settings.is_writable_info=Permitir que esta chave de deploy faça push para o repositório. settings.no_deploy_keys=Não há nenhuma chave de deploy ainda. @@ -2321,7 +2313,7 @@ settings.block_outdated_branch_desc=O merge não será possível quando o branch settings.default_branch_desc=Selecione um branch padrão para pull requests e commits de código: settings.merge_style_desc=Estilos de merge settings.default_merge_style_desc=Estilo de merge padrão -settings.choose_branch=Escolha um branch... +settings.choose_branch=Escolha um ramo… settings.no_protected_branch=Não há branches protegidos. settings.edit_protected_branch=Editar settings.protected_branch_required_rule_name=Nome da regra é obrigatório @@ -2624,7 +2616,6 @@ activity.navbar.code_frequency = Frequência de código settings.protect_status_check_matched = Correspondente branch.tag_collision = O ramo "%s" não pode ser criado porque já existe uma etiqueta com o mesmo nome no repositório. settings.archive.mirrors_unavailable = Réplicas não estão disponíveis em repositórios arquivados. -release.download_count_one = %s download settings.mirror_settings.docs.no_new_mirrors = O seu repositório está replicando alterações de ou para outro repositório. Observe que não é possível criar novas réplicas no momento. settings.mirror_settings.docs.pull_mirror_instructions = Para configurar uma réplica de outro repositório, consulte: settings.wiki_rename_branch_main_desc = Renomear o branch usado internamente pela Wiki para "%s". Esta ação é permanente e não pode ser desfeita. @@ -2636,7 +2627,6 @@ settings.trust_model.committer.desc = Uma assinatura de commit é considerada "c settings.wiki_branch_rename_success = O nome do ramo da wiki do repositório foi regularizado com sucesso. pulls.nothing_to_compare_have_tag = Os branches/etiquetas escolhidos são iguais. settings.sourcehut_builds.secrets = Segredos -release.download_count_few = %s downloads release.hide_archive_links = Ocultar arquivos gerados automaticamente release.system_generated = Este anexo foi gerado automaticamente. settings.wiki_branch_rename_failure = Falha ao regularizar o nome do ramo da wiki do repositório. @@ -2768,8 +2758,8 @@ issues.filter_no_results_placeholder = Tente ajustar seus filtros de pesquisa. archive.nocomment = Não é possível comentar pois o repositório foi arquivado. migrate.repo_desc_helper = Deixe em branco para importar a descrição existente comment.blocked_by_user = Não é possível comentar pois você foi bloqueado pelo dono ou autor do repositório. -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.branch_behind_few = Este ramo está %[1]d commits atrás de %[2]s +sync_fork.branch_behind_one = Este ramo está %[1]d commit(s) atrás de %[2]s sync_fork.button = Sincronizar settings.event_header_action = Eventos de execução de Actions settings.event_action_failure = Falha @@ -3001,9 +2991,9 @@ dashboard.delete_old_actions.started=A exclusão de todas as atividades antigas dashboard.update_checker=Verificador de atualização dashboard.delete_old_system_notices=Excluir todos os avisos de sistema antigos do banco de dados dashboard.gc_lfs=Coletar lixos dos meta-objetos LFS -dashboard.stop_zombie_tasks=Parar tarefas de actions zumbi -dashboard.stop_endless_tasks=Parar tarefas infinitas de actions -dashboard.cancel_abandoned_jobs=Cancelar trabalhos abandonados de actions +dashboard.stop_zombie_tasks=Parar tarefas de Actions zumbis +dashboard.stop_endless_tasks=Parar tarefas de Actions intermináveis +dashboard.cancel_abandoned_jobs=Cancelar trabalhos abandonados de Actions users.user_manage_panel=Gerenciar contas de usuário users.new_account=Criar conta de usuário @@ -3406,7 +3396,7 @@ notices.delete_success=Os avisos do sistema foram excluídos. identity_access = Identidade e acesso settings = Configurações de administrador users.bot = Robô -dashboard.start_schedule_tasks = Iniciar tarefas de actions programadas +dashboard.start_schedule_tasks = Iniciar tarefas de Actions programadas users.reserved = Reservado emails.change_email_text = Tem certeza de que deseja atualizar este endereço de e-mail? self_check = Autoverificação @@ -3817,7 +3807,7 @@ variables.id_not_exist = A variável com o ID %d não existe. variables.deletion.failed = Falha ao remover a variável. variables.creation.failed = Falha ao adicionar a variável. runs.no_results = Nenhum resultado. -variables.description = As variáveis serão passadas para certas ações e não poderão ser lidas de outra forma. +variables.description = As variáveis serão passadas para certas Actions e não poderão ser lidas de outra forma. workflow.dispatch.trigger_found = Este workflow tem um disparador de evento workflow_dispatch. workflow.dispatch.run = Executar workflow runs.no_runs = O workflow ainda não foi executado. diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 5bf44426e1..eee266e885 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -375,10 +375,6 @@ code=Código code_last_indexed_at=Última indexação %s relevant_repositories_tooltip=Repositórios que são derivações ou que não têm tópico, nem ícone, nem descrição, estão escondidos. relevant_repositories=Apenas estão a ser mostrados os repositórios relevantes. Mostrar resultados não filtrados. -stars_one = %d estrela -stars_few = %d estrelas -forks_one = %d derivação -forks_few = %d derivações [auth] create_new_account=Fazer inscrição @@ -1979,8 +1975,6 @@ activity.period.quarterly=3 meses activity.period.semiyearly=6 meses activity.period.yearly=1 ano activity.overview=Panorama geral -activity.active_prs_count_1=%d pedido de integração vigente -activity.active_prs_count_n=%d pedidos de integração vigentes activity.merged_prs_count_1=Pedido de integração executado activity.merged_prs_count_n=Pedidos de integração executados activity.opened_prs_count_1=Pedido de integração proposto @@ -1993,8 +1987,6 @@ activity.title.prs_merged_by=%s executado(s) por %s activity.title.prs_opened_by=%s proposto por %s activity.merged_prs_label=Integrado activity.opened_prs_label=Proposto -activity.active_issues_count_1=%d questão vigente -activity.active_issues_count_n=%d questões vigentes activity.closed_issues_count_1=Questão encerrada activity.closed_issues_count_n=Questões encerradas activity.title.issues_1=%d questão @@ -2319,7 +2311,7 @@ settings.packagist_api_token=Código da API settings.packagist_package_url=URL do pacote Packagist settings.deploy_keys=Chaves de instalação settings.add_deploy_key=Adicionar chave de instalação -settings.deploy_key_desc=Chaves de instalação têm acesso para puxar do repositório apenas em modo de leitura. +settings.deploy_key_desc=Chaves de instalação podem ter acesso ao repositório apenas para leitura ou de leitura e escrita. settings.is_writable=Habilitar acesso de escrita settings.is_writable_info=Permitir a esta chave de instalação enviar para o repositório. settings.no_deploy_keys=Ainda não existem quaisquer chaves de instalação. @@ -2660,8 +2652,6 @@ wiki.cancel = Cancelar settings.wiki_rename_branch_main = Normalizar o nome do ramo do Wiki settings.enforce_on_admins = Impor esta regra nos administradores de repositórios settings.enforce_on_admins_desc = Os administradores de repositórios não podem contornar esta regra. -release.download_count_one = %s descarga -release.download_count_few = %s descargas release.system_generated = Este anexo é gerado automaticamente. pulls.ready_for_review = Pronto/a para rever? settings.units.units = Unidades diff --git a/options/locale/locale_pt.ini b/options/locale/locale_pt.ini deleted file mode 100644 index cae467f52e..0000000000 --- a/options/locale/locale_pt.ini +++ /dev/null @@ -1,31 +0,0 @@ - - - -[common] -create_new = Criar… -home = Início -dashboard = Painel -explore = Explorar -help = Ajuda -logo = Logótipo -sign_in = Entrar -sign_in_with_provider = Entrar com %s -sign_in_or = ou -sign_out = Sair -sign_up = Registar -register = Registar -powered_by = À base de %s -page = Página -template = Template -notifications = Notificações -link_account = Conectar conta -version = Versão -active_stopwatch = Cronómetro ativo -tracked_time_summary = Resumo do tempo cronometrado baseado nos filtros da lista de issues -user_profile_and_more = Perfil e definições… -signed_in_as = Conectado como -enable_javascript = Este website requer JavaScript. -toc = Índice -licenses = Licenças -return_to_forgejo = Voltar ao Forgejo -toggle_menu = Abrir/fechar menu \ No newline at end of file diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index d3804aa524..8173811c1e 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -375,10 +375,6 @@ code=Код code_last_indexed_at=Последняя индексация %s relevant_repositories_tooltip=Скрыты ответвления и репозитории, не имеющие ни темы, ни значка, ни описания. relevant_repositories=Показаны только релевантные репозитории, показать результаты без фильтрации. -forks_one = %d ответвление -forks_few = %d ответвлений -stars_one = В избранном у %d пользователя -stars_few = В избранном у %d пользователей [auth] create_new_account=Зарегистрировать учётную запись @@ -1940,8 +1936,6 @@ activity.period.quarterly=3 месяца activity.period.semiyearly=6 месяцев activity.period.yearly=1 год activity.overview=Обзор -activity.active_prs_count_1=%d активный запрос слияния -activity.active_prs_count_n=%d активных запросов слияния activity.merged_prs_count_1=Принятый запрос слияния activity.merged_prs_count_n=Принятых запросов слияния activity.opened_prs_count_1=Новый запрос слияния @@ -1954,8 +1948,6 @@ activity.title.prs_merged_by=%s приняты %s activity.title.prs_opened_by=%s предложены %s activity.merged_prs_label=Принято activity.opened_prs_label=Предложено -activity.active_issues_count_1=%d активная задача -activity.active_issues_count_n=%d активных задач activity.closed_issues_count_1=Закрытая задача activity.closed_issues_count_n=Закрытых задач activity.title.issues_1=%d задача @@ -2271,7 +2263,7 @@ settings.packagist_api_token=Токен API settings.packagist_package_url=Ссылка на пакет Packagist settings.deploy_keys=Ключи развёртывания settings.add_deploy_key=Добавить ключ развёртывания -settings.deploy_key_desc=Ключи развёртывания предоставляют доступ к репозиторию только для чтения. +settings.deploy_key_desc=Ключи развёртывания дают доступ к просмотру репозитория и опционально к отправке изменений. settings.is_writable=Разрешить запись settings.is_writable_info=Может ли этот ключ быть использован для выполнения push в репозиторий? Ключи развёртывания всегда имеют доступ на pull. settings.no_deploy_keys=Вы не добавляли ключи развёртывания. @@ -2670,8 +2662,6 @@ settings.web_hook_name_sourcehut_builds = Сборки SourceHut settings.sourcehut_builds.manifest_path = Путь манифеста сборки settings.sourcehut_builds.visibility = Видимость задач settings.sourcehut_builds.secrets = Секреты -release.download_count_one = %s скачивание -release.download_count_few = %s скачиваний release.system_generated = Это вложение сгенерировано автоматически. settings.event_pull_request_enforcement = Форсирование pulls.cmd_instruction_checkout_desc = В репозитории вашего проекта перейдите на эту ветвь и протестируйте изменения. diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 658acd26e9..8ad4571000 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -1334,8 +1334,6 @@ activity.period.quarterly=මාස 3 activity.period.semiyearly=මාස 6 activity.period.yearly=වසර 1 activity.overview=දළ විශ්ලේෂණය -activity.active_prs_count_1=%d ක්රියාකාරී අදින්න ඉල්ලීම -activity.active_prs_count_n=%d ක්රියාකාරී අදින්න ඉල්ලීම් activity.merged_prs_count_1=ඒකාබද්ධ අදින්න ඉල්ලීම activity.merged_prs_count_n=ඒකාබද්ධ අදින්න ඉල්ලීම් activity.opened_prs_count_1=යෝජිත අදින්න ඉල්ලීම @@ -1348,8 +1346,6 @@ activity.title.prs_merged_by=%s විසින් ඒකාබද්ධ කර activity.title.prs_opened_by=%s විසින් යෝජනා කරන ලද %s activity.merged_prs_label=සංයුක්ත කෙරිණි activity.opened_prs_label=යෝජිත -activity.active_issues_count_1=%d ක්රියාකාරී නිකුතුව -activity.active_issues_count_n=%d ක්රියාකාරී ගැටළු activity.closed_issues_count_1=සංවෘත නිකුතුව activity.closed_issues_count_n=සංවෘත ගැටළු activity.title.issues_1=%d නිකුතුව diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini index eafdc9d831..3022570254 100644 --- a/options/locale/locale_sk-SK.ini +++ b/options/locale/locale_sk-SK.ini @@ -376,10 +376,6 @@ code=Zdrojový kód code_last_indexed_at=Naposledy indexované %s relevant_repositories_tooltip=Repozitáre, ktoré sú forkami alebo ktoré nemajú tému, žiadnu ikonu ani popis, sú skryté. relevant_repositories=Zobrazujú sa iba relevantné repozitáre, zobraziť nefiltrované výsledky. -forks_few = %d forky -forks_one = %d fork -stars_one = %d hviezda -stars_few = %d hviezdy [auth] create_new_account=Zaregistrovať účet diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index a71b0ad114..da00fd66ae 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -182,8 +182,8 @@ buttons.quote.tooltip = Citera text buttons.code.tooltip = Lägg till kod buttons.link.tooltip = Lägg till en länk buttons.heading.tooltip = Lägg till rubrik -buttons.bold.tooltip = Lägg till fetstilt text -buttons.italic.tooltip = Lägg till kursiv text +buttons.bold.tooltip = Lägg till fetstilt text (CTRL+B / ⌘B) +buttons.italic.tooltip = Lägg till kursiv text (CTRL+I / ⌘I) buttons.list.unordered.tooltip = Lägg till en punktlista buttons.list.ordered.tooltip = Lägg till en numrerad lista buttons.list.task.tooltip = Lägg till en lista med sysslor @@ -321,7 +321,7 @@ app_slogan_helper = Skriv in din slogan här. Lämna tom för att stänga av. domain = Serverdomän domain_helper = Domän eller värdadress för servern. reinstall_error = Du försöker att installera i en existerande Forgejo-databas -password_algorithm_helper = Ställ in hashalgoritmen för lösenord. Algoritmer har olika krav och styrka. Argon2-algoritmen är ganska säker men använder mycket minne och kan vara olämplig för små system. +password_algorithm_helper = Ställ in hashalgoritmen för lösenord. Algoritmer har olika krav och styrkor. Argon2-algoritmen är ganska säker men använder mycket minne och kan vara olämplig för små system. config_location_hint = Dessa konfigurationsinställningar kommer att sparas i: invalid_db_table = Databastabellen "%s" är ogiltig: %v secret_key_failed = Misslyckades att generera hemlig nyckel: %v @@ -362,10 +362,8 @@ users=Användare organizations=Organisationer code=Kod code_last_indexed_at=Indexerades senast %s -stars_one = %d stjärna go_to = Gå till relevant_repositories = Endast relevanta utvecklingskataloger visas, visa ofiltrerade resultat. -stars_few = %d stjärnor [auth] create_new_account=Registrera konto @@ -650,7 +648,7 @@ activate_email=Skicka aktivering activations_pending=Väntar på aktivering delete_email=Ta Bort email_deletion=Ta bort e-postadress -email_deletion_desc=Mejladressen och relaterad information kommer tas bort från ditt konto. Git-commits med denna mejladress förblir oförändrade. Vill du fortsätta? +email_deletion_desc=Denna mejladress och relaterad information kommer tas bort från ditt konto. Git-commits med denna mejladress förblir oförändrade. Vill du fortsätta? email_deletion_success=Mejladressen har tagits bort. theme_update_success=Ditt tema ändrades. theme_update_error=Det valda temat finns inte. @@ -822,7 +820,7 @@ license_helper_desc=En licens styr vad andra kan och inte kan göra med din kod. readme=README readme_helper=Välj en mall för README-filen readme_helper_desc=Här kan du skriva en fullständig beskrivning för ditt projekt. -auto_init=Initiera utvecklingskatalog (Lägger till .gitignore, License and README) +auto_init=Initiera utvecklingskatalog create_repo=Skapa utvecklingskatalog default_branch=Standardgren default_branch_helper=Den förvalda grenen är bas-gren för pull requests och kod-commits. @@ -866,7 +864,7 @@ migrate_items_wiki=Wiki migrate_items_milestones=Milstenar migrate_items_labels=Etiketter migrate_items_issues=Ärenden -migrate_items_pullrequests=Pull Requester +migrate_items_pullrequests=Pull-förfrågningar migrate_items_merge_requests=Begäran om sammanslagning migrate_items_releases=Releaser migrate_repo=Migrera utvecklingskatalog @@ -966,7 +964,7 @@ editor.propose_file_change=Föreslå filändring editor.new_branch_name_desc=Nytt branchnamn… editor.cancel=Avbryt editor.filename_cannot_be_empty=Filnamnet kan inte vara tomt. -editor.file_changed_while_editing=Filens innehåll har ändrats sedan du påbörjade din ändring.Klicka här för att se ändringarna eller commita ändringarna igen för att skriva över dem. +editor.file_changed_while_editing=Filens innehåll har ändrats sedan du öppnade filen.Klicka här för att se ändringarna eller commita ändringarna igen för att skriva över dem. editor.commit_empty_file_header=Committa en tom fil editor.commit_empty_file_text=Filen du vill committa är tom. Vill du fortsätta? editor.no_changes_to_show=Det finns inga ändringar att visa. @@ -1062,7 +1060,7 @@ issues.remove_self_assignment=`tog bort sin tilldelning %s` issues.change_title_at='ändrade titeln från %s till %s %s' issues.delete_branch_at='tog bort grenen %s %s' issues.filter_label=Etikett -issues.filter_label_exclude=`Använd alt + klicka/enter för att exkludera etiketter` +issues.filter_label_exclude=Använd Alt + klicka/enter för att exkludera etiketter issues.filter_label_no_select=Alla etiketter issues.filter_milestone=Milsten issues.filter_project_none=Inget projekt @@ -1342,8 +1340,6 @@ activity.period.quarterly=3 månader activity.period.semiyearly=6 månader activity.period.yearly=1 år activity.overview=Översikt -activity.active_prs_count_1=%d aktiv pull-förfrågan -activity.active_prs_count_n=%d aktiva pull-förfrågningar activity.merged_prs_count_1=Sammanfogad Pull-förfrågan activity.merged_prs_count_n=Sammanfogade Pull-förfrågningar activity.opened_prs_count_1=Föreslagen pull-förfrågan @@ -1356,8 +1352,6 @@ activity.title.prs_merged_by=%s sammanfogad av %s activity.title.prs_opened_by=%s föreslås av %s activity.merged_prs_label=Sammanfogad activity.opened_prs_label=Föreslagen -activity.active_issues_count_1=%d aktivt ärende -activity.active_issues_count_n=%d aktiva ärenden activity.closed_issues_count_1=Stängt ärende activity.closed_issues_count_n=Stängda ärenden activity.title.issues_1=%d ärende @@ -2247,7 +2241,7 @@ team_kind = Sök team… 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. +code_search_unavailable = Kodsökning är för närvarande inte tillgängligt. Vänligen kontakta webbplatsadministratören. fuzzy_tooltip = Inkludera resultat som är närliggande till söktermen no_results = Inga matchande resultat hittades. fuzzy = Ungefärlig diff --git a/options/locale/locale_tok.ini b/options/locale/locale_tok.ini new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/options/locale/locale_tok.ini @@ -0,0 +1 @@ + diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 04b62259aa..8d2af111c7 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -7,9 +7,9 @@ logo=Logo sign_in=Giriş Yap sign_in_with_provider=%s ile oturum aç sign_in_or=veya -sign_out=Çıkış Yap +sign_out=Çıkış yap sign_up=Kaydol -link_account=Bağlantı hesabı +link_account=Hesap Bağla register=Üye Ol version=Sürüm powered_by=%s tarafından desteklenen @@ -159,7 +159,7 @@ toggle_menu = Menüyü aç-kapa new_migrate.title = Yeni göç new_migrate.link = Yeni göç copy_path = Dizini kopyala -confirm_delete_artifact = "%s" adlı öğeyi silmek istediğinizden emin misiniz? +confirm_delete_artifact = "%s" adlı öğeyi silmek istediğinize emin misiniz? [aria] navbar=Gezinti çubuğu @@ -178,8 +178,8 @@ contributions_format = {day} {month} {year} tarihinde {contributions} katkı [editor] buttons.heading.tooltip=Başlık ekle -buttons.bold.tooltip=Kalın metin ekle -buttons.italic.tooltip=Eğik metin ekle +buttons.bold.tooltip=Kalın metin ekle (Ctrl+B / ⌘B) +buttons.italic.tooltip=Eğik metin ekle (Ctrl+I / ⌘I) buttons.quote.tooltip=Metni alıntıla buttons.code.tooltip=Kod ekle buttons.link.tooltip=Bağlantı ekle @@ -228,7 +228,7 @@ platform_desc = Forgejo'nun Linux ve FreeBSD gibi özgür işletim sistemlerinde [install] install=Kurulum -title=Başlangıç Yapılandırması +title=Başlangıç yapılandırması docker_helper=Eğer Forgejo'yı Docker içerisinde çalıştırıyorsanız, lütfen herhangi bir değişiklik yapmadan önce belgeleri okuyun. require_db_desc=Forgejo MySQL, PostgreSQL, SQLite3 veya TiDB (MySQL protokolü) gerektirir. db_title=Veritabanı ayarları @@ -257,7 +257,7 @@ err_admin_name_is_invalid=Yönetici kullanıcı adı geçersiz general_title=Genel ayarlar app_name=Site Başlığı -app_name_helper=Şirket adınızı buraya girebilirsiniz. +app_name_helper=Buraya örnek adınızı girin. Her sayfada görüntülenecektir. repo_path=Depo kök dizini repo_path_helper=Tüm uzak Git depoları bu dizine kaydedilecektir. lfs_path=Git LFS kök dizini @@ -266,9 +266,9 @@ run_user=Şu Kullanıcı Olarak Çalıştır run_user_helper=Forgejo'nin çalışacağı işletim sistemi kullanıcı adı. Bu kullanıcının depo kök yoluna erişiminin olması gerektiğini unutmayın. domain=Sunucu alan adı domain_helper=Sunucu için alan adı veya ana bilgisayar adresi. -ssh_port=SSH Sunucu Portu -ssh_port_helper=SSH sunucusunun dinleyeceği port numarası. Etkisizleştimek için boş bırakın. -http_port=Forgejo HTTP Dinleme Portu +ssh_port=SSH sunucu portu +ssh_port_helper=SSH sunucusunun dinleyeceği port numarası. SSH sunucusunu devre dışı bırakmak için boş bırakın. +http_port=HTTP dinleme portu http_port_helper=Forgejo'nın web sunucusunun dinleyeceği port numarası. app_url=Forgejo Kök URL app_url_helper=HTTP(S) kopyalama URL'leri ve e-posta bildirimleri için temel adres. @@ -376,10 +376,6 @@ code=Kod code_last_indexed_at=Son dizinlenen %s relevant_repositories_tooltip=Çatal olan veya konusu, simgesi veya açıklaması olmayan depolar gizlenmiştir. relevant_repositories=Sadece ilişkili depolar gösteriliyor, süzülmemiş sonuçları göster. -stars_one = %d yıldız -stars_few = %d yıldız -forks_one = %d çatal -forks_few = %d çatal [auth] create_new_account=Hesap oluştur @@ -1925,8 +1921,6 @@ activity.period.quarterly=3 ay activity.period.semiyearly=6 ay activity.period.yearly=1 yıl activity.overview=Genel Bakış -activity.active_prs_count_1=%d Aktif Değişiklik İsteği -activity.active_prs_count_n=%d Aktif Değişiklik İsteği activity.merged_prs_count_1=Birleştirilmiş Değişiklik İsteği activity.merged_prs_count_n=Birleştirilmiş Değişiklik İsteği activity.opened_prs_count_1=Önerilen Değişiklik İsteği @@ -1939,8 +1933,6 @@ activity.title.prs_merged_by=%s %s tarafından birleştirildi activity.title.prs_opened_by=%s %s tarafından önerildi activity.merged_prs_label=Birleştirilen activity.opened_prs_label=Önerilen -activity.active_issues_count_1=%d Aktif Konu -activity.active_issues_count_n=%d Aktif Konu activity.closed_issues_count_1=Kapalı Konu activity.closed_issues_count_n=Kapalı Konu activity.title.issues_1=%d Konu diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 047f1a4d03..a4ac52f706 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -375,10 +375,6 @@ code_last_indexed_at=Останні індексовані %s relevant_repositories = Показано лише релевантні репозиторії, переглянути результати без фільтру. relevant_repositories_tooltip = Приховано форки, а також репозиторії без теми, значка й опису. go_to = Перейти до -stars_one = %d зірка -stars_few = %d зірок -forks_one = %d форк -forks_few = %d форків [auth] create_new_account=Реєстрація облікового запису @@ -776,7 +772,7 @@ ssh_desc=Ці відкриті ключі SSH повʼязані з вашим principal_desc=Ці настройки SSH сертифікатів вказані у вашому обліковому записі та надають повний доступ до ваших репозиторіїв. gpg_desc=Ці відкриті ключі GPG пов'язані з вашим обліковим записом і використовуються для підтвердження комітів. Тримайте свої приватні ключі в безпеці, оскільки вони дозволяють підписувати коміти вашим особистим підписом. ssh_helper=Потрібна допомога? Дивіться гід на GitHub з генерації ключів SSH або виправлення типових неполадок SSH. -gpg_helper= Потрібна допомога? Перегляньте посібник GitHub про GPG . +gpg_helper=Потрібна допомога? Перегляньте посібник про GPG. 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» key_content_gpg_placeholder=Починається з «-----BEGIN PGP PUBLIC KEY BLOCK-----» add_new_principal=Додати принципал @@ -950,7 +946,7 @@ permissions_public_only = Тільки публічні select_permissions = Виберіть дозволи permissions_access_all = Усі (публічні, приватні й обмежені) create_oauth2_application_success = Ви успішно створили нову програму OAuth2. -keep_email_private_popup = Ваша адреса електронної пошти не буде відображатися у вашому профілі і не буде використовуватися за замовчуванням для комітів, зроблених через веб-інтерфейс, таких як завантаження файлів, редагування і об'єднання комітів. Натомість ви можете використовувати спеціальну адресу %s для прив'язки комітів до свого облікового запису. Ця опція не вплине на існуючі коміти. +keep_email_private_popup = Адреса електронної пошти не буде відображатися у вашому профілі і не буде використовуватися за замовчуванням для комітів, зроблених через веб-інтерфейс, таких як завантаження файлів, редагування і об'єднання комітів. Натомість ви можете використовувати спеціальну адресу %s для прив'язки комітів до свого облікового запису. Ця опція не вплине на існуючі коміти. blocked_since = Заблокований з %s can_not_add_email_activations_pending = Очікується активація, спробуйте ще раз за кілька хвилин, якщо хочете додати нову адресу електронної пошти. ssh_signonly = SSH наразі вимкнено, тому ці ключі використовуються лише для перевірки підпису комітів. @@ -1030,7 +1026,7 @@ ssh_token_help_ssh_agent = або, якщо ви використовуєте а [repo] owner=Власник -owner_helper=Деякі організації можуть не відображатися у випадаючому списку через максимальну кількість репозиторііїв. +owner_helper=Деякі організації можуть не відображатися у випадаючому списку через обмеження на максимальну кількість репозиторіїв. repo_name=Назва репозиторію repo_name_helper=Хороші назви репозиторіїв використовують короткі, унікальні ключові слова що легко запам'ятати. repo_size=Розмір репозиторію @@ -1039,7 +1035,7 @@ template_select=Виберіть шаблон template_helper=Зробити репозиторій шаблоном template_description=Шаблонні репозиторії дозволяють користувачам генерувати нові репозиторії із такою ж структурою директорій, файлами та додатковими налаштуваннями. visibility=Видимість -visibility_description=Тільки власник або члени організації які мають віповідні права, зможуть побачити. +visibility_description=Тільки власник або члени організації, які мають відповідні права, зможуть побачити. visibility_helper_forced=Адміністратор вашого сайту налаштував параметри: всі нові репозиторії будуть приватними. visibility_fork_helper=(Буде змінено видимість усіх форків.) clone_helper=Потрібна допомога у клонуванні? Відвідайте сторінку Допомога. @@ -1384,7 +1380,7 @@ issues.filter_assignee=Виконавець issues.filter_assginee_no_select=Всі виконавці issues.filter_assginee_no_assignee=Немає виконавця issues.filter_type=Тип -issues.filter_type.all_issues=Всі задачі +issues.filter_type.all_issues=Усі задачі issues.filter_type.assigned_to_you=Призначене вам issues.filter_type.created_by_you=Створено вами issues.filter_type.mentioning_you=Вас згадано @@ -1508,7 +1504,7 @@ issues.add_time_minutes=Хвилини issues.add_time_sum_to_small=Час не введено. issues.time_spent_total=Загальний витрачений час issues.time_spent_from_all_authors=`Загальний витрачений час: %s` -issues.due_date=Дата завершення +issues.due_date=Термін виконання issues.push_commit_1=додає %d коміт %s issues.push_commits_n=додає %d коміти(-ів) %s issues.force_push_codes=`примусово залито %[1]s з %[2]s %[8]s до %[4]s %[9]s %[6]s` @@ -1517,10 +1513,10 @@ issues.due_date_form=рррр-мм-дд issues.due_date_form_edit=Редагувати issues.due_date_form_remove=Видалити issues.due_date_not_set=Термін виконання не встановлено. -issues.due_date_added=додав(ла) дату завершення %s %s -issues.due_date_remove=видалив(ла) дату завершення %s %s +issues.due_date_added=додає термін виконання %s %s +issues.due_date_remove=видаляє термін виконання %s %s issues.due_date_overdue=Прострочено -issues.due_date_invalid=Термін дії недійсний або знаходиться за межами допустимого діапазону. Будь ласка, використовуйте формат «рррр-мм-дд». +issues.due_date_invalid=Термін виконання недійсний або знаходиться за межами допустимого діапазону. Будь ласка, використовуйте формат «рррр-мм-дд». issues.dependency.title=Залежності issues.dependency.add=Додати залежність… issues.dependency.cancel=Відмінити @@ -1631,7 +1627,7 @@ pulls.no_merge_desc=Цей запити на злиття неможливо з pulls.no_merge_helper=Увімкніть параметри злиття в налаштуваннях репозиторія або злийте запити на злиття вручну. pulls.no_merge_wip=Цей пулл-реквест не можливо об'єднати, тому-що він вже виконується. pulls.no_merge_not_ready=Цей запит не готовий до злиття, перевірте статус рецензіювання і статус перевірки. -pulls.no_merge_access=Ви не авторизовані, щоб виконати цей запит на злиття. +pulls.no_merge_access=У вас нема дозволу злити цей запит. pulls.merge_pull_request=Створити коміт зі злиттям pulls.rebase_merge_pull_request=Перебазувати, а потім виконати злиття перемотуванням pulls.rebase_merge_commit_pull_request=Перебазувати, а потім створити коміт злиття @@ -1679,7 +1675,7 @@ milestones.completeness=%d%% завершено milestones.create=Створити етап milestones.title=Заголовок milestones.desc=Опис -milestones.due_date=Дата завершення (опціонально) +milestones.due_date=Термін виконання (необов'язково) milestones.clear=Очистити milestones.invalid_due_date_format=Термін виконання має бути у форматі «рррр-мм-дд». milestones.edit=Редагувати етап @@ -1727,8 +1723,6 @@ activity.period.quarterly=3 місяці activity.period.semiyearly=6 місяців activity.period.yearly=1 рік activity.overview=Огляд -activity.active_prs_count_1=%d активний запит на злиття -activity.active_prs_count_n=%d активних запитів на злиття activity.merged_prs_count_1=Об'єднаний запит на злиття activity.merged_prs_count_n=Об'єднані запити на злиття activity.opened_prs_count_1=Запропоновані запити на злиття @@ -1741,8 +1735,6 @@ activity.title.prs_merged_by=%s злито %s activity.title.prs_opened_by=%s запропоновано %s activity.merged_prs_label=Злито activity.opened_prs_label=Запропоновано -activity.active_issues_count_1=%d активна задача -activity.active_issues_count_n=%d активних задач activity.closed_issues_count_1=Закрита задача activity.closed_issues_count_n=Закриті задачі activity.title.issues_1=%d задача @@ -2287,8 +2279,6 @@ migrate.migrating_failed.error = Міграція не вдалася: %s all_branches = Усі гілки settings.tracker_issue_style.regexp_pattern = Шаблон регулярного виразу settings.tracker_issue_style.regexp = Регулярний вираз -release.download_count_one = %s завантаження -release.download_count_few = %s завантажень release.invalid_external_url = Неправильна зовнішня URL-адреса: «%s» issues.role.collaborator_helper = Цього користувача запрошено до співпраці над репозиторієм. settings.add_collaborator_owner = Неможливо додати власника в якості співавтора. @@ -2737,6 +2727,10 @@ tree_path_not_found.commit = Шлях %[1]s не існує в коміті %[2] tree_path_not_found.tag = Шлях %[1]s не існує в тегу %[2]s tree_path_not_found.branch = Шлях %[1]s не існує в гілці %[2]s pulls.recently_pushed_new_branches = Ви надіслали зміни до гілки %[1]s %[2]s +issues.due_date_modified = змінює термін виконання з %[2]s на %[1]s %[3]s +milestones.filter_sort.earliest_due_data = Найближчий термін виконання +milestones.filter_sort.latest_due_date = Найдальший термін виконання +settings.mirror_settings.pushed_repository = Цільовий репозиторій [graphs] contributors.what = внески diff --git a/options/locale/locale_vi.ini b/options/locale/locale_vi.ini index 484fc604a3..11a48b8067 100644 --- a/options/locale/locale_vi.ini +++ b/options/locale/locale_vi.ini @@ -299,7 +299,7 @@ default_keep_email_private = Ẩn địa chỉ thư điện tử theo mặc đ default_keep_email_private.description = Bật ẩn địa chỉ thư điện tử cho người dùng mới theo mặt định để khiến thông tin này không bị lộ ngay sau khi đăng kí. default_allow_create_organization = Cho phép tạo tổ chức theo mặc định default_allow_create_organization.description = Cho phép người dùng mới tạo tổ chức theo mặc định. Khi tuỳ chọn này bị vô hiệu hoá, quản trị viên sẽ phải cấp quyền tạo tổ chức cho người dùng mới. -default_enable_timetracking = +default_enable_timetracking = admin_title = Cài đặt tài khoản quản trị viên admin_setting.description = Việc tạo tài khoản quản trị viên là không bắt buộc. Người dùng đăng kí đầu tiên sẽ tự động trở thành quản trị viên. admin_name = Tên người dùng quản trị viên @@ -351,10 +351,6 @@ issues.in_your_repos = Trong các kho mã của bạn [explore] repos = Kho mã users = Người dùng -stars_one = %d sao -stars_few = %d sao -forks_one = %d phân nhánh -forks_few = %d phân nhánh organizations = Tổ chức go_to = Đi đén code = Mã @@ -684,7 +680,7 @@ update_hints = Cập nhật các gợi ý update_hints_success = Đã cập nhật các gợi ý. hidden_comment_types = Loại bình luận bị ẩn hidden_comment_types_description = Các loại bình luận được chọn ở đây sẽ không hiện trên các trang vấn đề. Ví dụ: Chọn "Nhãn" thì sẽ loại bỏ tất cả các bình luận " đã thêm/bỏ