diff --git a/.deadcode-out b/.deadcode-out index 61c5bcb055..24facdf12e 100644 --- a/.deadcode-out +++ b/.deadcode-out @@ -13,13 +13,6 @@ forgejo.org/models IsErrSHANotFound IsErrMergeDivergingFastForwardOnly -forgejo.org/models/activities - GetActivityByID - NewFederatedUserActivity - CreateUserActivity - GetFollowingFeeds - FederatedUserActivity.loadActor - forgejo.org/models/auth WebAuthnCredentials @@ -27,15 +20,13 @@ forgejo.org/models/db TruncateBeans InTransaction DumpTables + GetTableNames forgejo.org/models/dbfs file.renameTo Create Rename -forgejo.org/models/forgefed - GetFederationHost - forgejo.org/models/forgejo/semver GetVersion SetVersionString @@ -61,17 +52,10 @@ forgejo.org/models/user IsErrExternalLoginUserAlreadyExist IsErrExternalLoginUserNotExist NewFederatedUser - NewFederatedUserFollower IsErrUserSettingIsNotExist GetUserAllSettings DeleteUserSetting GetFederatedUser - GetFederatedUserByUserID - UpdateFederatedUser - GetFollowersForUser - AddFollower - RemoveFollower - IsFollowingAp forgejo.org/modules/activitypub NewContext @@ -102,24 +86,14 @@ forgejo.org/modules/eventsource Event.String forgejo.org/modules/forgefed - NewForgeFollowFromAp NewForgeFollow - ForgeFollow.MarshalJSON - ForgeFollow.UnmarshalJSON - ForgeFollow.Validate NewForgeUndoLike ForgeUndoLike.UnmarshalJSON ForgeUndoLike.Validate - NewForgeUserActivityFromAp - NewForgeUserActivity - ForgeUserActivity.Validate NewPersonIDFromModel GetItemByType JSONUnmarshalerFn NotEmpty - NewForgeUserActivityNoteFromAp - newNote - ForgeUserActivityNote.Validate ToRepository OnRepository @@ -231,7 +205,6 @@ forgejo.org/modules/util/filebuffer forgejo.org/modules/validation IsErrNotValid - ValidateIDExists forgejo.org/modules/web RouteMock @@ -248,6 +221,9 @@ forgejo.org/routers/web/org forgejo.org/services/context GetPrivateContext +forgejo.org/services/federation + FollowRemoteActor + forgejo.org/services/repository IsErrForkAlreadyExist diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 28fa9e4555..3f250e5682 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "ghcr.io/devcontainers/features/node:1": { "version": "22" }, - "ghcr.io/devcontainers/features/git-lfs:1.2.4": {}, + "ghcr.io/devcontainers/features/git-lfs:1.2.5": {}, "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} }, "customizations": { diff --git a/.forgejo/issue_template/bug-report-ui.yaml b/.forgejo/issue_template/bug-report-ui.yaml index 8bb7bf1d49..1c66c3b648 100644 --- a/.forgejo/issue_template/bug-report-ui.yaml +++ b/.forgejo/issue_template/bug-report-ui.yaml @@ -23,8 +23,9 @@ body: It is running the latest development branch and will confirm the problem is not already fixed. If you can reproduce it, provide a URL in the description. options: - - "Yes" - - "No" + - "Yes, I've linked the repository below" + - "No, I've tried it and the problem is not present there" + - "No, I can't try it on the test instance for some reason" validations: required: true - type: textarea diff --git a/.forgejo/issue_template/bug-report.yaml b/.forgejo/issue_template/bug-report.yaml index a2b50dbca2..c11ebf9c1f 100644 --- a/.forgejo/issue_template/bug-report.yaml +++ b/.forgejo/issue_template/bug-report.yaml @@ -23,8 +23,9 @@ body: It is running the latest development branch and will confirm the problem is not already fixed. If you can reproduce it, provide a URL in the description. options: - - "Yes" - - "No" + - "Yes, I've linked the repository below" + - "No, I've tried it and the problem is not present there" + - "No, I can't try it on the test instance for some reason" validations: required: true - type: textarea diff --git a/.forgejo/workflows-composite/install-minimum-git-version/action.yaml b/.forgejo/workflows-composite/install-minimum-git-version/action.yaml new file mode 100644 index 0000000000..d4e6e3f2a7 --- /dev/null +++ b/.forgejo/workflows-composite/install-minimum-git-version/action.yaml @@ -0,0 +1,22 @@ +# +# Install the minimal version of Git supported by Forgejo +# +runs: + using: "composite" + steps: + - name: install git and git-lfs + run: | + set -x + + export DEBIAN_FRONTEND=noninteractive + + apt-get update -qq + apt-get -q install -y -qq curl ca-certificates + + curl -sS -o /tmp/git-man.deb http://archive.ubuntu.com/ubuntu/pool/main/g/git/git-man_2.34.1-1ubuntu1_all.deb + curl -sS -o /tmp/git.deb https://archive.ubuntu.com/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1_amd64.deb + curl -sS -o /tmp/git-lfs.deb https://archive.ubuntu.com/ubuntu/pool/universe/g/git-lfs/git-lfs_3.0.2-1_amd64.deb + + apt-get -q install --allow-downgrades -y -qq /tmp/git-man.deb + apt-get -q install --allow-downgrades -y -qq /tmp/git.deb + apt-get -q install --allow-downgrades -y -qq /tmp/git-lfs.deb diff --git a/.forgejo/workflows/build-release-integration.yml b/.forgejo/workflows/build-release-integration.yml index 1af6d567dd..6fecac3ff6 100644 --- a/.forgejo/workflows/build-release-integration.yml +++ b/.forgejo/workflows/build-release-integration.yml @@ -28,7 +28,7 @@ jobs: - uses: https://data.forgejo.org/actions/checkout@v4 - id: forgejo - uses: https://data.forgejo.org/actions/setup-forgejo@v2.0.4 + uses: https://data.forgejo.org/actions/setup-forgejo@v3.0.3 with: user: root password: admin1234 diff --git a/.forgejo/workflows/build-release.yml b/.forgejo/workflows/build-release.yml index 3ab63b0589..b70439f12f 100644 --- a/.forgejo/workflows/build-release.yml +++ b/.forgejo/workflows/build-release.yml @@ -164,7 +164,7 @@ jobs: - name: build container & release if: ${{ secrets.TOKEN != '' }} - uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.5 + uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.4.1 with: forgejo: "${{ env.GITHUB_SERVER_URL }}" owner: "${{ env.GITHUB_REPOSITORY_OWNER }}" @@ -183,7 +183,7 @@ jobs: - name: build rootless container if: ${{ secrets.TOKEN != '' }} - uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.5 + uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.4.1 with: forgejo: "${{ env.GITHUB_SERVER_URL }}" owner: "${{ env.GITHUB_REPOSITORY_OWNER }}" @@ -201,7 +201,7 @@ jobs: - name: end-to-end tests if: ${{ secrets.TOKEN != '' && vars.ROLE == 'forgejo-integration' && vars.SKIP_END_TO_END != 'true' }} - uses: https://data.forgejo.org/actions/cascading-pr@v2.2.0 + uses: https://data.forgejo.org/actions/cascading-pr@v2.3.0 with: origin-url: ${{ env.GITHUB_SERVER_URL }} origin-repo: ${{ github.repository }} @@ -212,6 +212,7 @@ jobs: destination-repo: forgejo/end-to-end destination-branch: main destination-token: ${{ secrets.CASCADE_DESTINATION_TOKEN }} + close: true update: .forgejo/cascading-release-end-to-end - name: copy to experimental diff --git a/.forgejo/workflows/cascade-setup-end-to-end.yml b/.forgejo/workflows/cascade-setup-end-to-end.yml index 7c8c56de13..e62dbd9e7d 100644 --- a/.forgejo/workflows/cascade-setup-end-to-end.yml +++ b/.forgejo/workflows/cascade-setup-end-to-end.yml @@ -41,7 +41,7 @@ jobs: with: fetch-depth: '0' show-progress: 'false' - - uses: https://data.forgejo.org/actions/cascading-pr@v2.2.0 + - uses: https://data.forgejo.org/actions/cascading-pr@v2.3.0 with: origin-url: ${{ env.GITHUB_SERVER_URL }} origin-repo: ${{ github.repository }} @@ -53,5 +53,5 @@ jobs: destination-repo: forgejo/end-to-end destination-branch: main destination-token: ${{ secrets.END_TO_END_CASCADING_PR_DESTINATION }} - close-merge: true + close: true update: .forgejo/cascading-pr-end-to-end diff --git a/.forgejo/workflows/coverage.yml b/.forgejo/workflows/coverage.yml new file mode 100644 index 0000000000..194b116251 --- /dev/null +++ b/.forgejo/workflows/coverage.yml @@ -0,0 +1,89 @@ +name: coverage + +on: + workflow_dispatch: + inputs: + repository: + description: 'repository' + type: string + ref: + description: 'ref' + type: string + unit-tests-env: + description: 'COVERAGE_TEST_PACKAGES=forgejo.org/modules/actions' + type: string + integration-tests-env: + description: 'COVERAGE_TEST_ARGS=-run=TestAPIListRepoComments' + type: string + +jobs: + all: + runs-on: docker + container: + image: 'data.forgejo.org/oci/ci:1' + options: --tmpfs /tmp:exec,noatime + services: + elasticsearch: + image: data.forgejo.org/oci/bitnami/elasticsearch:7 + options: --tmpfs /bitnami/elasticsearch/data + env: + discovery.type: single-node + ES_JAVA_OPTS: "-Xms512m -Xmx512m" + minio: + image: data.forgejo.org/oci/bitnami/minio:2024.8.17 + options: >- + --hostname gitea.minio --tmpfs /bitnami/minio/data:noatime + env: + MINIO_DOMAIN: minio + MINIO_ROOT_USER: 123456 + MINIO_ROOT_PASSWORD: 12345678 + mysql: + image: 'data.forgejo.org/oci/bitnami/mysql:8.4' + env: + ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: testgitea + # + # See also https://codeberg.org/forgejo/forgejo/issues/976 + # + 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 + ldap: + image: data.forgejo.org/oci/test-openldap:latest + pgsql: + image: data.forgejo.org/oci/bitnami/postgresql:16 + env: + POSTGRESQL_DATABASE: test + POSTGRESQL_PASSWORD: postgres + POSTGRESQL_FSYNC: off + POSTGRESQL_EXTRA_FLAGS: -c full_page_writes=off + options: --tmpfs /bitnami/postgresql + cacher: + image: registry.redict.io/redict:7.3.5-scratch + options: --tmpfs /data:noatime + steps: + - uses: https://data.forgejo.org/actions/checkout@v4 + with: + repository: ${{ inputs.repository }} + ref: ${{ inputs.ref }} + - uses: ./.forgejo/workflows-composite/setup-env + - name: install git >= 2.42 + uses: ./.forgejo/workflows-composite/apt-install-from + with: + packages: git + - uses: ./.forgejo/workflows-composite/build-backend + - run: | + su forgejo -c '${{ inputs.unit-tests-env }} make coverage-run' + su forgejo -c '${{ inputs.integration-tests-env }} make coverage-run-pgsql' + su forgejo -c '${{ inputs.integration-tests-env }} make coverage-run-mysql' + su forgejo -c '${{ inputs.integration-tests-env }} make coverage-run-sqlite' + su forgejo -c 'make coverage-merge' + timeout-minutes: 180 + env: + TEST_ELASTICSEARCH_URL: http://elasticsearch:9200 + TEST_MINIO_ENDPOINT: minio:9000 + TEST_LDAP: 1 + TEST_REDIS_SERVER: cacher:6379 + - uses: https://code.forgejo.org/forgejo/upload-artifact@v4 + with: + name: coverage + path: ${{ forge.workspace }}/coverage/merged diff --git a/.forgejo/workflows/merge-requirements.yml b/.forgejo/workflows/merge-requirements.yml index 9aaf2af68d..9bed4b8797 100644 --- a/.forgejo/workflows/merge-requirements.yml +++ b/.forgejo/workflows/merge-requirements.yml @@ -1,4 +1,4 @@ -# Copyright 2024 The Forgejo Authors +# Copyright 2025 The Forgejo Authors # SPDX-License-Identifier: MIT name: requirements @@ -13,7 +13,8 @@ on: jobs: merge-conditions: - if: vars.ROLE == 'forgejo-coding' + if: > + vars.ROLE == 'forgejo-coding' && forge.event.pull_request.head.repo.full_name != 'forgejo-cascading-pr/forgejo' runs-on: docker container: image: 'data.forgejo.org/oci/node:22-bookworm' @@ -26,9 +27,9 @@ jobs: - name: Missing test label if: > !( - contains(toJSON(github.event.pull_request.labels), 'test/present') - || contains(toJSON(github.event.pull_request.labels), 'test/not-needed') - || contains(toJSON(github.event.pull_request.labels), 'test/manual') + contains(toJSON(forge.event.pull_request.labels), 'test/present') + || contains(toJSON(forge.event.pull_request.labels), 'test/not-needed') + || contains(toJSON(forge.event.pull_request.labels), 'test/manual') ) run: | echo "A team member must set the label to either 'present', 'not-needed' or 'manual'." @@ -36,8 +37,8 @@ jobs: - name: Missing manual test instructions if: > ( - contains(toJSON(github.event.pull_request.labels), 'test/manual') - && !contains(toJSON(github.event.pull_request.body), '# Test') + contains(toJSON(forge.event.pull_request.labels), 'test/manual') + && !contains(toJSON(forge.event.pull_request.body), '# Test') ) run: | echo "Manual test label is set. The PR description needs to contain test steps introduced by a heading like:" diff --git a/.forgejo/workflows/publish-release.yml b/.forgejo/workflows/publish-release.yml index 3aec46fb03..5303f902e3 100644 --- a/.forgejo/workflows/publish-release.yml +++ b/.forgejo/workflows/publish-release.yml @@ -44,7 +44,7 @@ jobs: - uses: https://data.forgejo.org/actions/checkout@v4 - name: copy & sign - uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.5 + uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.4.1 with: from-forgejo: ${{ vars.FORGEJO }} to-forgejo: ${{ vars.FORGEJO }} @@ -80,7 +80,7 @@ jobs: label: trigger - name: upgrade v*.next.forgejo.org - uses: https://data.forgejo.org/infrastructure/next-digest@v1.1.0 + uses: https://data.forgejo.org/infrastructure/next-digest@v1.2.2 with: url: https://placeholder:${{ secrets.TOKEN_NEXT_DIGEST }}@invisible.forgejo.org/infrastructure/next-digest ref_name: '${{ github.ref_name }}' diff --git a/.forgejo/workflows/release-notes-assistant-milestones.yml b/.forgejo/workflows/release-notes-assistant-milestones.yml index 7f77098357..57c9a23f02 100644 --- a/.forgejo/workflows/release-notes-assistant-milestones.yml +++ b/.forgejo/workflows/release-notes-assistant-milestones.yml @@ -5,32 +5,34 @@ on: - cron: '@daily' env: - RNA_VERSION: v1.2.5 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org + RNA_WORKDIR: /srv/rna + RNA_VERSION: v1.4.1 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org jobs: release-notes: if: vars.ROLE == 'forgejo-coding' runs-on: docker container: - image: 'data.forgejo.org/oci/node:22-bookworm' + image: 'data.forgejo.org/oci/ci:1' steps: - uses: https://data.forgejo.org/actions/checkout@v4 - - uses: https://data.forgejo.org/actions/setup-go@v5 + - uses: https://data.forgejo.org/actions/cache@v4 with: - go-version-file: "go.mod" - cache: false + key: rna-${{ env.RNA_VERSION }} + path: ${{ env.RNA_WORKDIR }} - - name: apt install jq + - name: install release-notes-assistant run: | - export DEBIAN_FRONTEND=noninteractive - apt-get update -qq - apt-get -q install -y -qq jq + set -x + wget -O /usr/local/bin/rna https://code.forgejo.org/forgejo/release-notes-assistant/releases/download/${{ env.RNA_VERSION}}/release-notes-assistant + chmod +x /usr/local/bin/rna - name: update open milestones run: | set -x - curl -sS $GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/milestones?state=open | jq -r '.[] | .title' | while read forgejo version ; do + mkdir -p ${{ env.RNA_WORKDIR }} + curl -sS $FORGEJO_SERVER_URL/api/v1/repos/$FORGEJO_REPOSITORY/milestones?state=open | jq -r '.[] | .title' | while read forgejo version ; do milestone="$forgejo $version" - go run code.forgejo.org/forgejo/release-notes-assistant@$RNA_VERSION --config .release-notes-assistant.yaml --storage milestone --storage-location "$milestone" --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} release $version + rna --workdir ${{ env.RNA_WORKDIR }} --config .release-notes-assistant.yaml --storage milestone --storage-location "$milestone" --forgejo-url $FORGEJO_SERVER_URL --repository $FORGEJO_REPOSITORY --token ${{ secrets.RELEASE_NOTES_ASSISTANT_TOKEN }} release $version done diff --git a/.forgejo/workflows/release-notes-assistant.yml b/.forgejo/workflows/release-notes-assistant.yml index cdcd2e6fe4..a727e57afb 100644 --- a/.forgejo/workflows/release-notes-assistant.yml +++ b/.forgejo/workflows/release-notes-assistant.yml @@ -8,7 +8,7 @@ on: - labeled env: - RNA_VERSION: v1.2.5 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org + RNA_VERSION: v1.4.1 # renovate: datasource=gitea-releases depName=forgejo/release-notes-assistant registryUrl=https://code.forgejo.org jobs: release-notes: diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index 5aa6c8cd98..9e7f03f299 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.1.4 + image: data.forgejo.org/renovate/renovate:41.76.0 steps: - name: Load renovate repo cache @@ -49,7 +49,7 @@ jobs: LOG_LEVEL: debug RENOVATE_BASE_DIR: ${{ github.workspace }}/.tmp RENOVATE_ENDPOINT: ${{ github.server_url }} - RENOVATE_PLATFORM: gitea + RENOVATE_PLATFORM: forgejo RENOVATE_REPOSITORY_CACHE: 'enabled' RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }} RENOVATE_GIT_AUTHOR: 'Renovate Bot ' @@ -63,6 +63,10 @@ jobs: OSV_OFFLINE_ROOT_DIR: ${{ github.workspace }}/.tmp/osv + # use direct connection for these domains for renovate go datasource instead of the go proxy + # allows faster lookups + GONOPROXY: code.forgejo.org + - name: Save renovate repo cache if: always() && env.RENOVATE_DRY_RUN != 'full' uses: https://data.forgejo.org/actions/cache/save@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1 diff --git a/.forgejo/workflows/testing-integration.yml b/.forgejo/workflows/testing-integration.yml index 9e5cfb92ed..6822c45f39 100644 --- a/.forgejo/workflows/testing-integration.yml +++ b/.forgejo/workflows/testing-integration.yml @@ -1,7 +1,8 @@ # # Additional integration tests designed to run once a day when # `mirror.yml` pushes to https://codeberg.org/forgejo-integration/forgejo -# and send a notification via email should they fail. +# and send a notification via email to the contact email of the +# organization should they fail. # # For debug purposes: # @@ -22,6 +23,8 @@ on: - 'forgejo' - 'v*/forgejo' +enable-email-notifications: true + jobs: test-unit: # if: vars.ROLE == 'forgejo-coding' @@ -33,11 +36,8 @@ jobs: steps: - uses: https://data.forgejo.org/actions/checkout@v4 - uses: ./.forgejo/workflows-composite/setup-env - - name: install git 2.30 - uses: ./.forgejo/workflows-composite/apt-install-from - with: - packages: git/bullseye git-lfs/bullseye - release: bullseye + - name: install git 2.34.1 and git-lfs 3.0.2 + uses: ./.forgejo/workflows-composite/install-minimum-git-version - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-backend test-check' @@ -55,11 +55,8 @@ jobs: steps: - uses: https://data.forgejo.org/actions/checkout@v4 - uses: ./.forgejo/workflows-composite/setup-env - - name: install git 2.30 - uses: ./.forgejo/workflows-composite/apt-install-from - with: - packages: git/bullseye git-lfs/bullseye - release: bullseye + - name: install git 2.34.1 and git-lfs 3.0.2 + uses: ./.forgejo/workflows-composite/install-minimum-git-version - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-sqlite-migration test-sqlite' @@ -69,3 +66,34 @@ jobs: RACE_ENABLED: true TEST_TAGS: sqlite sqlite_unlock_notify USE_REPO_TEST_DIR: 1 + test-mariadb: +# if: vars.ROLE == 'forgejo-coding' + if: vars.ROLE == 'forgejo-integration' + runs-on: docker + name: ${{ format('test-mariadb (v{0})', matrix.version) }} + strategy: + matrix: + version: ['10.6', '11.8'] + container: + image: 'data.forgejo.org/oci/node:22-bookworm' + options: --tmpfs /tmp:exec,noatime + services: + mysql: + image: ${{ format('data.forgejo.org/oci/mariadb:{0}', matrix.version) }} + env: + MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: yes + MARIADB_DATABASE: testgitea + options: --tmpfs /var/lib/mysql:noatime + steps: + - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: ./.forgejo/workflows-composite/setup-env + - name: install dependencies & git >= 2.42 + uses: ./.forgejo/workflows-composite/apt-install-from + with: + packages: git git-lfs + - uses: ./.forgejo/workflows-composite/build-backend + - run: | + su forgejo -c 'make test-mysql-migration test-mysql' + timeout-minutes: 120 + env: + USE_REPO_TEST_DIR: 1 diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml index 7a93bb66a8..4c5e18a5e9 100644 --- a/.forgejo/workflows/testing.yml +++ b/.forgejo/workflows/testing.yml @@ -37,7 +37,11 @@ jobs: - run: make deps-frontend - run: make lint-frontend - run: make checks-frontend - - run: make test-frontend-coverage + - run: | + # Usage of `dayjs` can be impacted by local system timezone and can be sensitive to DST differences; since + # frontend tests are very short they're run twice with varying DST rules to reduce regression risk. + TZ=Europe/Berlin make test-frontend-coverage + TZ=America/Edmonton make test-frontend-coverage - run: make frontend - name: Install zstd for cache saving # works around https://github.com/actions/cache/issues/1169, because the diff --git a/.gitignore b/.gitignore index 744e24a09a..ffc493a51d 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,8 @@ _testmain.go *coverage.out coverage.all +coverage.html +coverage.html.gz coverage/ cpu.out @@ -53,6 +55,8 @@ cpu.out *.log *.log.*.gz +/build/lint-locale/lint-locale +/build/lint-locale-usage/lint-locale-usage /gitea /gitea-vet /debug @@ -129,3 +133,4 @@ prime/ # Manpage /man +tests/integration/api_activitypub_person_inbox_useractivity_test.go diff --git a/.golangci.yml b/.golangci.yml index 532132838d..b8884dd080 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -42,6 +42,10 @@ linters: desc: do not use the ini package, use gitea's config system instead - pkg: github.com/minio/sha256-simd desc: use crypto/sha256 instead, see https://codeberg.org/forgejo/forgejo/pulls/1528 + - pkg: github.com/go-git/go-git + desc: use forgejo.org/modules/git instead, see https://codeberg.org/forgejo/forgejo/pulls/4941 + - pkg: gopkg.in/yaml.v3 + desc: use go.yaml.in/yaml instead, see https://codeberg.org/forgejo/forgejo/pulls/8956 gocritic: disabled-checks: - ifElseChain @@ -79,6 +83,10 @@ linters: - name: unreachable-code - name: var-declaration - name: var-naming + arguments: + - [] + - [] + - - skip-package-name-checks: true - name: redefines-builtin-id disabled: true staticcheck: diff --git a/CODEOWNERS b/CODEOWNERS index 34cdceca09..25a3f698dc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -39,4 +39,5 @@ options/locale/.* @0ko options/locale_next/.* @0ko # Personal interest +build/lint-locale-usage/.* @fogti .*/webhook.* @oliverpool diff --git a/Makefile b/Makefile index e770f2a989..6f7a99d40c 100644 --- a/Makefile +++ b/Makefile @@ -39,15 +39,15 @@ 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.1.6 # renovate: datasource=go -GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 # renovate: datasource=go +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.3.1 # 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 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.34.0 # renovate: datasource=go -GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.2 # renovate: datasource=go -RENOVATE_NPM_PACKAGE ?= renovate@41.1.4 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate +DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.36.0 # renovate: datasource=go +GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.6.0 # renovate: datasource=go +RENOVATE_NPM_PACKAGE ?= renovate@41.76.0 # 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: ... @@ -115,9 +115,6 @@ LDFLAGS := $(LDFLAGS) -X "main.ReleaseVersion=$(RELEASE_VERSION)" -X "main.MakeV LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64 -ifeq ($(HAS_GO), yes) - 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 ./...)) -endif REMOTE_CACHER_MODULES ?= cache nosql session queue GO_TEST_REMOTE_CACHER_PACKAGES ?= $(addprefix forgejo.org/modules/,$(REMOTE_CACHER_MODULES)) @@ -159,9 +156,6 @@ GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/optio GO_SOURCES += $(GENERATED_GO_DEST) GO_SOURCES_NO_BINDATA := $(GO_SOURCES) -ifeq ($(HAS_GO), yes) - MIGRATION_PACKAGES := $(shell $(GO) list forgejo.org/models/migrations/... forgejo.org/models/forgejo_migrations/...) -endif ifeq ($(filter $(TAGS_SPLIT),bindata),bindata) GO_SOURCES += $(BINDATA_DEST) @@ -238,6 +232,9 @@ help: @echo " - test-frontend-coverage test frontend files and display code coverage" @echo " - test-backend test backend files" @echo " - test-remote-cacher test backend files that use a remote cache" + @echo " - coverage-run* test and collect coverages in the coverage/data directory" + @echo " - coverage-show-html display coverage-run results in an HTML page" + @echo " - coverage-show-percent display coverage-run per package coverage percentage" @echo " - test-e2e-sqlite[\#name.test.e2e] test end to end using playwright and sqlite" @echo " - webpack build webpack files" @echo " - svg build svg files" @@ -282,6 +279,24 @@ show-version-minor: verify-version show-version-api: verify-version @echo ${FORGEJO_VERSION_API} +### +# Package computation targets +### + +# Target to compute GO_TEST_PACKAGES - only runs when needed +.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 ./...))) +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/...)) +endif + ### # Check system and environment requirements ### @@ -460,7 +475,7 @@ lint-locale: .PHONY: lint-locale-usage lint-locale-usage: - $(GO) run build/lint-locale-usage/lint-locale-usage.go + $(GO) run ./build/lint-locale-usage --allow-masked-usages-from=build/lint-locale-usage/allowed-masked-usage.txt .PHONY: lint-md lint-md: node_modules @@ -522,9 +537,9 @@ watch-backend: go-check test: test-frontend test-backend .PHONY: test-backend -test-backend: +test-backend: | compute-go-test-packages @echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." - @$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES) + @TZ=UTC $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES) .PHONY: test-remote-cacher test-remote-cacher: @@ -552,20 +567,39 @@ test-check: fi .PHONY: test\#% -test\#%: +test\#%: | compute-go-test-packages @echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." - @$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES) + @TZ=UTC $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES) -.PHONY: coverage -coverage: - grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out - grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out - $(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all +coverage-merge: + rm -fr coverage/merged ; mkdir -p coverage/merged + $(GO) tool covdata merge -i `find coverage/data -name 'covmeta.*' | sed -e 's|/covmeta.*|,|' | tr -d '\n' | sed -e 's/,$$//'` -o coverage/merged -.PHONY: unit-test-coverage -unit-test-coverage: - @echo "Running unit-test-coverage $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." - @$(GOTEST) $(GOTESTFLAGS) -timeout=20m -tags='$(TEST_TAGS)' -cover -coverprofile coverage.out $(GO_TEST_PACKAGES) && echo "\n==>\033[32m Ok\033[m\n" || exit 1 +coverage-convert: coverage-merge + $(GO) tool covdata textfmt -i=coverage/merged -o=coverage/textfmt.out + +coverage-show-html: coverage-convert + ( cd coverage ; $(GO) tool cover -html=textfmt.out -o coverage.html ) + xdg-open coverage/coverage.html + +coverage-show-percentage: coverage-convert + go tool cover -func=coverage/textfmt.out + +coverage-run: | compute-go-test-packages + contrib/coverage-helper.sh test_packages $(COVERAGE_TEST_PACKAGES) + +coverage-run-%: generate-ini-% | compute-migration-packages + # + # Migration tests go first + # + $(MAKE) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/$*.ini COVERAGE_TEST_ARGS= COVERAGE_TEST_PACKAGES=forgejo.org/tests/integration/migration-test coverage-run + for pkg in $(MIGRATION_PACKAGES); do \ + $(MAKE) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/$*.ini COVERAGE_TEST_DATABASE=$* COVERAGE_TEST_ARGS= COVERAGE_TEST_PACKAGES=$$pkg coverage-run ; \ + done + # + # All other integration tests follow + # + $(MAKE) GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/$*.ini COVERAGE_TEST_DATABASE=$* COVERAGE_TEST_PACKAGES=forgejo.org/tests/integration coverage-run .PHONY: tidy tidy: @@ -638,6 +672,7 @@ generate-ini-pgsql: -e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \ -e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \ -e 's|{{TEST_STORAGE_TYPE}}|$(or $(TEST_STORAGE_TYPE),minio)|g' \ + -e 's|{{TEST_S3_HOST}}|$(or $(TEST_S3_HOST),minio:9000)|g' \ tests/pgsql.ini.tmpl > tests/pgsql.ini .PHONY: test-pgsql @@ -684,7 +719,7 @@ test-e2e-mysql\#%: playwright e2e.mysql.test generate-ini-mysql GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.mysql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e/$* .PHONY: test-e2e-pgsql -test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql +GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.initest-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTESTCOMPILEDRUNPREFIX) ./e2e.pgsql.test $(GOTESTCOMPILEDRUNSUFFIX) -test.run TestE2e .PHONY: test-e2e-pgsql\#% @@ -707,14 +742,6 @@ bench-mysql: integrations.mysql.test generate-ini-mysql bench-pgsql: integrations.pgsql.test generate-ini-pgsql GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . -.PHONY: integration-test-coverage -integration-test-coverage: integrations.cover.test generate-ini-mysql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.cover.test -test.coverprofile=integration.coverage.out - -.PHONY: integration-test-coverage-sqlite -integration-test-coverage-sqlite: integrations.cover.sqlite.test generate-ini-sqlite - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./integrations.cover.sqlite.test -test.coverprofile=integration.coverage.out - integrations.mysql.test: git-check $(GO_SOURCES) $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -o integrations.mysql.test @@ -724,12 +751,6 @@ integrations.pgsql.test: git-check $(GO_SOURCES) integrations.sqlite.test: git-check $(GO_SOURCES) $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -o integrations.sqlite.test -tags '$(TEST_TAGS)' -integrations.cover.test: git-check $(GO_SOURCES) - $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.test - -integrations.cover.sqlite.test: git-check $(GO_SOURCES) - $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration -coverpkg $(shell echo $(GO_TEST_PACKAGES) | tr ' ' ',') -o integrations.cover.sqlite.test -tags '$(TEST_TAGS)' - .PHONY: migrations.mysql.test migrations.mysql.test: $(GO_SOURCES) generate-ini-mysql $(GOTEST) $(GOTESTFLAGS) -c forgejo.org/tests/integration/migration-test -o migrations.mysql.test @@ -746,7 +767,7 @@ migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTESTCOMPILEDRUNPREFIX) ./migrations.sqlite.test $(GOTESTCOMPILEDRUNSUFFIX) .PHONY: migrations.individual.mysql.test -migrations.individual.mysql.test: $(GO_SOURCES) +migrations.individual.mysql.test: $(GO_SOURCES) | compute-migration-packages for pkg in $(MIGRATION_PACKAGES); do \ GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1; \ done @@ -756,7 +777,7 @@ 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/$* .PHONY: migrations.individual.pgsql.test -migrations.individual.pgsql.test: $(GO_SOURCES) +migrations.individual.pgsql.test: $(GO_SOURCES) | compute-migration-packages for pkg in $(MIGRATION_PACKAGES); do \ GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1;\ done @@ -766,7 +787,7 @@ 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/$* .PHONY: migrations.individual.sqlite.test -migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite +migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite | compute-migration-packages for pkg in $(MIGRATION_PACKAGES); do \ GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GOTEST) $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg || exit 1; \ done @@ -940,6 +961,7 @@ fomantic: cd $(FOMANTIC_WORK_DIR) && npm install --no-save cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/ + rm -rf $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/themes/default/modules/dropdown.overrides $(SED_INPLACE) -e 's/ overrideBrowserslist\r/ overrideBrowserslist: ["defaults"]\r/g' $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/tasks/config/tasks.js cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build # fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event diff --git a/README.md b/README.md index f95aebadeb..e6f1b6c3d1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Hi there! Tired of big platforms playing monopoly? Providing Git hosting for your project, friends, company or community? **Forgejo** (/for'd͡ส’e.jo/ inspired by forฤejo โ€“ the Esperanto word for *forge*) has you covered with its intuitive interface, -light and easy hosting and a lot of builtin functionality. +light and easy hosting and a lot of built-in functionality. Forgejo was [created in 2022](https://forgejo.org/2022-12-15-hello-forgejo/) because we think that the project should be owned by an independent community. diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 32f7b8c264..c688045d1c 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -4,7 +4,7 @@ A minor or major Forgejo release is published every [three months](https://forge A [patch or minor release](https://semver.org/spec/v2.0.0.html) (e.g. upgrading from v7.0.0 to v7.0.1 or v7.1.0) does not require manual intervention. But [major releases](https://semver.org/spec/v2.0.0.html#spec-item-8) where the first version number changes (e.g. upgrading from v1.21 to v7.0) contain breaking changes and the release notes explain how to deal with them. -The release notes of each release [are available in the release-notes-published directory of this repository](release-notes-published), starting with [Forgejo 7.0.7](release-notes-published/7.0.7.md) and [Forgejo 8.0.1](release-notes-published/8.0.1.md). +The release notes of each release [are available in the release-notes-published directory of this repository](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/release-notes-published), starting with [Forgejo 7.0.7](release-notes-published/7.0.7.md) and [Forgejo 8.0.1](release-notes-published/8.0.1.md). ## 9.0.2 diff --git a/assets/go-licenses.json b/assets/go-licenses.json index fb6c201a5e..494671f320 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -34,6 +34,141 @@ "path": "code.forgejo.org/forgejo/reply/LICENSE", "licenseText": "MIT License\n\nCopyright (c) The Forgejo Authors\nCopyright (c) Discourse\nCopyright (c) Claudemiro\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": "code.forgejo.org/forgejo/runner/v11/act", + "path": "code.forgejo.org/forgejo/runner/v11/act/LICENSE", + "licenseText": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. \u003chttps://fsf.org/\u003e\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n Preamble\n\n The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users. We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors. You can apply it to\nyour programs, too.\n\n When we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights. Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received. You must make sure that they, too, receive\nor can get the source code. And you must show them these terms so they\nknow their rights.\n\n Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software. For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so. This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software. The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable. Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts. If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary. To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n The precise terms and conditions for copying, distribution and\nmodification follow.\n\n TERMS AND CONDITIONS\n\n 0. Definitions.\n\n \"This License\" refers to version 3 of the GNU General Public License.\n\n \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n \"The Program\" refers to any copyrightable work licensed under this\nLicense. Each licensee is addressed as \"you\". \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy. The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy. Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies. Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License. If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n 1. Source Code.\n\n The \"source code\" for a work means the preferred form of the work\nfor making modifications to it. \"Object code\" means any non-source\nform of a work.\n\n A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form. A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities. However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work. For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n The Corresponding Source for a work in source code form is that\nsame work.\n\n 2. Basic Permissions.\n\n All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met. This License explicitly affirms your unlimited\npermission to run the unmodified Program. The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work. This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force. You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright. Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n Conveying under any other circumstances is permitted solely under\nthe conditions stated below. Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n 4. Conveying Verbatim Copies.\n\n You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n 5. Conveying Modified Source Versions.\n\n You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n a) The work must carry prominent notices stating that you modified\n it, and giving a relevant date.\n\n b) The work must carry prominent notices stating that it is\n released under this License and any conditions added under section\n 7. This requirement modifies the requirement in section 4 to\n \"keep intact all notices\".\n\n c) You must license the entire work, as a whole, under this\n License to anyone who comes into possession of a copy. This\n License will therefore apply, along with any applicable section 7\n additional terms, to the whole of the work, and all its parts,\n regardless of how they are packaged. This License gives no\n permission to license the work in any other way, but it does not\n invalidate such permission if you have separately received it.\n\n d) If the work has interactive user interfaces, each must display\n Appropriate Legal Notices; however, if the Program has interactive\n interfaces that do not display Appropriate Legal Notices, your\n work need not make them do so.\n\n A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit. Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n 6. Conveying Non-Source Forms.\n\n You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n a) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by the\n Corresponding Source fixed on a durable physical medium\n customarily used for software interchange.\n\n b) Convey the object code in, or embodied in, a physical product\n (including a physical distribution medium), accompanied by a\n written offer, valid for at least three years and valid for as\n long as you offer spare parts or customer support for that product\n model, to give anyone who possesses the object code either (1) a\n copy of the Corresponding Source for all the software in the\n product that is covered by this License, on a durable physical\n medium customarily used for software interchange, for a price no\n more than your reasonable cost of physically performing this\n conveying of source, or (2) access to copy the\n Corresponding Source from a network server at no charge.\n\n c) Convey individual copies of the object code with a copy of the\n written offer to provide the Corresponding Source. This\n alternative is allowed only occasionally and noncommercially, and\n only if you received the object code with such an offer, in accord\n with subsection 6b.\n\n d) Convey the object code by offering access from a designated\n place (gratis or for a charge), and offer equivalent access to the\n Corresponding Source in the same way through the same place at no\n further charge. You need not require recipients to copy the\n Corresponding Source along with the object code. If the place to\n copy the object code is a network server, the Corresponding Source\n may be on a different server (operated by you or a third party)\n that supports equivalent copying facilities, provided you maintain\n clear directions next to the object code saying where to find the\n Corresponding Source. Regardless of what server hosts the\n Corresponding Source, you remain obligated to ensure that it is\n available for as long as needed to satisfy these requirements.\n\n e) Convey the object code using peer-to-peer transmission, provided\n you inform other peers where the object code and Corresponding\n Source of the work are being offered to the general public at no\n charge under subsection 6d.\n\n A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling. In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage. For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product. A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source. The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information. But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed. Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n 7. Additional Terms.\n\n \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law. If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit. (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.) You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n a) Disclaiming warranty or limiting liability differently from the\n terms of sections 15 and 16 of this License; or\n\n b) Requiring preservation of specified reasonable legal notices or\n author attributions in that material or in the Appropriate Legal\n Notices displayed by works containing it; or\n\n c) Prohibiting misrepresentation of the origin of that material, or\n requiring that modified versions of such material be marked in\n reasonable ways as different from the original version; or\n\n d) Limiting the use for publicity purposes of names of licensors or\n authors of the material; or\n\n e) Declining to grant rights under trademark law for use of some\n trade names, trademarks, or service marks; or\n\n f) Requiring indemnification of licensors and authors of that\n material by anyone who conveys the material (or modified versions of\n it) with contractual assumptions of liability to the recipient, for\n any liability that these contractual assumptions directly impose on\n those licensors and authors.\n\n All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10. If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term. If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n 8. Termination.\n\n You may not propagate or modify a covered work except as expressly\nprovided under this License. Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License. If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n 9. Acceptance Not Required for Having Copies.\n\n You are not required to accept this License in order to receive or\nrun a copy of the Program. Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance. However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work. These actions infringe copyright if you do\nnot accept this License. Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n 10. Automatic Licensing of Downstream Recipients.\n\n Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License. You are not responsible\nfor enforcing compliance by third parties with this License.\n\n An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations. If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License. For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n 11. Patents.\n\n A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based. The\nwork thus licensed is called the contributor's \"contributor version\".\n\n A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version. For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement). To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients. \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License. You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n 12. No Surrender of Others' Freedom.\n\n If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License. If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all. For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n 13. Use with the GNU Affero General Public License.\n\n Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work. The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n 14. Revised Versions of this License.\n\n The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time. Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n Each version is given a distinguishing version number. If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation. If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n Later license versions may give you additional or different\npermissions. However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n 15. Disclaimer of Warranty.\n\n THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n 16. Limitation of Liability.\n\n IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n 17. Interpretation of Sections 15 and 16.\n\n If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n END OF TERMS AND CONDITIONS\n\n How to Apply These Terms to Your New Programs\n\n If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n To do so, attach the following notices to the program. It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n \u003cone line to give the program's name and a brief idea of what it does.\u003e\n Copyright (C) \u003cyear\u003e \u003cname of author\u003e\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU General Public License for more details.\n\n You should have received a copy of the GNU General Public License\n along with this program. If not, see \u003chttps://www.gnu.org/licenses/\u003e.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n \u003cprogram\u003e Copyright (C) \u003cyear\u003e \u003cname of author\u003e\n This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n This is free software, and you are welcome to redistribute it\n under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License. Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n\u003chttps://www.gnu.org/licenses/\u003e.\n\n The GNU General Public License does not permit incorporating your program\ninto proprietary programs. If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library. If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License. But first, please read\n\u003chttps://www.gnu.org/licenses/why-not-lgpl.html\u003e.\n" + }, + { + "name": "code.forgejo.org/forgejo/runner/v11/act/act/lookpath", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/lookpath/LICENSE", + "licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n\t* Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n\t* 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\t* Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + }, + { + "name": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@actions/core", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@actions/core/LICENSE.md", + "licenseText": "The MIT License (MIT)\n\nCopyright 2019 GitHub\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + }, + { + "name": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@actions/github/node_modules/@actions/http-client", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@actions/github/node_modules/@actions/http-client/LICENSE", + "licenseText": "Actions Http Client for Node.js\n\nCopyright (c) GitHub, Inc.\n\nAll rights reserved.\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and\nassociated documentation files (the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all 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\nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\nNO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" + }, + { + "name": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@actions/http-client", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@actions/http-client/LICENSE", + "licenseText": "Actions Http Client for Node.js\n\nCopyright (c) GitHub, Inc.\n\nAll rights reserved.\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and\nassociated documentation files (the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all 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\nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\nNO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" + }, + { + "name": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/auth-token", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/auth-token/LICENSE", + "licenseText": "The MIT License\n\nCopyright (c) 2019 Octokit contributors\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.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/core", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/core/LICENSE", + "licenseText": "The MIT License\n\nCopyright (c) 2019 Octokit contributors\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.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/endpoint", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/endpoint/LICENSE", + "licenseText": "The MIT License\n\nCopyright (c) 2018 Octokit contributors\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.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/graphql", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/graphql/LICENSE", + "licenseText": "The MIT License\n\nCopyright (c) 2018 Octokit contributors\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.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/openapi-types", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/openapi-types/LICENSE", + "licenseText": "Copyright 2020 Gregor Martynus\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + }, + { + "name": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/plugin-paginate-rest", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/plugin-paginate-rest/LICENSE", + "licenseText": "MIT License Copyright (c) 2019 Octokit contributors\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 (including the next paragraph) 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.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/plugin-rest-endpoint-methods", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/plugin-rest-endpoint-methods/LICENSE", + "licenseText": "MIT License Copyright (c) 2019 Octokit contributors\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 (including the next paragraph) 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.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/request-error", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/request-error/LICENSE", + "licenseText": "The MIT License\n\nCopyright (c) 2019 Octokit contributors\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.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/request", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/request/LICENSE", + "licenseText": "The MIT License\n\nCopyright (c) 2018 Octokit contributors\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.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/types", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@octokit/types/LICENSE", + "licenseText": "MIT License Copyright (c) 2019 Octokit contributors\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 (including the next paragraph) 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.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@vercel/ncc", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/@vercel/ncc/LICENSE", + "licenseText": "Copyright 2018 ZEIT, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + }, + { + "name": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/before-after-hook", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/before-after-hook/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 2018 Gregor Martynus and other contributors.\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": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/deprecation", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/deprecation/LICENSE", + "licenseText": "The ISC License\n\nCopyright (c) Gregor Martynus and contributors\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR\nIN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n" + }, + { + "name": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/is-plain-object", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/is-plain-object/LICENSE", + "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014-2017, Jon Schlinkert.\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.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/node-fetch", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/node-fetch/LICENSE.md", + "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2016 David Frank\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n" + }, + { + "name": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/once", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/once/LICENSE", + "licenseText": "The ISC License\n\nCopyright (c) Isaac Z. Schlueter and Contributors\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR\nIN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n" + }, + { + "name": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/tunnel", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/tunnel/LICENSE", + "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2012 Koichi Kobayashi\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.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/universal-user-agent", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/universal-user-agent/LICENSE.md", + "licenseText": "# [ISC License](https://spdx.org/licenses/ISC)\n\nCopyright (c) 2018, Gregor Martynus (https://github.com/gr2m)\n\nPermission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n" + }, + { + "name": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/uuid", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/uuid/LICENSE.md", + "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2010-2020 Robert Kieffer and other contributors\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.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/webidl-conversions", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/webidl-conversions/LICENSE.md", + "licenseText": "# The BSD 2-Clause License\n\nCopyright (c) 2014, Domenic Denicola\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + }, + { + "name": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/whatwg-url", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/whatwg-url/LICENSE.txt", + "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015โ€“2016 Sebastian Mayr\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.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/wrappy", + "path": "code.forgejo.org/forgejo/runner/v11/act/act/runner/testdata/actions/node20/node_modules/wrappy/LICENSE", + "licenseText": "The ISC License\n\nCopyright (c) Isaac Z. Schlueter and Contributors\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR\nIN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n" + }, { "name": "code.forgejo.org/go-chi/binding", "path": "code.forgejo.org/go-chi/binding/LICENSE", @@ -114,6 +249,11 @@ "path": "github.com/RoaringBitmap/roaring/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 2016 by the 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\n================================================================================\n\nPortions of runcontainer.go are from the Go standard library, which is licensed\nunder:\n\nCopyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\n copyright notice, this list of conditions and the following disclaimer\n in the documentation and/or other materials provided with the\n distribution.\n * Neither the name of Google Inc. nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, + { + "name": "github.com/STARRY-S/zip", + "path": "github.com/STARRY-S/zip/LICENSE", + "licenseText": "BSD 3-Clause License\n\nCopyright (c) 2023, Starry\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + }, { "name": "github.com/SaveTheRbtz/zstd-seekable-format-go/pkg", "path": "github.com/SaveTheRbtz/zstd-seekable-format-go/pkg/LICENSE", @@ -244,6 +384,26 @@ "path": "github.com/blevesearch/zapx/v16/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/bmatcuk/doublestar/v4", + "path": "github.com/bmatcuk/doublestar/v4/LICENSE", + "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014 Bob Matcuk\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n" + }, + { + "name": "github.com/bodgit/plumbing", + "path": "github.com/bodgit/plumbing/LICENSE", + "licenseText": "BSD 3-Clause License\n\nCopyright (c) 2019, Matt Dainty\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n" + }, + { + "name": "github.com/bodgit/sevenzip", + "path": "github.com/bodgit/sevenzip/LICENSE", + "licenseText": "BSD 3-Clause License\n\nCopyright (c) 2020, Matt Dainty\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + }, + { + "name": "github.com/bodgit/windows", + "path": "github.com/bodgit/windows/LICENSE", + "licenseText": "BSD 3-Clause License\n\nCopyright (c) 2020, Matt Dainty\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n" + }, { "name": "github.com/boombuler/barcode", "path": "github.com/boombuler/barcode/LICENSE", @@ -564,11 +724,21 @@ "path": "github.com/gorilla/sessions/LICENSE", "licenseText": "Copyright (c) 2024 The Gorilla Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n\t * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n\t * 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\t * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, + { + "name": "github.com/hashicorp/errwrap", + "path": "github.com/hashicorp/errwrap/LICENSE", + "licenseText": "Mozilla Public License, version 2.0\n\n1. Definitions\n\n1.1. โ€œContributorโ€\n\n means each individual or legal entity that creates, contributes to the\n creation of, or owns Covered Software.\n\n1.2. โ€œContributor Versionโ€\n\n means the combination of the Contributions of others (if any) used by a\n Contributor and that particular Contributorโ€™s Contribution.\n\n1.3. โ€œContributionโ€\n\n means Covered Software of a particular Contributor.\n\n1.4. โ€œCovered Softwareโ€\n\n means Source Code Form to which the initial Contributor has attached the\n notice in Exhibit A, the Executable Form of such Source Code Form, and\n Modifications of such Source Code Form, in each case including portions\n thereof.\n\n1.5. โ€œIncompatible With Secondary Licensesโ€\n means\n\n a. that the initial Contributor has attached the notice described in\n Exhibit B to the Covered Software; or\n\n b. that the Covered Software was made available under the terms of version\n 1.1 or earlier of the License, but not also under the terms of a\n Secondary License.\n\n1.6. โ€œExecutable Formโ€\n\n means any form of the work other than Source Code Form.\n\n1.7. โ€œLarger Workโ€\n\n means a work that combines Covered Software with other material, in a separate\n file or files, that is not Covered Software.\n\n1.8. โ€œLicenseโ€\n\n means this document.\n\n1.9. โ€œLicensableโ€\n\n means having the right to grant, to the maximum extent possible, whether at the\n time of the initial grant or subsequently, any and all of the rights conveyed by\n this License.\n\n1.10. โ€œModificationsโ€\n\n means any of the following:\n\n a. any file in Source Code Form that results from an addition to, deletion\n from, or modification of the contents of Covered Software; or\n\n b. any new file in Source Code Form that contains any Covered Software.\n\n1.11. โ€œPatent Claimsโ€ of a Contributor\n\n means any patent claim(s), including without limitation, method, process,\n and apparatus claims, in any patent Licensable by such Contributor that\n would be infringed, but for the grant of the License, by the making,\n using, selling, offering for sale, having made, import, or transfer of\n either its Contributions or its Contributor Version.\n\n1.12. โ€œSecondary Licenseโ€\n\n means either the GNU General Public License, Version 2.0, the GNU Lesser\n General Public License, Version 2.1, the GNU Affero General Public\n License, Version 3.0, or any later versions of those licenses.\n\n1.13. โ€œSource Code Formโ€\n\n means the form of the work preferred for making modifications.\n\n1.14. โ€œYouโ€ (or โ€œYourโ€)\n\n means an individual or a legal entity exercising rights under this\n License. For legal entities, โ€œYouโ€ includes any entity that controls, is\n controlled by, or is under common control with You. For purposes of this\n definition, โ€œcontrolโ€ means (a) the power, direct or indirect, to cause\n the direction or management of such entity, whether by contract or\n otherwise, or (b) ownership of more than fifty percent (50%) of the\n outstanding shares or beneficial ownership of such entity.\n\n\n2. License Grants and Conditions\n\n2.1. Grants\n\n Each Contributor hereby grants You a world-wide, royalty-free,\n non-exclusive license:\n\n a. under intellectual property rights (other than patent or trademark)\n Licensable by such Contributor to use, reproduce, make available,\n modify, display, perform, distribute, and otherwise exploit its\n Contributions, either on an unmodified basis, with Modifications, or as\n part of a Larger Work; and\n\n b. under Patent Claims of such Contributor to make, use, sell, offer for\n sale, have made, import, and otherwise transfer either its Contributions\n or its Contributor Version.\n\n2.2. Effective Date\n\n The licenses granted in Section 2.1 with respect to any Contribution become\n effective for each Contribution on the date the Contributor first distributes\n such Contribution.\n\n2.3. Limitations on Grant Scope\n\n The licenses granted in this Section 2 are the only rights granted under this\n License. No additional rights or licenses will be implied from the distribution\n or licensing of Covered Software under this License. Notwithstanding Section\n 2.1(b) above, no patent license is granted by a Contributor:\n\n a. for any code that a Contributor has removed from Covered Software; or\n\n b. for infringements caused by: (i) Your and any other third partyโ€™s\n modifications of Covered Software, or (ii) the combination of its\n Contributions with other software (except as part of its Contributor\n Version); or\n\n c. under Patent Claims infringed by Covered Software in the absence of its\n Contributions.\n\n This License does not grant any rights in the trademarks, service marks, or\n logos of any Contributor (except as may be necessary to comply with the\n notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\n No Contributor makes additional grants as a result of Your choice to\n distribute the Covered Software under a subsequent version of this License\n (see Section 10.2) or under the terms of a Secondary License (if permitted\n under the terms of Section 3.3).\n\n2.5. Representation\n\n Each Contributor represents that the Contributor believes its Contributions\n are its original creation(s) or it has sufficient rights to grant the\n rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\n This License is not intended to limit any rights You have under applicable\n copyright doctrines of fair use, fair dealing, or other equivalents.\n\n2.7. Conditions\n\n Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in\n Section 2.1.\n\n\n3. Responsibilities\n\n3.1. Distribution of Source Form\n\n All distribution of Covered Software in Source Code Form, including any\n Modifications that You create or to which You contribute, must be under the\n terms of this License. You must inform recipients that the Source Code Form\n of the Covered Software is governed by the terms of this License, and how\n they can obtain a copy of this License. You may not attempt to alter or\n restrict the recipientsโ€™ rights in the Source Code Form.\n\n3.2. Distribution of Executable Form\n\n If You distribute Covered Software in Executable Form then:\n\n a. such Covered Software must also be made available in Source Code Form,\n as described in Section 3.1, and You must inform recipients of the\n Executable Form how they can obtain a copy of such Source Code Form by\n reasonable means in a timely manner, at a charge no more than the cost\n of distribution to the recipient; and\n\n b. You may distribute such Executable Form under the terms of this License,\n or sublicense it under different terms, provided that the license for\n the Executable Form does not attempt to limit or alter the recipientsโ€™\n rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\n You may create and distribute a Larger Work under terms of Your choice,\n provided that You also comply with the requirements of this License for the\n Covered Software. If the Larger Work is a combination of Covered Software\n with a work governed by one or more Secondary Licenses, and the Covered\n Software is not Incompatible With Secondary Licenses, this License permits\n You to additionally distribute such Covered Software under the terms of\n such Secondary License(s), so that the recipient of the Larger Work may, at\n their option, further distribute the Covered Software under the terms of\n either this License or such Secondary License(s).\n\n3.4. Notices\n\n You may not remove or alter the substance of any license notices (including\n copyright notices, patent notices, disclaimers of warranty, or limitations\n of liability) contained within the Source Code Form of the Covered\n Software, except that You may alter any license notices to the extent\n required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\n You may choose to offer, and to charge a fee for, warranty, support,\n indemnity or liability obligations to one or more recipients of Covered\n Software. However, You may do so only on Your own behalf, and not on behalf\n of any Contributor. You must make it absolutely clear that any such\n warranty, support, indemnity, or liability obligation is offered by You\n alone, and You hereby agree to indemnify every Contributor for any\n liability incurred by such Contributor as a result of warranty, support,\n indemnity or liability terms You offer. You may include additional\n disclaimers of warranty and limitations of liability specific to any\n jurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n\n If it is impossible for You to comply with any of the terms of this License\n with respect to some or all of the Covered Software due to statute, judicial\n order, or regulation then You must: (a) comply with the terms of this License\n to the maximum extent possible; and (b) describe the limitations and the code\n they affect. Such description must be placed in a text file included with all\n distributions of the Covered Software under this License. Except to the\n extent prohibited by statute or regulation, such description must be\n sufficiently detailed for a recipient of ordinary skill to be able to\n understand it.\n\n5. Termination\n\n5.1. The rights granted under this License will terminate automatically if You\n fail to comply with any of its terms. However, if You become compliant,\n then the rights granted under this License from a particular Contributor\n are reinstated (a) provisionally, unless and until such Contributor\n explicitly and finally terminates Your grants, and (b) on an ongoing basis,\n if such Contributor fails to notify You of the non-compliance by some\n reasonable means prior to 60 days after You have come back into compliance.\n Moreover, Your grants from a particular Contributor are reinstated on an\n ongoing basis if such Contributor notifies You of the non-compliance by\n some reasonable means, this is the first time You have received notice of\n non-compliance with this License from such Contributor, and You become\n compliant prior to 30 days after Your receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\n infringement claim (excluding declaratory judgment actions, counter-claims,\n and cross-claims) alleging that a Contributor Version directly or\n indirectly infringes any patent, then the rights granted to You by any and\n all Contributors for the Covered Software under Section 2.1 of this License\n shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user\n license agreements (excluding distributors and resellers) which have been\n validly granted by You or Your distributors under this License prior to\n termination shall survive termination.\n\n6. Disclaimer of Warranty\n\n Covered Software is provided under this License on an โ€œas isโ€ basis, without\n warranty of any kind, either expressed, implied, or statutory, including,\n without limitation, warranties that the Covered Software is free of defects,\n merchantable, fit for a particular purpose or non-infringing. The entire\n risk as to the quality and performance of the Covered Software is with You.\n Should any Covered Software prove defective in any respect, You (not any\n Contributor) assume the cost of any necessary servicing, repair, or\n correction. This disclaimer of warranty constitutes an essential part of this\n License. No use of any Covered Software is authorized under this License\n except under this disclaimer.\n\n7. Limitation of Liability\n\n Under no circumstances and under no legal theory, whether tort (including\n negligence), contract, or otherwise, shall any Contributor, or anyone who\n distributes Covered Software as permitted above, be liable to You for any\n direct, indirect, special, incidental, or consequential damages of any\n character including, without limitation, damages for lost profits, loss of\n goodwill, work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses, even if such party shall have been\n informed of the possibility of such damages. This limitation of liability\n shall not apply to liability for death or personal injury resulting from such\n partyโ€™s negligence to the extent applicable law prohibits such limitation.\n Some jurisdictions do not allow the exclusion or limitation of incidental or\n consequential damages, so this exclusion and limitation may not apply to You.\n\n8. Litigation\n\n Any litigation relating to this License may be brought only in the courts of\n a jurisdiction where the defendant maintains its principal place of business\n and such litigation shall be governed by laws of that jurisdiction, without\n reference to its conflict-of-law provisions. Nothing in this Section shall\n prevent a partyโ€™s ability to bring cross-claims or counter-claims.\n\n9. Miscellaneous\n\n This License represents the complete agreement concerning the subject matter\n hereof. If any provision of this License is held to be unenforceable, such\n provision shall be reformed only to the extent necessary to make it\n enforceable. Any law or regulation which provides that the language of a\n contract shall be construed against the drafter shall not be used to construe\n this License against a Contributor.\n\n\n10. Versions of the License\n\n10.1. New Versions\n\n Mozilla Foundation is the license steward. Except as provided in Section\n 10.3, no one other than the license steward has the right to modify or\n publish new versions of this License. Each version will be given a\n distinguishing version number.\n\n10.2. Effect of New Versions\n\n You may distribute the Covered Software under the terms of the version of\n the License under which You originally received the Covered Software, or\n under the terms of any subsequent version published by the license\n steward.\n\n10.3. Modified Versions\n\n If you create software not governed by this License, and you want to\n create a new license for such software, you may create and use a modified\n version of this License if you rename the license and remove any\n references to the name of the license steward (except to note that such\n modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses\n If You choose to distribute Source Code Form that is Incompatible With\n Secondary Licenses under the terms of this version of the License, the\n notice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n\n This Source Code Form is subject to the\n terms of the Mozilla Public License, v.\n 2.0. If a copy of the MPL was not\n distributed with this file, You can\n obtain one at\n http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular file, then\nYou may include the notice in a location (such as a LICENSE file in a relevant\ndirectory) where a recipient would be likely to look for such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - โ€œIncompatible With Secondary Licensesโ€ Notice\n\n This Source Code Form is โ€œIncompatible\n With Secondary Licensesโ€, as defined by\n the Mozilla Public License, v. 2.0.\n\n" + }, { "name": "github.com/hashicorp/go-cleanhttp", "path": "github.com/hashicorp/go-cleanhttp/LICENSE", "licenseText": "Mozilla Public License, version 2.0\n\n1. Definitions\n\n1.1. \"Contributor\"\n\n means each individual or legal entity that creates, contributes to the\n creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n\n means the combination of the Contributions of others (if any) used by a\n Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n\n means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n\n means Source Code Form to which the initial Contributor has attached the\n notice in Exhibit A, the Executable Form of such Source Code Form, and\n Modifications of such Source Code Form, in each case including portions\n thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n means\n\n a. that the initial Contributor has attached the notice described in\n Exhibit B to the Covered Software; or\n\n b. that the Covered Software was made available under the terms of\n version 1.1 or earlier of the License, but not also under the terms of\n a Secondary License.\n\n1.6. \"Executable Form\"\n\n means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n\n means a work that combines Covered Software with other material, in a\n separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n\n means this document.\n\n1.9. \"Licensable\"\n\n means having the right to grant, to the maximum extent possible, whether\n at the time of the initial grant or subsequently, any and all of the\n rights conveyed by this License.\n\n1.10. \"Modifications\"\n\n means any of the following:\n\n a. any file in Source Code Form that results from an addition to,\n deletion from, or modification of the contents of Covered Software; or\n\n b. any new file in Source Code Form that contains any Covered Software.\n\n1.11. \"Patent Claims\" of a Contributor\n\n means any patent claim(s), including without limitation, method,\n process, and apparatus claims, in any patent Licensable by such\n Contributor that would be infringed, but for the grant of the License,\n by the making, using, selling, offering for sale, having made, import,\n or transfer of either its Contributions or its Contributor Version.\n\n1.12. \"Secondary License\"\n\n means either the GNU General Public License, Version 2.0, the GNU Lesser\n General Public License, Version 2.1, the GNU Affero General Public\n License, Version 3.0, or any later versions of those licenses.\n\n1.13. \"Source Code Form\"\n\n means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n\n means an individual or a legal entity exercising rights under this\n License. For legal entities, \"You\" includes any entity that controls, is\n controlled by, or is under common control with You. For purposes of this\n definition, \"control\" means (a) the power, direct or indirect, to cause\n the direction or management of such entity, whether by contract or\n otherwise, or (b) ownership of more than fifty percent (50%) of the\n outstanding shares or beneficial ownership of such entity.\n\n\n2. License Grants and Conditions\n\n2.1. Grants\n\n Each Contributor hereby grants You a world-wide, royalty-free,\n non-exclusive license:\n\n a. under intellectual property rights (other than patent or trademark)\n Licensable by such Contributor to use, reproduce, make available,\n modify, display, perform, distribute, and otherwise exploit its\n Contributions, either on an unmodified basis, with Modifications, or\n as part of a Larger Work; and\n\n b. under Patent Claims of such Contributor to make, use, sell, offer for\n sale, have made, import, and otherwise transfer either its\n Contributions or its Contributor Version.\n\n2.2. Effective Date\n\n The licenses granted in Section 2.1 with respect to any Contribution\n become effective for each Contribution on the date the Contributor first\n distributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\n The licenses granted in this Section 2 are the only rights granted under\n this License. No additional rights or licenses will be implied from the\n distribution or licensing of Covered Software under this License.\n Notwithstanding Section 2.1(b) above, no patent license is granted by a\n Contributor:\n\n a. for any code that a Contributor has removed from Covered Software; or\n\n b. for infringements caused by: (i) Your and any other third party's\n modifications of Covered Software, or (ii) the combination of its\n Contributions with other software (except as part of its Contributor\n Version); or\n\n c. under Patent Claims infringed by Covered Software in the absence of\n its Contributions.\n\n This License does not grant any rights in the trademarks, service marks,\n or logos of any Contributor (except as may be necessary to comply with\n the notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\n No Contributor makes additional grants as a result of Your choice to\n distribute the Covered Software under a subsequent version of this\n License (see Section 10.2) or under the terms of a Secondary License (if\n permitted under the terms of Section 3.3).\n\n2.5. Representation\n\n Each Contributor represents that the Contributor believes its\n Contributions are its original creation(s) or it has sufficient rights to\n grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\n This License is not intended to limit any rights You have under\n applicable copyright doctrines of fair use, fair dealing, or other\n equivalents.\n\n2.7. Conditions\n\n Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in\n Section 2.1.\n\n\n3. Responsibilities\n\n3.1. Distribution of Source Form\n\n All distribution of Covered Software in Source Code Form, including any\n Modifications that You create or to which You contribute, must be under\n the terms of this License. You must inform recipients that the Source\n Code Form of the Covered Software is governed by the terms of this\n License, and how they can obtain a copy of this License. You may not\n attempt to alter or restrict the recipients' rights in the Source Code\n Form.\n\n3.2. Distribution of Executable Form\n\n If You distribute Covered Software in Executable Form then:\n\n a. such Covered Software must also be made available in Source Code Form,\n as described in Section 3.1, and You must inform recipients of the\n Executable Form how they can obtain a copy of such Source Code Form by\n reasonable means in a timely manner, at a charge no more than the cost\n of distribution to the recipient; and\n\n b. You may distribute such Executable Form under the terms of this\n License, or sublicense it under different terms, provided that the\n license for the Executable Form does not attempt to limit or alter the\n recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\n You may create and distribute a Larger Work under terms of Your choice,\n provided that You also comply with the requirements of this License for\n the Covered Software. If the Larger Work is a combination of Covered\n Software with a work governed by one or more Secondary Licenses, and the\n Covered Software is not Incompatible With Secondary Licenses, this\n License permits You to additionally distribute such Covered Software\n under the terms of such Secondary License(s), so that the recipient of\n the Larger Work may, at their option, further distribute the Covered\n Software under the terms of either this License or such Secondary\n License(s).\n\n3.4. Notices\n\n You may not remove or alter the substance of any license notices\n (including copyright notices, patent notices, disclaimers of warranty, or\n limitations of liability) contained within the Source Code Form of the\n Covered Software, except that You may alter any license notices to the\n extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\n You may choose to offer, and to charge a fee for, warranty, support,\n indemnity or liability obligations to one or more recipients of Covered\n Software. However, You may do so only on Your own behalf, and not on\n behalf of any Contributor. You must make it absolutely clear that any\n such warranty, support, indemnity, or liability obligation is offered by\n You alone, and You hereby agree to indemnify every Contributor for any\n liability incurred by such Contributor as a result of warranty, support,\n indemnity or liability terms You offer. You may include additional\n disclaimers of warranty and limitations of liability specific to any\n jurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n\n If it is impossible for You to comply with any of the terms of this License\n with respect to some or all of the Covered Software due to statute,\n judicial order, or regulation then You must: (a) comply with the terms of\n this License to the maximum extent possible; and (b) describe the\n limitations and the code they affect. Such description must be placed in a\n text file included with all distributions of the Covered Software under\n this License. Except to the extent prohibited by statute or regulation,\n such description must be sufficiently detailed for a recipient of ordinary\n skill to be able to understand it.\n\n5. Termination\n\n5.1. The rights granted under this License will terminate automatically if You\n fail to comply with any of its terms. However, if You become compliant,\n then the rights granted under this License from a particular Contributor\n are reinstated (a) provisionally, unless and until such Contributor\n explicitly and finally terminates Your grants, and (b) on an ongoing\n basis, if such Contributor fails to notify You of the non-compliance by\n some reasonable means prior to 60 days after You have come back into\n compliance. Moreover, Your grants from a particular Contributor are\n reinstated on an ongoing basis if such Contributor notifies You of the\n non-compliance by some reasonable means, this is the first time You have\n received notice of non-compliance with this License from such\n Contributor, and You become compliant prior to 30 days after Your receipt\n of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\n infringement claim (excluding declaratory judgment actions,\n counter-claims, and cross-claims) alleging that a Contributor Version\n directly or indirectly infringes any patent, then the rights granted to\n You by any and all Contributors for the Covered Software under Section\n 2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user\n license agreements (excluding distributors and resellers) which have been\n validly granted by You or Your distributors under this License prior to\n termination shall survive termination.\n\n6. Disclaimer of Warranty\n\n Covered Software is provided under this License on an \"as is\" basis,\n without warranty of any kind, either expressed, implied, or statutory,\n including, without limitation, warranties that the Covered Software is free\n of defects, merchantable, fit for a particular purpose or non-infringing.\n The entire risk as to the quality and performance of the Covered Software\n is with You. Should any Covered Software prove defective in any respect,\n You (not any Contributor) assume the cost of any necessary servicing,\n repair, or correction. This disclaimer of warranty constitutes an essential\n part of this License. No use of any Covered Software is authorized under\n this License except under this disclaimer.\n\n7. Limitation of Liability\n\n Under no circumstances and under no legal theory, whether tort (including\n negligence), contract, or otherwise, shall any Contributor, or anyone who\n distributes Covered Software as permitted above, be liable to You for any\n direct, indirect, special, incidental, or consequential damages of any\n character including, without limitation, damages for lost profits, loss of\n goodwill, work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses, even if such party shall have been\n informed of the possibility of such damages. This limitation of liability\n shall not apply to liability for death or personal injury resulting from\n such party's negligence to the extent applicable law prohibits such\n limitation. Some jurisdictions do not allow the exclusion or limitation of\n incidental or consequential damages, so this exclusion and limitation may\n not apply to You.\n\n8. Litigation\n\n Any litigation relating to this License may be brought only in the courts\n of a jurisdiction where the defendant maintains its principal place of\n business and such litigation shall be governed by laws of that\n jurisdiction, without reference to its conflict-of-law provisions. Nothing\n in this Section shall prevent a party's ability to bring cross-claims or\n counter-claims.\n\n9. Miscellaneous\n\n This License represents the complete agreement concerning the subject\n matter hereof. If any provision of this License is held to be\n unenforceable, such provision shall be reformed only to the extent\n necessary to make it enforceable. Any law or regulation which provides that\n the language of a contract shall be construed against the drafter shall not\n be used to construe this License against a Contributor.\n\n\n10. Versions of the License\n\n10.1. New Versions\n\n Mozilla Foundation is the license steward. Except as provided in Section\n 10.3, no one other than the license steward has the right to modify or\n publish new versions of this License. Each version will be given a\n distinguishing version number.\n\n10.2. Effect of New Versions\n\n You may distribute the Covered Software under the terms of the version\n of the License under which You originally received the Covered Software,\n or under the terms of any subsequent version published by the license\n steward.\n\n10.3. Modified Versions\n\n If you create software not governed by this License, and you want to\n create a new license for such software, you may create and use a\n modified version of this License if you rename the license and remove\n any references to the name of the license steward (except to note that\n such modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\n Licenses If You choose to distribute Source Code Form that is\n Incompatible With Secondary Licenses under the terms of this version of\n the License, the notice described in Exhibit B of this License must be\n attached.\n\nExhibit A - Source Code Form License Notice\n\n This Source Code Form is subject to the\n terms of the Mozilla Public License, v.\n 2.0. If a copy of the MPL was not\n distributed with this file, You can\n obtain one at\n http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular file,\nthen You may include the notice in a location (such as a LICENSE file in a\nrelevant directory) where a recipient would be likely to look for such a\nnotice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n\n This Source Code Form is \"Incompatible\n With Secondary Licenses\", as defined by\n the Mozilla Public License, v. 2.0.\n\n" }, + { + "name": "github.com/hashicorp/go-multierror", + "path": "github.com/hashicorp/go-multierror/LICENSE", + "licenseText": "Mozilla Public License, version 2.0\n\n1. Definitions\n\n1.1. โ€œContributorโ€\n\n means each individual or legal entity that creates, contributes to the\n creation of, or owns Covered Software.\n\n1.2. โ€œContributor Versionโ€\n\n means the combination of the Contributions of others (if any) used by a\n Contributor and that particular Contributorโ€™s Contribution.\n\n1.3. โ€œContributionโ€\n\n means Covered Software of a particular Contributor.\n\n1.4. โ€œCovered Softwareโ€\n\n means Source Code Form to which the initial Contributor has attached the\n notice in Exhibit A, the Executable Form of such Source Code Form, and\n Modifications of such Source Code Form, in each case including portions\n thereof.\n\n1.5. โ€œIncompatible With Secondary Licensesโ€\n means\n\n a. that the initial Contributor has attached the notice described in\n Exhibit B to the Covered Software; or\n\n b. that the Covered Software was made available under the terms of version\n 1.1 or earlier of the License, but not also under the terms of a\n Secondary License.\n\n1.6. โ€œExecutable Formโ€\n\n means any form of the work other than Source Code Form.\n\n1.7. โ€œLarger Workโ€\n\n means a work that combines Covered Software with other material, in a separate\n file or files, that is not Covered Software.\n\n1.8. โ€œLicenseโ€\n\n means this document.\n\n1.9. โ€œLicensableโ€\n\n means having the right to grant, to the maximum extent possible, whether at the\n time of the initial grant or subsequently, any and all of the rights conveyed by\n this License.\n\n1.10. โ€œModificationsโ€\n\n means any of the following:\n\n a. any file in Source Code Form that results from an addition to, deletion\n from, or modification of the contents of Covered Software; or\n\n b. any new file in Source Code Form that contains any Covered Software.\n\n1.11. โ€œPatent Claimsโ€ of a Contributor\n\n means any patent claim(s), including without limitation, method, process,\n and apparatus claims, in any patent Licensable by such Contributor that\n would be infringed, but for the grant of the License, by the making,\n using, selling, offering for sale, having made, import, or transfer of\n either its Contributions or its Contributor Version.\n\n1.12. โ€œSecondary Licenseโ€\n\n means either the GNU General Public License, Version 2.0, the GNU Lesser\n General Public License, Version 2.1, the GNU Affero General Public\n License, Version 3.0, or any later versions of those licenses.\n\n1.13. โ€œSource Code Formโ€\n\n means the form of the work preferred for making modifications.\n\n1.14. โ€œYouโ€ (or โ€œYourโ€)\n\n means an individual or a legal entity exercising rights under this\n License. For legal entities, โ€œYouโ€ includes any entity that controls, is\n controlled by, or is under common control with You. For purposes of this\n definition, โ€œcontrolโ€ means (a) the power, direct or indirect, to cause\n the direction or management of such entity, whether by contract or\n otherwise, or (b) ownership of more than fifty percent (50%) of the\n outstanding shares or beneficial ownership of such entity.\n\n\n2. License Grants and Conditions\n\n2.1. Grants\n\n Each Contributor hereby grants You a world-wide, royalty-free,\n non-exclusive license:\n\n a. under intellectual property rights (other than patent or trademark)\n Licensable by such Contributor to use, reproduce, make available,\n modify, display, perform, distribute, and otherwise exploit its\n Contributions, either on an unmodified basis, with Modifications, or as\n part of a Larger Work; and\n\n b. under Patent Claims of such Contributor to make, use, sell, offer for\n sale, have made, import, and otherwise transfer either its Contributions\n or its Contributor Version.\n\n2.2. Effective Date\n\n The licenses granted in Section 2.1 with respect to any Contribution become\n effective for each Contribution on the date the Contributor first distributes\n such Contribution.\n\n2.3. Limitations on Grant Scope\n\n The licenses granted in this Section 2 are the only rights granted under this\n License. No additional rights or licenses will be implied from the distribution\n or licensing of Covered Software under this License. Notwithstanding Section\n 2.1(b) above, no patent license is granted by a Contributor:\n\n a. for any code that a Contributor has removed from Covered Software; or\n\n b. for infringements caused by: (i) Your and any other third partyโ€™s\n modifications of Covered Software, or (ii) the combination of its\n Contributions with other software (except as part of its Contributor\n Version); or\n\n c. under Patent Claims infringed by Covered Software in the absence of its\n Contributions.\n\n This License does not grant any rights in the trademarks, service marks, or\n logos of any Contributor (except as may be necessary to comply with the\n notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\n No Contributor makes additional grants as a result of Your choice to\n distribute the Covered Software under a subsequent version of this License\n (see Section 10.2) or under the terms of a Secondary License (if permitted\n under the terms of Section 3.3).\n\n2.5. Representation\n\n Each Contributor represents that the Contributor believes its Contributions\n are its original creation(s) or it has sufficient rights to grant the\n rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\n This License is not intended to limit any rights You have under applicable\n copyright doctrines of fair use, fair dealing, or other equivalents.\n\n2.7. Conditions\n\n Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in\n Section 2.1.\n\n\n3. Responsibilities\n\n3.1. Distribution of Source Form\n\n All distribution of Covered Software in Source Code Form, including any\n Modifications that You create or to which You contribute, must be under the\n terms of this License. You must inform recipients that the Source Code Form\n of the Covered Software is governed by the terms of this License, and how\n they can obtain a copy of this License. You may not attempt to alter or\n restrict the recipientsโ€™ rights in the Source Code Form.\n\n3.2. Distribution of Executable Form\n\n If You distribute Covered Software in Executable Form then:\n\n a. such Covered Software must also be made available in Source Code Form,\n as described in Section 3.1, and You must inform recipients of the\n Executable Form how they can obtain a copy of such Source Code Form by\n reasonable means in a timely manner, at a charge no more than the cost\n of distribution to the recipient; and\n\n b. You may distribute such Executable Form under the terms of this License,\n or sublicense it under different terms, provided that the license for\n the Executable Form does not attempt to limit or alter the recipientsโ€™\n rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\n You may create and distribute a Larger Work under terms of Your choice,\n provided that You also comply with the requirements of this License for the\n Covered Software. If the Larger Work is a combination of Covered Software\n with a work governed by one or more Secondary Licenses, and the Covered\n Software is not Incompatible With Secondary Licenses, this License permits\n You to additionally distribute such Covered Software under the terms of\n such Secondary License(s), so that the recipient of the Larger Work may, at\n their option, further distribute the Covered Software under the terms of\n either this License or such Secondary License(s).\n\n3.4. Notices\n\n You may not remove or alter the substance of any license notices (including\n copyright notices, patent notices, disclaimers of warranty, or limitations\n of liability) contained within the Source Code Form of the Covered\n Software, except that You may alter any license notices to the extent\n required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\n You may choose to offer, and to charge a fee for, warranty, support,\n indemnity or liability obligations to one or more recipients of Covered\n Software. However, You may do so only on Your own behalf, and not on behalf\n of any Contributor. You must make it absolutely clear that any such\n warranty, support, indemnity, or liability obligation is offered by You\n alone, and You hereby agree to indemnify every Contributor for any\n liability incurred by such Contributor as a result of warranty, support,\n indemnity or liability terms You offer. You may include additional\n disclaimers of warranty and limitations of liability specific to any\n jurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n\n If it is impossible for You to comply with any of the terms of this License\n with respect to some or all of the Covered Software due to statute, judicial\n order, or regulation then You must: (a) comply with the terms of this License\n to the maximum extent possible; and (b) describe the limitations and the code\n they affect. Such description must be placed in a text file included with all\n distributions of the Covered Software under this License. Except to the\n extent prohibited by statute or regulation, such description must be\n sufficiently detailed for a recipient of ordinary skill to be able to\n understand it.\n\n5. Termination\n\n5.1. The rights granted under this License will terminate automatically if You\n fail to comply with any of its terms. However, if You become compliant,\n then the rights granted under this License from a particular Contributor\n are reinstated (a) provisionally, unless and until such Contributor\n explicitly and finally terminates Your grants, and (b) on an ongoing basis,\n if such Contributor fails to notify You of the non-compliance by some\n reasonable means prior to 60 days after You have come back into compliance.\n Moreover, Your grants from a particular Contributor are reinstated on an\n ongoing basis if such Contributor notifies You of the non-compliance by\n some reasonable means, this is the first time You have received notice of\n non-compliance with this License from such Contributor, and You become\n compliant prior to 30 days after Your receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\n infringement claim (excluding declaratory judgment actions, counter-claims,\n and cross-claims) alleging that a Contributor Version directly or\n indirectly infringes any patent, then the rights granted to You by any and\n all Contributors for the Covered Software under Section 2.1 of this License\n shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user\n license agreements (excluding distributors and resellers) which have been\n validly granted by You or Your distributors under this License prior to\n termination shall survive termination.\n\n6. Disclaimer of Warranty\n\n Covered Software is provided under this License on an โ€œas isโ€ basis, without\n warranty of any kind, either expressed, implied, or statutory, including,\n without limitation, warranties that the Covered Software is free of defects,\n merchantable, fit for a particular purpose or non-infringing. The entire\n risk as to the quality and performance of the Covered Software is with You.\n Should any Covered Software prove defective in any respect, You (not any\n Contributor) assume the cost of any necessary servicing, repair, or\n correction. This disclaimer of warranty constitutes an essential part of this\n License. No use of any Covered Software is authorized under this License\n except under this disclaimer.\n\n7. Limitation of Liability\n\n Under no circumstances and under no legal theory, whether tort (including\n negligence), contract, or otherwise, shall any Contributor, or anyone who\n distributes Covered Software as permitted above, be liable to You for any\n direct, indirect, special, incidental, or consequential damages of any\n character including, without limitation, damages for lost profits, loss of\n goodwill, work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses, even if such party shall have been\n informed of the possibility of such damages. This limitation of liability\n shall not apply to liability for death or personal injury resulting from such\n partyโ€™s negligence to the extent applicable law prohibits such limitation.\n Some jurisdictions do not allow the exclusion or limitation of incidental or\n consequential damages, so this exclusion and limitation may not apply to You.\n\n8. Litigation\n\n Any litigation relating to this License may be brought only in the courts of\n a jurisdiction where the defendant maintains its principal place of business\n and such litigation shall be governed by laws of that jurisdiction, without\n reference to its conflict-of-law provisions. Nothing in this Section shall\n prevent a partyโ€™s ability to bring cross-claims or counter-claims.\n\n9. Miscellaneous\n\n This License represents the complete agreement concerning the subject matter\n hereof. If any provision of this License is held to be unenforceable, such\n provision shall be reformed only to the extent necessary to make it\n enforceable. Any law or regulation which provides that the language of a\n contract shall be construed against the drafter shall not be used to construe\n this License against a Contributor.\n\n\n10. Versions of the License\n\n10.1. New Versions\n\n Mozilla Foundation is the license steward. Except as provided in Section\n 10.3, no one other than the license steward has the right to modify or\n publish new versions of this License. Each version will be given a\n distinguishing version number.\n\n10.2. Effect of New Versions\n\n You may distribute the Covered Software under the terms of the version of\n the License under which You originally received the Covered Software, or\n under the terms of any subsequent version published by the license\n steward.\n\n10.3. Modified Versions\n\n If you create software not governed by this License, and you want to\n create a new license for such software, you may create and use a modified\n version of this License if you rename the license and remove any\n references to the name of the license steward (except to note that such\n modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses\n If You choose to distribute Source Code Form that is Incompatible With\n Secondary Licenses under the terms of this version of the License, the\n notice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n\n This Source Code Form is subject to the\n terms of the Mozilla Public License, v.\n 2.0. If a copy of the MPL was not\n distributed with this file, You can\n obtain one at\n http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular file, then\nYou may include the notice in a location (such as a LICENSE file in a relevant\ndirectory) where a recipient would be likely to look for such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - โ€œIncompatible With Secondary Licensesโ€ Notice\n\n This Source Code Form is โ€œIncompatible\n With Secondary Licensesโ€, as defined by\n the Mozilla Public License, v. 2.0.\n" + }, { "name": "github.com/hashicorp/go-retryablehttp", "path": "github.com/hashicorp/go-retryablehttp/LICENSE", @@ -595,8 +765,8 @@ "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Huan Du\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n" }, { - "name": "github.com/jaytaylor/html2text", - "path": "github.com/jaytaylor/html2text/LICENSE", + "name": "github.com/inbucket/html2text", + "path": "github.com/inbucket/html2text/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Jay Taylor\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n" }, { @@ -704,6 +874,11 @@ "path": "github.com/mattn/go-runewidth/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2016 Yasuhiro Matsumoto\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/mattn/go-shellwords", + "path": "github.com/mattn/go-shellwords/LICENSE", + "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2017 Yasuhiro Matsumoto\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/meilisearch/meilisearch-go", "path": "github.com/meilisearch/meilisearch-go/LICENSE", @@ -715,8 +890,8 @@ "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": "github.com/mholt/archiver/v3", - "path": "github.com/mholt/archiver/v3/LICENSE", + "name": "github.com/mholt/archives", + "path": "github.com/mholt/archives/LICENSE", "licenseText": "MIT License\n\nCopyright (c) 2016 Matthew Holt\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." }, { @@ -729,6 +904,11 @@ "path": "github.com/miekg/dns/LICENSE", "licenseText": "BSD 3-Clause License\n\nCopyright (c) 2009, The Go Authors. Extensions copyright (c) 2011, Miek Gieben. \nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, + { + "name": "github.com/mikelolasagasti/xz", + "path": "github.com/mikelolasagasti/xz/LICENSE", + "licenseText": "Copyright (C) 2015-2017 Michael Cross \u003chttps://github.com/xi2\u003e\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n" + }, { "name": "github.com/minio/crc64nvme", "path": "github.com/minio/crc64nvme/LICENSE", @@ -744,6 +924,11 @@ "path": "github.com/minio/minio-go/v7/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/minio/minlz", + "path": "github.com/minio/minlz/LICENSE", + "licenseText": "\r\n Apache License\r\n Version 2.0, January 2004\r\n http://www.apache.org/licenses/\r\n\r\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\r\n\r\n 1. Definitions.\r\n\r\n \"License\" shall mean the terms and conditions for use, reproduction,\r\n and distribution as defined by Sections 1 through 9 of this document.\r\n\r\n \"Licensor\" shall mean the copyright owner or entity authorized by\r\n the copyright owner that is granting the License.\r\n\r\n \"Legal Entity\" shall mean the union of the acting entity and all\r\n other entities that control, are controlled by, or are under common\r\n control with that entity. For the purposes of this definition,\r\n \"control\" means (i) the power, direct or indirect, to cause the\r\n direction or management of such entity, whether by contract or\r\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\r\n outstanding shares, or (iii) beneficial ownership of such entity.\r\n\r\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\r\n exercising permissions granted by this License.\r\n\r\n \"Source\" form shall mean the preferred form for making modifications,\r\n including but not limited to software source code, documentation\r\n source, and configuration files.\r\n\r\n \"Object\" form shall mean any form resulting from mechanical\r\n transformation or translation of a Source form, including but\r\n not limited to compiled object code, generated documentation,\r\n and conversions to other media types.\r\n\r\n \"Work\" shall mean the work of authorship, whether in Source or\r\n Object form, made available under the License, as indicated by a\r\n copyright notice that is included in or attached to the work\r\n (an example is provided in the Appendix below).\r\n\r\n \"Derivative Works\" shall mean any work, whether in Source or Object\r\n form, that is based on (or derived from) the Work and for which the\r\n editorial revisions, annotations, elaborations, or other modifications\r\n represent, as a whole, an original work of authorship. For the purposes\r\n of this License, Derivative Works shall not include works that remain\r\n separable from, or merely link (or bind by name) to the interfaces of,\r\n the Work and Derivative Works thereof.\r\n\r\n \"Contribution\" shall mean any work of authorship, including\r\n the original version of the Work and any modifications or additions\r\n to that Work or Derivative Works thereof, that is intentionally\r\n submitted to Licensor for inclusion in the Work by the copyright owner\r\n or by an individual or Legal Entity authorized to submit on behalf of\r\n the copyright owner. For the purposes of this definition, \"submitted\"\r\n means any form of electronic, verbal, or written communication sent\r\n to the Licensor or its representatives, including but not limited to\r\n communication on electronic mailing lists, source code control systems,\r\n and issue tracking systems that are managed by, or on behalf of, the\r\n Licensor for the purpose of discussing and improving the Work, but\r\n excluding communication that is conspicuously marked or otherwise\r\n designated in writing by the copyright owner as \"Not a Contribution.\"\r\n\r\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\r\n on behalf of whom a Contribution has been received by Licensor and\r\n subsequently incorporated within the Work.\r\n\r\n 2. Grant of Copyright License. Subject to the terms and conditions of\r\n this License, each Contributor hereby grants to You a perpetual,\r\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n copyright license to reproduce, prepare Derivative Works of,\r\n publicly display, publicly perform, sublicense, and distribute the\r\n Work and such Derivative Works in Source or Object form.\r\n\r\n 3. Grant of Patent License. Subject to the terms and conditions of\r\n this License, each Contributor hereby grants to You a perpetual,\r\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n (except as stated in this section) patent license to make, have made,\r\n use, offer to sell, sell, import, and otherwise transfer the Work,\r\n where such license applies only to those patent claims licensable\r\n by such Contributor that are necessarily infringed by their\r\n Contribution(s) alone or by combination of their Contribution(s)\r\n with the Work to which such Contribution(s) was submitted. If You\r\n institute patent litigation against any entity (including a\r\n cross-claim or counterclaim in a lawsuit) alleging that the Work\r\n or a Contribution incorporated within the Work constitutes direct\r\n or contributory patent infringement, then any patent licenses\r\n granted to You under this License for that Work shall terminate\r\n as of the date such litigation is filed.\r\n\r\n 4. Redistribution. You may reproduce and distribute copies of the\r\n Work or Derivative Works thereof in any medium, with or without\r\n modifications, and in Source or Object form, provided that You\r\n meet the following conditions:\r\n\r\n (a) You must give any other recipients of the Work or\r\n Derivative Works a copy of this License; and\r\n\r\n (b) You must cause any modified files to carry prominent notices\r\n stating that You changed the files; and\r\n\r\n (c) You must retain, in the Source form of any Derivative Works\r\n that You distribute, all copyright, patent, trademark, and\r\n attribution notices from the Source form of the Work,\r\n excluding those notices that do not pertain to any part of\r\n the Derivative Works; and\r\n\r\n (d) If the Work includes a \"NOTICE\" text file as part of its\r\n distribution, then any Derivative Works that You distribute must\r\n include a readable copy of the attribution notices contained\r\n within such NOTICE file, excluding those notices that do not\r\n pertain to any part of the Derivative Works, in at least one\r\n of the following places: within a NOTICE text file distributed\r\n as part of the Derivative Works; within the Source form or\r\n documentation, if provided along with the Derivative Works; or,\r\n within a display generated by the Derivative Works, if and\r\n wherever such third-party notices normally appear. The contents\r\n of the NOTICE file are for informational purposes only and\r\n do not modify the License. You may add Your own attribution\r\n notices within Derivative Works that You distribute, alongside\r\n or as an addendum to the NOTICE text from the Work, provided\r\n that such additional attribution notices cannot be construed\r\n as modifying the License.\r\n\r\n You may add Your own copyright statement to Your modifications and\r\n may provide additional or different license terms and conditions\r\n for use, reproduction, or distribution of Your modifications, or\r\n for any such Derivative Works as a whole, provided Your use,\r\n reproduction, and distribution of the Work otherwise complies with\r\n the conditions stated in this License.\r\n\r\n 5. Submission of Contributions. Unless You explicitly state otherwise,\r\n any Contribution intentionally submitted for inclusion in the Work\r\n by You to the Licensor shall be under the terms and conditions of\r\n this License, without any additional terms or conditions.\r\n Notwithstanding the above, nothing herein shall supersede or modify\r\n the terms of any separate license agreement you may have executed\r\n with Licensor regarding such Contributions.\r\n\r\n 6. Trademarks. This License does not grant permission to use the trade\r\n names, trademarks, service marks, or product names of the Licensor,\r\n except as required for reasonable and customary use in describing the\r\n origin of the Work and reproducing the content of the NOTICE file.\r\n\r\n 7. Disclaimer of Warranty. Unless required by applicable law or\r\n agreed to in writing, Licensor provides the Work (and each\r\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\r\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r\n implied, including, without limitation, any warranties or conditions\r\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\r\n PARTICULAR PURPOSE. You are solely responsible for determining the\r\n appropriateness of using or redistributing the Work and assume any\r\n risks associated with Your exercise of permissions under this License.\r\n\r\n 8. Limitation of Liability. In no event and under no legal theory,\r\n whether in tort (including negligence), contract, or otherwise,\r\n unless required by applicable law (such as deliberate and grossly\r\n negligent acts) or agreed to in writing, shall any Contributor be\r\n liable to You for damages, including any direct, indirect, special,\r\n incidental, or consequential damages of any character arising as a\r\n result of this License or out of the use or inability to use the\r\n Work (including but not limited to damages for loss of goodwill,\r\n work stoppage, computer failure or malfunction, or any and all\r\n other commercial damages or losses), even if such Contributor\r\n has been advised of the possibility of such damages.\r\n\r\n 9. Accepting Warranty or Additional Liability. While redistributing\r\n the Work or Derivative Works thereof, You may choose to offer,\r\n and charge a fee for, acceptance of support, warranty, indemnity,\r\n or other liability obligations and/or rights consistent with this\r\n License. However, in accepting such obligations, You may act only\r\n on Your own behalf and on Your sole responsibility, not on behalf\r\n of any other Contributor, and only if You agree to indemnify,\r\n defend, and hold each Contributor harmless for any liability\r\n incurred by, or claims asserted against, such Contributor by reason\r\n of your accepting any such warranty or additional liability.\r\n\r\nEND OF TERMS AND CONDITIONS" + }, { "name": "github.com/mitchellh/mapstructure", "path": "github.com/mitchellh/mapstructure/LICENSE", @@ -764,21 +949,26 @@ "path": "github.com/munnerz/goautoneg/LICENSE", "licenseText": "Copyright (c) 2011, Open Knowledge Foundation Ltd.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n\n Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in\n the documentation and/or other materials provided with the\n distribution.\n\n Neither the name of the Open Knowledge Foundation Ltd. nor the\n names of its contributors may be used to endorse or promote\n products derived from this software without specific prior written\n permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, - { - "name": "github.com/nektos/act/pkg", - "path": "github.com/nektos/act/pkg/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2022 The Gitea Authors\nCopyright (c) 2019\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/niklasfasching/go-org/org", "path": "github.com/niklasfasching/go-org/org/LICENSE", "licenseText": "MIT License\n\nCopyright (c) 2018 Niklas Fasching\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/nwaples/rardecode", - "path": "github.com/nwaples/rardecode/LICENSE", + "name": "github.com/nwaples/rardecode/v2", + "path": "github.com/nwaples/rardecode/v2/LICENSE", "licenseText": "Copyright (c) 2015, Nicholas Waples\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, + { + "name": "github.com/olekukonko/errors", + "path": "github.com/olekukonko/errors/LICENSE", + "licenseText": "MIT License\n\nCopyright (c) 2025 Oleku Konko\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/olekukonko/ll", + "path": "github.com/olekukonko/ll/LICENSE", + "licenseText": "MIT License\n\nCopyright (c) 2025 Oleku Konko\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/olekukonko/tablewriter", "path": "github.com/olekukonko/tablewriter/LICENSE.md", @@ -904,6 +1094,11 @@ "path": "github.com/skeema/knownhosts/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": "github.com/sorairolake/lzip-go", + "path": "github.com/sorairolake/lzip-go/LICENSE", + "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n---\n\nMIT License\n\nCopyright (c) 2024 Shun Sakai\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/ssor/bom", "path": "github.com/ssor/bom/LICENSE", @@ -999,6 +1194,16 @@ "path": "go.uber.org/zap/exp/zapslog/LICENSE", "licenseText": "Copyright (c) 2016-2024 Uber Technologies, Inc.\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": "go.yaml.in/yaml/v3", + "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": "go4.org", + "path": "go4.org/LICENSE", + "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n" + }, { "name": "golang.org/x/crypto", "path": "golang.org/x/crypto/LICENSE", diff --git a/build/lint-locale-usage/allowed-masked-usage.txt b/build/lint-locale-usage/allowed-masked-usage.txt new file mode 100644 index 0000000000..06675530db --- /dev/null +++ b/build/lint-locale-usage/allowed-masked-usage.txt @@ -0,0 +1,54 @@ +# translation tooling test keys +meta.last_line +translation_meta.test + +# models/admin/task.go: instances of $TranslatableMessage.Format +# this also gets instantiated as a Messenger once +repo.migrate.migrating_failed.error + +# models/asymkey/gpg_key_object_verification.go: $ObjectVerification.Reason +# unfortunately, it is non-trivial to parse all the occurences +gpg.error.extract_sign +gpg.error.failed_retrieval_gpg_keys +gpg.error.generate_hash +gpg.error.no_committer_account + +# models/system/notice.go: func (n *Notice) TrStr() string +admin.notices.type_1 +admin.notices.type_2 + +# modules/setting/ui.go +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 + +# templates/repo/issue/view_content/comments.tmpl: indirection via $refTr +repo.issues.ref_closing_from +repo.issues.ref_issue_from +repo.issues.ref_pull_from +repo.issues.ref_reopening_from + +# templates/repo/issue/view_content/comments.tmpl: ctx.Locale.Tr (printf "projects.type-%d.display_name" .OldProject.Type) +projects. +projects.type-1.display_name +projects.type-2.display_name +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/handle-go.go b/build/lint-locale-usage/handle-go.go new file mode 100644 index 0000000000..a91a210313 --- /dev/null +++ b/build/lint-locale-usage/handle-go.go @@ -0,0 +1,217 @@ +// 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" + "strconv" + "strings" +) + +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) + if err != nil { + return + } + // found interesting strings + arg = prefix + arg + if strings.HasSuffix(arg, ".") || strings.HasSuffix(arg, "_") { + prep, trunc := PrepareMsgidPrefix(arg) + if trunc { + handler.OnWarning(fset, argLit.ValuePos, fmt.Sprintf("needed to truncate message id prefix: %s", arg)) + } + handler.OnMsgidPrefix(fset, argLit.ValuePos, prep, trunc) + } else { + handler.OnMsgid(fset, argLit.ValuePos, arg) + } + } +} + +func (handler Handler) handleGoTrArgument(fset *token.FileSet, n ast.Expr, prefix string) { + if argLit, ok := n.(*ast.BasicLit); ok { + handler.handleGoTrBasicLit(fset, argLit, prefix) + } else if argBinExpr, ok := n.(*ast.BinaryExpr); ok { + if argBinExpr.Op != token.ADD { + // pass + } else if argLit, ok := argBinExpr.X.(*ast.BasicLit); ok && argLit.Kind == token.STRING { + // extract string content + arg, err := strconv.Unquote(argLit.Value) + if err != nil { + return + } + // found interesting strings + arg = prefix + arg + prep, trunc := PrepareMsgidPrefix(arg) + if trunc { + handler.OnWarning(fset, argLit.ValuePos, fmt.Sprintf("needed to truncate message id prefix: %s", arg)) + } + handler.OnMsgidPrefix(fset, argLit.ValuePos, prep, trunc) + } + } +} + +func (handler Handler) handleGoCommentGroup(fset *token.FileSet, cg *ast.CommentGroup, commentPrefix string) *string { + if cg == nil { + return nil + } + var matches []token.Pos + matchInsPrefix := "" + commentPrefix = "//" + commentPrefix + for _, comment := range cg.List { + ctxt := strings.TrimSpace(comment.Text) + if ctxt == commentPrefix { + matches = append(matches, comment.Slash) + } else if after, found := strings.CutPrefix(ctxt, commentPrefix+"Suffix "); found { + matches = append(matches, comment.Slash) + matchInsPrefix = strings.TrimSpace(after) + } + } + switch len(matches) { + case 0: + return nil + case 1: + return &matchInsPrefix + default: + handler.OnWarning( + fset, + matches[0], + fmt.Sprintf("encountered multiple %s... directives, ignoring", strings.TrimSpace(commentPrefix)), + ) + 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 new file mode 100644 index 0000000000..a71d7d47e3 --- /dev/null +++ b/build/lint-locale-usage/handle-tmpl.go @@ -0,0 +1,233 @@ +// 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/token" + "os" + "strings" + "text/template" + tmplParser "text/template/parse" + + fjTemplates "forgejo.org/modules/templates" + "forgejo.org/modules/util" +) + +// derived from source: modules/templates/scopedtmpl/scopedtmpl.go, L169-L213 +func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.Node) { + switch node.Type() { + case tmplParser.NodeAction: + handler.handleTemplatePipeNode(fset, node.(*tmplParser.ActionNode).Pipe) + case tmplParser.NodeList: + nodeList := node.(*tmplParser.ListNode) + handler.handleTemplateFileNodes(fset, nodeList.Nodes) + case tmplParser.NodePipe: + handler.handleTemplatePipeNode(fset, node.(*tmplParser.PipeNode)) + case tmplParser.NodeTemplate: + handler.handleTemplatePipeNode(fset, node.(*tmplParser.TemplateNode).Pipe) + case tmplParser.NodeIf: + nodeIf := node.(*tmplParser.IfNode) + handler.handleTemplateBranchNode(fset, nodeIf.BranchNode) + case tmplParser.NodeRange: + nodeRange := node.(*tmplParser.RangeNode) + handler.handleTemplateBranchNode(fset, nodeRange.BranchNode) + case tmplParser.NodeWith: + nodeWith := node.(*tmplParser.WithNode) + handler.handleTemplateBranchNode(fset, nodeWith.BranchNode) + + case tmplParser.NodeCommand: + nodeCommand := node.(*tmplParser.CommandNode) + + handler.handleTemplateFileNodes(fset, nodeCommand.Args) + + if len(nodeCommand.Args) < 2 { + return + } + + funcname := "" + if nodeChain, ok := nodeCommand.Args[0].(*tmplParser.ChainNode); ok { + 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 { + if len(nodeField.Ident) != 2 || !(nodeField.Ident[0] == "locale" || nodeField.Ident[0] == "Locale") { + return + } + funcname = nodeField.Ident[1] + } + + var gotUnexpectedInvoke *int + ltf, ok := handler.LocaleTrFunctions[funcname] + if !ok { + return + } + + for _, argNum := range ltf { + if len(nodeCommand.Args) >= int(argNum+2) { + handler.handleTemplateMsgid(fset, nodeCommand.Args[int(argNum+1)]) + } else { + argc := len(nodeCommand.Args) - 1 + gotUnexpectedInvoke = &argc + } + } + + if gotUnexpectedInvoke != nil { + handler.OnUnexpectedInvoke(fset, token.Pos(nodeCommand.Pos), funcname, *gotUnexpectedInvoke) + } + + default: + } +} + +func (handler Handler) handleTemplateMsgid(fset *token.FileSet, node tmplParser.Node) { + // the column numbers are a bit "off", but much better than nothing + pos := token.Pos(node.Position()) + + switch node.Type() { + case tmplParser.NodeString: + nodeString := node.(*tmplParser.StringNode) + // found interesting strings + handler.OnMsgid(fset, pos, nodeString.Text) + + case tmplParser.NodePipe: + nodePipe := node.(*tmplParser.PipeNode) + handler.handleTemplatePipeNode(fset, nodePipe) + + if len(nodePipe.Cmds) == 0 { + handler.OnWarning(fset, pos, fmt.Sprintf("unsupported invocation of locate function (no commands): %s", node.String())) + } else if len(nodePipe.Cmds) != 1 { + handler.OnWarning(fset, pos, fmt.Sprintf("unsupported invocation of locate function (too many commands): %s", node.String())) + return + } + nodeCommand := nodePipe.Cmds[0] + if len(nodeCommand.Args) < 2 { + handler.OnWarning(fset, pos, fmt.Sprintf("unsupported invocation of locate function (not enough arguments): %s", node.String())) + return + } + + nodeIdent, ok := nodeCommand.Args[0].(*tmplParser.IdentifierNode) + if !ok || (nodeIdent.Ident != "print" && nodeIdent.Ident != "printf") { + // handler.OnWarning(fset, pos, fmt.Sprintf("unsupported invocation of locate function (bad command): %s", node.String())) + return + } + + nodeString, ok := nodeCommand.Args[1].(*tmplParser.StringNode) + if !ok { + //handler.OnWarning( + // fset, + // pos, + // fmt.Sprintf("unsupported invocation of locate function (string should be first argument to %s): %s", nodeIdent.Ident, node.String()), + //) + return + } + + msgidPrefix := nodeString.Text + stringPos := token.Pos(nodeString.Pos) + + if len(nodeCommand.Args) == 2 { + // found interesting strings + handler.OnMsgid(fset, stringPos, msgidPrefix) + } else { + if nodeIdent.Ident == "printf" { + parts := strings.SplitN(msgidPrefix, "%", 2) + if len(parts) != 2 { + handler.OnWarning( + fset, + stringPos, + fmt.Sprintf("unsupported invocation of locate function (format string doesn't match \"prefix%%smth\" pattern): %s", nodeString.String()), + ) + return + } + msgidPrefix = parts[0] + } + + msgidPrefixFin, truncated := PrepareMsgidPrefix(msgidPrefix) + if truncated { + handler.OnWarning(fset, stringPos, fmt.Sprintf("needed to truncate message id prefix: %s", msgidPrefix)) + } + + // found interesting strings + handler.OnMsgidPrefix(fset, stringPos, msgidPrefixFin, truncated) + } + + default: + // handler.OnWarning(fset, pos, fmt.Sprintf("unknown invocation of locate function: %s", node.String())) + } +} + +func (handler Handler) handleTemplatePipeNode(fset *token.FileSet, pipeNode *tmplParser.PipeNode) { + if pipeNode == nil { + return + } + + // NOTE: we can't pass `pipeNode.Cmds` to handleTemplateFileNodes due to incompatible argument types + for _, node := range pipeNode.Cmds { + handler.handleTemplateNode(fset, node) + } +} + +func (handler Handler) handleTemplateBranchNode(fset *token.FileSet, branchNode tmplParser.BranchNode) { + handler.handleTemplatePipeNode(fset, branchNode.Pipe) + handler.handleTemplateFileNodes(fset, branchNode.List.Nodes) + if branchNode.ElseList != nil { + handler.handleTemplateFileNodes(fset, branchNode.ElseList.Nodes) + } +} + +func (handler Handler) handleTemplateFileNodes(fset *token.FileSet, nodes []tmplParser.Node) { + for _, node := range nodes { + handler.handleTemplateNode(fset, node) + } +} + +// 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) HandleTemplateFile(fname string, src any) error { + var tmplContent []byte + switch src2 := src.(type) { + case nil: + var err error + tmplContent, err = os.ReadFile(fname) + if err != nil { + return LocatedError{ + Location: fname, + Kind: "ReadFile", + Err: err, + } + } + case []byte: + tmplContent = src2 + case string: + // SAFETY: we do not modify tmplContent below + tmplContent = util.UnsafeStringToBytes(src2) + default: + panic("invalid type for 'src'") + } + + fset := token.NewFileSet() + fset.AddFile(fname, 1, len(tmplContent)).SetLinesForContent(tmplContent) + // SAFETY: we do not modify tmplContent2 below + tmplContent2 := util.UnsafeBytesToString(tmplContent) + + tmpl := template.New(fname) + tmpl.Funcs(fjTemplates.NewFuncMap()) + tmplParsed, err := tmpl.Parse(tmplContent2) + if err != nil { + return LocatedError{ + Location: fname, + Kind: "Template parser", + Err: err, + } + } + handler.handleTemplateFileNodes(fset, tmplParsed.Root.Nodes) + return nil +} diff --git a/build/lint-locale-usage/lint-locale-usage.go b/build/lint-locale-usage/lint-locale-usage.go index 88375c1c36..8c67a781e9 100644 --- a/build/lint-locale-usage/lint-locale-usage.go +++ b/build/lint-locale-usage/lint-locale-usage.go @@ -5,22 +5,19 @@ package main import ( + "bufio" + "errors" + "flag" "fmt" - "go/ast" - goParser "go/parser" "go/token" "io/fs" "os" "path/filepath" - "strconv" + "sort" "strings" - "text/template" - tmplParser "text/template/parse" "forgejo.org/modules/container" - fjTemplates "forgejo.org/modules/templates" "forgejo.org/modules/translation/localeiter" - "forgejo.org/modules/util" ) // this works by first gathering all valid source string IDs from `en-US` reference files @@ -63,241 +60,180 @@ func InitLocaleTrFunctions() map[string][]uint { 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 } -// 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`} +type StringTrie interface { + Matches(key []string) bool +} -func (handler Handler) HandleGoFile(fname string, src any) error { - fset := token.NewFileSet() - node, err := goParser.ParseFile(fset, fname, src, goParser.SkipObjectResolution) - 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, ...)` - - call, ok := n.(*ast.CallExpr) - if !ok || len(call.Args) < 1 { - return true - } - - funSel, ok := call.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(call.Args) >= int(argNum+1) { - argLit, ok := call.Args[int(argNum)].(*ast.BasicLit) - if !ok || argLit.Kind != token.STRING { - continue - } - - // extract string content - arg, err := strconv.Unquote(argLit.Value) - if err == nil { - // found interesting strings - handler.OnMsgid(fset, argLit.ValuePos, arg) - } - } else { - argc := len(call.Args) - gotUnexpectedInvoke = &argc - } - } - - if gotUnexpectedInvoke != nil { - handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke) - } +type StringTrieMap map[string]StringTrie +func (m StringTrieMap) Matches(key []string) bool { + if len(key) == 0 || m == nil { return true - }) - - return nil -} - -// derived from source: modules/templates/scopedtmpl/scopedtmpl.go, L169-L213 -func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.Node) { - switch node.Type() { - case tmplParser.NodeAction: - handler.handleTemplatePipeNode(fset, node.(*tmplParser.ActionNode).Pipe) - case tmplParser.NodeList: - nodeList := node.(*tmplParser.ListNode) - handler.handleTemplateFileNodes(fset, nodeList.Nodes) - case tmplParser.NodePipe: - handler.handleTemplatePipeNode(fset, node.(*tmplParser.PipeNode)) - case tmplParser.NodeTemplate: - handler.handleTemplatePipeNode(fset, node.(*tmplParser.TemplateNode).Pipe) - case tmplParser.NodeIf: - nodeIf := node.(*tmplParser.IfNode) - handler.handleTemplateBranchNode(fset, nodeIf.BranchNode) - case tmplParser.NodeRange: - nodeRange := node.(*tmplParser.RangeNode) - handler.handleTemplateBranchNode(fset, nodeRange.BranchNode) - case tmplParser.NodeWith: - nodeWith := node.(*tmplParser.WithNode) - handler.handleTemplateBranchNode(fset, nodeWith.BranchNode) - - case tmplParser.NodeCommand: - nodeCommand := node.(*tmplParser.CommandNode) - - handler.handleTemplateFileNodes(fset, nodeCommand.Args) - - if len(nodeCommand.Args) < 2 { - return - } - - nodeChain, ok := nodeCommand.Args[0].(*tmplParser.ChainNode) - if !ok { - return - } - - nodeIdent, ok := nodeChain.Node.(*tmplParser.IdentifierNode) - if !ok || nodeIdent.Ident != "ctx" || len(nodeChain.Field) != 2 || nodeChain.Field[0] != "Locale" { - return - } - - ltf, ok := handler.LocaleTrFunctions[nodeChain.Field[1]] - if !ok { - return - } - - var gotUnexpectedInvoke *int - - for _, argNum := range ltf { - if len(nodeCommand.Args) >= int(argNum+2) { - nodeString, ok := nodeCommand.Args[int(argNum+1)].(*tmplParser.StringNode) - if ok { - // found interesting strings - // the column numbers are a bit "off", but much better than nothing - handler.OnMsgid(fset, token.Pos(nodeString.Pos), nodeString.Text) - } - } else { - argc := len(nodeCommand.Args) - 1 - gotUnexpectedInvoke = &argc - } - } - - if gotUnexpectedInvoke != nil { - handler.OnUnexpectedInvoke(fset, token.Pos(nodeChain.Pos), nodeChain.Field[1], *gotUnexpectedInvoke) - } - - default: } + value, ok := m[key[0]] + if !ok { + return false + } + if value == nil { + return true + } + return value.Matches(key[1:]) } -func (handler Handler) handleTemplatePipeNode(fset *token.FileSet, pipeNode *tmplParser.PipeNode) { - if pipeNode == nil { +func (m StringTrieMap) Insert(key []string) { + if m == nil { return } - // NOTE: we can't pass `pipeNode.Cmds` to handleTemplateFileNodes due to incompatible argument types - for _, node := range pipeNode.Cmds { - handler.handleTemplateNode(fset, node) - } -} + switch len(key) { + case 0: + return -func (handler Handler) handleTemplateBranchNode(fset *token.FileSet, branchNode tmplParser.BranchNode) { - handler.handleTemplatePipeNode(fset, branchNode.Pipe) - handler.handleTemplateFileNodes(fset, branchNode.List.Nodes) - if branchNode.ElseList != nil { - handler.handleTemplateFileNodes(fset, branchNode.ElseList.Nodes) - } -} + case 1: + m[key[0]] = nil -func (handler Handler) handleTemplateFileNodes(fset *token.FileSet, nodes []tmplParser.Node) { - for _, node := range nodes { - handler.handleTemplateNode(fset, node) - } -} - -func (handler Handler) HandleTemplateFile(fname string, src any) error { - var tmplContent []byte - switch src2 := src.(type) { - case nil: - var err error - tmplContent, err = os.ReadFile(fname) - if err != nil { - return LocatedError{ - Location: fname, - Kind: "ReadFile", - Err: err, - } - } - case []byte: - tmplContent = src2 - case string: - // SAFETY: we do not modify tmplContent below - tmplContent = util.UnsafeStringToBytes(src2) default: - panic("invalid type for 'src'") + if value, ok := m[key[0]]; ok { + if value == nil { + return + } + } else { + m[key[0]] = make(StringTrieMap) + } + m[key[0]].(StringTrieMap).Insert(key[1:]) } +} - fset := token.NewFileSet() - fset.AddFile(fname, 1, len(tmplContent)).SetLinesForContent(tmplContent) - // SAFETY: we do not modify tmplContent2 below - tmplContent2 := util.UnsafeBytesToString(tmplContent) - - tmpl := template.New(fname) - tmpl.Funcs(fjTemplates.NewFuncMap()) - tmplParsed, err := tmpl.Parse(tmplContent2) +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{ Location: fname, - Kind: "Template parser", + Kind: "Open", + Err: err, + } + } + defer file.Close() + + scanner := bufio.NewScanner(file) + lno := 0 + for scanner.Scan() { + lno++ + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + if linePrefix, found := strings.CutSuffix(line, "."); found { + allowedMaskedPrefixes.Insert(strings.Split(linePrefix, ".")) + } else { + if !chkMsgid(line) { + return LocatedError{ + Location: fmt.Sprintf("%s: line %d", fname, lno), + Kind: "undefined msgid", + Err: errors.New(line), + } + } + usedMsgids.Add(line) + } + } + if err := scanner.Err(); err != nil { + return LocatedError{ + Location: fname, + Kind: "Scanner", Err: err, } } - handler.handleTemplateFileNodes(fset, tmplParsed.Root.Nodes) return nil } -// This command assumes that we get started from the project root directory -// -// Possible command line flags: -// -// --allow-missing-msgids don't return an error code if missing message IDs are found -// -// EXIT CODES: -// -// 0 success, no issues found -// 1 unable to walk directory tree -// 2 unable to parse locale ini/json files -// 3 unable to parse go or text/template files -// 4 found missing message IDs -// +// 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]) + flag.PrintDefaults() + + fmt.Fprintf(outp, "\nThis command assumes that it gets started from the project root directory.\n") + + fmt.Fprintf(outp, "\nExit codes:\n") + for _, i := range []string{ + "0\tsuccess, no issues found", + "1\tunable to walk directory tree", + "2\tunable to parse locale ini/json files", + "3\tunable to parse go or text/template files", + "4\tfound missing message IDs", + "5\tfound unused message IDs", + } { + fmt.Fprintf(outp, "\t%s\n", i) + } + + fmt.Fprintf(outp, "\nSpecial Go doc comments:\n") + for _, i := range []string{ + "//llu:returnsTrKey", + "\tcan be used in front of functions to indicate", + "\tthat the function returns message IDs", + "\tWARNING: this currently doesn't support nested functions properly", + "", + "//llu:returnsTrKeySuffix prefix.", + "\tsimilar to llu:returnsTrKey, but the given prefix is prepended", + "\tto the found strings before interpreting them as msgids", + "", + "// llu:TrKeys", + "\tcan be used in front of 'const' and 'var' blocks", + "\tin order to mark all contained strings as message IDs", + "", + "// llu:TrKeysSuffix prefix.", + "\tlike llu:returnsTrKeySuffix, but for 'const' and 'var' blocks", + } { + if i == "" { + fmt.Fprintf(outp, "\n") + } else { + fmt.Fprintf(outp, "\t%s\n", i) + } + } +} + //nolint:forbidigo func main() { allowMissingMsgids := false - for _, arg := range os.Args[1:] { - if arg == "--allow-missing-msgids" { - allowMissingMsgids = true - } - } + allowUnusedMsgids := false + usedMsgids := make(container.Set[string]) + allowedMaskedPrefixes := make(StringTrieMap) - onError := func(err error) { - if err == nil { - return - } - fmt.Println(err.Error()) - os.Exit(3) + // It's possible for execl to hand us an empty os.Args. + if len(os.Args) == 0 { + flag.CommandLine = flag.NewFlagSet("lint-locale-usage", flag.ExitOnError) + } else { + flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) } + flag.CommandLine.Usage = Usage + flag.Usage = Usage + + flag.BoolVar( + &allowMissingMsgids, + "allow-missing-msgids", + false, + "don't return an error code if missing message IDs are found", + ) + flag.BoolVar( + &allowUnusedMsgids, + "allow-unused-msgids", + false, + "don't return an error code if unused message IDs are found", + ) msgids := make(container.Set[string]) @@ -334,17 +270,50 @@ func main() { gotAnyMsgidError := false + flag.Func( + "allow-masked-usages-from", + "supply a file containing a newline-separated list of allowed masked usages", + func(argval string) error { + return ParseAllowedMaskedUsages(argval, usedMsgids, allowedMaskedPrefixes, func(msgid string) bool { + return msgids.Contains(msgid) + }) + }, + ) + flag.Parse() + + onError := func(err error) { + if err == nil { + return + } + fmt.Println(err.Error()) + os.Exit(3) + } + handler := Handler{ + OnMsgidPrefix: func(fset *token.FileSet, pos token.Pos, msgidPrefix string, truncated bool) { + msgidPrefixSplit := strings.Split(msgidPrefix, ".") + if !truncated { + allowedMaskedPrefixes.Insert(msgidPrefixSplit) + } else if !allowedMaskedPrefixes.Matches(msgidPrefixSplit) { + gotAnyMsgidError = true + fmt.Printf("%s:\tmissing msgid prefix: %s\n", fset.Position(pos).String(), msgidPrefix) + } + }, OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) { if !msgids.Contains(msgid) { gotAnyMsgidError = true fmt.Printf("%s:\tmissing msgid: %s\n", fset.Position(pos).String(), msgid) + } else { + usedMsgids.Add(msgid) } }, OnUnexpectedInvoke: func(fset *token.FileSet, pos token.Pos, funcname string, argc int) { gotAnyMsgidError = true fmt.Printf("%s:\tunexpected invocation of %s with %d arguments\n", fset.Position(pos).String(), funcname, argc) }, + OnWarning: func(fset *token.FileSet, pos token.Pos, msg string) { + fmt.Printf("%s:\tWARNING: %s\n", fset.Position(pos).String(), msg) + }, LocaleTrFunctions: InitLocaleTrFunctions(), } @@ -377,7 +346,27 @@ func main() { os.Exit(1) } + unusedMsgids := []string{} + + for msgid := range msgids { + if !usedMsgids.Contains(msgid) && !allowedMaskedPrefixes.Matches(strings.Split(msgid, ".")) { + unusedMsgids = append(unusedMsgids, msgid) + } + } + + sort.Strings(unusedMsgids) + + if len(unusedMsgids) != 0 { + fmt.Printf("=== unused msgids (%d): ===\n", len(unusedMsgids)) + for _, msgid := range unusedMsgids { + fmt.Printf("- %s\n", msgid) + } + } + if !allowMissingMsgids && gotAnyMsgidError { os.Exit(4) } + if !allowUnusedMsgids && len(unusedMsgids) != 0 { + os.Exit(5) + } } diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go index cb95b3b3c8..91b344b1e9 100644 --- a/cmd/admin_auth.go +++ b/cmd/admin_auth.go @@ -17,6 +17,15 @@ import ( "github.com/urfave/cli/v3" ) +type ( + authService struct { + initDB func(ctx context.Context) error + createAuthSource func(context.Context, *auth_model.Source) error + updateAuthSource func(context.Context, *auth_model.Source) error + getAuthSourceByID func(ctx context.Context, id int64) (*auth_model.Source, error) + } +) + func microcmdAuthDelete() *cli.Command { return &cli.Command{ Name: "delete", @@ -60,6 +69,16 @@ func microcmdAuthList() *cli.Command { } } +// newAuthService creates a service with default functions. +func newAuthService() *authService { + return &authService{ + initDB: initDB, + createAuthSource: auth_model.CreateSource, + updateAuthSource: auth_model.UpdateSource, + getAuthSourceByID: auth_model.GetSourceByID, + } +} + func runListAuth(ctx context.Context, c *cli.Command) error { ctx, cancel := installSignals(ctx) defer cancel() diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 997d6b3a16..9af6c331d3 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -14,15 +14,6 @@ import ( "github.com/urfave/cli/v3" ) -type ( - authService struct { - initDB func(ctx context.Context) error - createAuthSource func(context.Context, *auth.Source) error - updateAuthSource func(context.Context, *auth.Source) error - getAuthSourceByID func(ctx context.Context, id int64) (*auth.Source, error) - } -) - func commonLdapCLIFlags() []cli.Flag { return []cli.Flag{ &cli.StringFlag{ @@ -184,16 +175,6 @@ func microcmdAuthUpdateLdapSimpleAuth() *cli.Command { } } -// newAuthService creates a service with default functions. -func newAuthService() *authService { - return &authService{ - initDB: initDB, - createAuthSource: auth.CreateSource, - updateAuthSource: auth.UpdateSource, - getAuthSourceByID: auth.GetSourceByID, - } -} - // parseAuthSource assigns values on authSource according to command line flags. func parseAuthSource(c *cli.Command, authSource *auth.Source) { if c.IsSet("name") { diff --git a/cmd/admin_auth_oauth.go b/cmd/admin_auth_oauth.go index abdcd5d48a..ef5d9116e3 100644 --- a/cmd/admin_auth_oauth.go +++ b/cmd/admin_auth_oauth.go @@ -86,6 +86,11 @@ func oauthCLIFlags() []cli.Flag { Value: nil, Usage: "Scopes to request when to authenticate against this OAuth2 source", }, + &cli.StringFlag{ + Name: "attribute-ssh-public-key", + Value: "", + Usage: "Claim name providing SSH public keys for this source", + }, &cli.StringFlag{ Name: "required-claim-name", Value: "", @@ -120,6 +125,10 @@ func oauthCLIFlags() []cli.Flag { Name: "group-team-map-removal", Usage: "Activate automatic team membership removal depending on groups", }, + &cli.BoolFlag{ + Name: "allow-username-change", + Usage: "Allow users to change their username", + }, } } @@ -127,7 +136,7 @@ func microcmdAuthAddOauth() *cli.Command { return &cli.Command{ Name: "add-oauth", Usage: "Add new Oauth authentication source", - Action: runAddOauth, + Action: newAuthService().addOauth, Flags: oauthCLIFlags(), } } @@ -136,7 +145,7 @@ func microcmdAuthUpdateOauth() *cli.Command { return &cli.Command{ Name: "update-oauth", Usage: "Update existing Oauth authentication source", - Action: runUpdateOauth, + Action: newAuthService().updateOauth, Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{idFlag()}, oauthCLIFlags()[1:]...)...), } } @@ -163,6 +172,7 @@ func parseOAuth2Config(_ context.Context, c *cli.Command) *oauth2.Source { IconURL: c.String("icon-url"), SkipLocalTwoFA: c.Bool("skip-local-2fa"), Scopes: c.StringSlice("scopes"), + AttributeSSHPublicKey: c.String("attribute-ssh-public-key"), RequiredClaimName: c.String("required-claim-name"), RequiredClaimValue: c.String("required-claim-value"), GroupClaimName: c.String("group-claim-name"), @@ -170,14 +180,15 @@ func parseOAuth2Config(_ context.Context, c *cli.Command) *oauth2.Source { RestrictedGroup: c.String("restricted-group"), GroupTeamMap: c.String("group-team-map"), GroupTeamMapRemoval: c.Bool("group-team-map-removal"), + AllowUsernameChange: c.Bool("allow-username-change"), } } -func runAddOauth(ctx context.Context, c *cli.Command) error { +func (a *authService) addOauth(ctx context.Context, c *cli.Command) error { ctx, cancel := installSignals(ctx) defer cancel() - if err := initDB(ctx); err != nil { + if err := a.initDB(ctx); err != nil { return err } @@ -189,7 +200,7 @@ func runAddOauth(ctx context.Context, c *cli.Command) error { } } - return auth_model.CreateSource(ctx, &auth_model.Source{ + return a.createAuthSource(ctx, &auth_model.Source{ Type: auth_model.OAuth2, Name: c.String("name"), IsActive: true, @@ -197,7 +208,7 @@ func runAddOauth(ctx context.Context, c *cli.Command) error { }) } -func runUpdateOauth(ctx context.Context, c *cli.Command) error { +func (a *authService) updateOauth(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } @@ -205,11 +216,11 @@ func runUpdateOauth(ctx context.Context, c *cli.Command) error { ctx, cancel := installSignals(ctx) defer cancel() - if err := initDB(ctx); err != nil { + if err := a.initDB(ctx); err != nil { return err } - source, err := auth_model.GetSourceByID(ctx, c.Int64("id")) + source, err := a.getAuthSourceByID(ctx, c.Int64("id")) if err != nil { return err } @@ -244,6 +255,10 @@ func runUpdateOauth(ctx context.Context, c *cli.Command) error { oAuth2Config.Scopes = c.StringSlice("scopes") } + if c.IsSet("attribute-ssh-public-key") { + oAuth2Config.AttributeSSHPublicKey = c.String("attribute-ssh-public-key") + } + if c.IsSet("required-claim-name") { oAuth2Config.RequiredClaimName = c.String("required-claim-name") } @@ -267,6 +282,10 @@ func runUpdateOauth(ctx context.Context, c *cli.Command) error { oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal") } + if c.IsSet("allow-username-change") { + oAuth2Config.AllowUsernameChange = c.Bool("allow-username-change") + } + // update custom URL mapping customURLMapping := &oauth2.CustomURLMapping{} @@ -300,5 +319,5 @@ func runUpdateOauth(ctx context.Context, c *cli.Command) error { oAuth2Config.CustomURLMapping = customURLMapping source.Cfg = oAuth2Config - return auth_model.UpdateSource(ctx, source) + return a.updateAuthSource(ctx, source) } diff --git a/cmd/admin_auth_oauth_test.go b/cmd/admin_auth_oauth_test.go new file mode 100644 index 0000000000..3430ad1f56 --- /dev/null +++ b/cmd/admin_auth_oauth_test.go @@ -0,0 +1,706 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package cmd + +import ( + "context" + "testing" + + "forgejo.org/models/auth" + "forgejo.org/modules/test" + "forgejo.org/services/auth/source/oauth2" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v3" +) + +func TestAddOauth(t *testing.T) { + // Mock cli functions to do not exit on error + defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() + + // Test cases + cases := []struct { + args []string + source *auth.Source + errMsg string + }{ + // case 0 + { + args: []string{ + "oauth-test", + "--name", "oauth2 (via openidConnect) source full", + "--provider", "openidConnect", + "--key", "client id", + "--secret", "client secret", + "--auto-discover-url", "https://example.com/.well-known/openid-configuration", + "--use-custom-urls", "", + "--custom-tenant-id", "tenant id", + "--custom-auth-url", "https://example.com/auth", + "--custom-token-url", "https://example.com/token", + "--custom-profile-url", "https://example.com/profile", + "--custom-email-url", "https://example.com/email", + "--icon-url", "https://example.com/icon.svg", + "--skip-local-2fa", + "--scopes", "address", + "--scopes", "email", + "--scopes", "phone", + "--scopes", "profile", + "--attribute-ssh-public-key", "ssh_public_key", + "--required-claim-name", "can_access", + "--required-claim-value", "yes", + "--group-claim-name", "groups", + "--admin-group", "admin", + "--restricted-group", "restricted", + "--group-team-map", `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`, + "--group-team-map-removal", + "--allow-username-change", + }, + source: &auth.Source{ + Type: auth.OAuth2, + Name: "oauth2 (via openidConnect) source full", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "openidConnect", + ClientID: "client id", + ClientSecret: "client secret", + OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration", + CustomURLMapping: &oauth2.CustomURLMapping{ + AuthURL: "https://example.com/auth", + TokenURL: "https://example.com/token", + ProfileURL: "https://example.com/profile", + EmailURL: "https://example.com/email", + Tenant: "tenant id", + }, + IconURL: "https://example.com/icon.svg", + Scopes: []string{"address", "email", "phone", "profile"}, + AttributeSSHPublicKey: "ssh_public_key", + RequiredClaimName: "can_access", + RequiredClaimValue: "yes", + GroupClaimName: "groups", + AdminGroup: "admin", + GroupTeamMap: `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`, + GroupTeamMapRemoval: true, + RestrictedGroup: "restricted", + SkipLocalTwoFA: true, + AllowUsernameChange: true, + }, + }, + }, + // case 1 + { + args: []string{ + "oauth-test", + "--name", "oauth2 (via openidConnect) source min", + "--provider", "openidConnect", + "--auto-discover-url", "https://example.com/.well-known/openid-configuration", + }, + source: &auth.Source{ + Type: auth.OAuth2, + Name: "oauth2 (via openidConnect) source min", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "openidConnect", + OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration", + Scopes: []string{}, + }, + }, + }, + // case 2 + { + args: []string{ + "oauth-test", + "--name", "oauth2 (via openidConnect) source `--use-custom-urls` required for `--custom-*` flags", + "--custom-tenant-id", "tenant id", + "--custom-auth-url", "https://example.com/auth", + "--custom-token-url", "https://example.com/token", + "--custom-profile-url", "https://example.com/profile", + "--custom-email-url", "https://example.com/email", + }, + source: &auth.Source{ + Type: auth.OAuth2, + Name: "oauth2 (via openidConnect) source `--use-custom-urls` required for `--custom-*` flags", + IsActive: true, + Cfg: &oauth2.Source{ + Scopes: []string{}, + }, + }, + }, + // case 3 + { + args: []string{ + "oauth-test", + "--name", "oauth2 (via openidConnect) source `--scopes` aggregates multiple uses", + "--provider", "openidConnect", + "--auto-discover-url", "https://example.com/.well-known/openid-configuration", + "--scopes", "address", + "--scopes", "email", + "--scopes", "phone", + "--scopes", "profile", + }, + source: &auth.Source{ + Type: auth.OAuth2, + Name: "oauth2 (via openidConnect) source `--scopes` aggregates multiple uses", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "openidConnect", + OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration", + Scopes: []string{"address", "email", "phone", "profile"}, + }, + }, + }, + // case 4 + { + args: []string{ + "oauth-test", + "--name", "oauth2 (via openidConnect) source `--scopes` supports commas as separators", + "--provider", "openidConnect", + "--auto-discover-url", "https://example.com/.well-known/openid-configuration", + "--scopes", "address,email,phone,profile", + }, + source: &auth.Source{ + Type: auth.OAuth2, + Name: "oauth2 (via openidConnect) source `--scopes` supports commas as separators", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "openidConnect", + OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration", + Scopes: []string{"address", "email", "phone", "profile"}, + }, + }, + }, + // case 5 + { + args: []string{ + "oauth-test", + "--name", "oauth2 (via openidConnect) source", + "--provider", "openidConnect", + }, + errMsg: "invalid Auto Discovery URL: (this must be a valid URL starting with http:// or https://)", + }, + // case 6 + { + args: []string{ + "oauth-test", + "--name", "oauth2 (via openidConnect) source", + "--provider", "openidConnect", + "--auto-discover-url", "example.com", + }, + errMsg: "invalid Auto Discovery URL: example.com (this must be a valid URL starting with http:// or https://)", + }, + } + + for n, c := range cases { + // Mock functions. + var createdAuthSource *auth.Source + service := &authService{ + initDB: func(context.Context) error { + return nil + }, + createAuthSource: func(ctx context.Context, authSource *auth.Source) error { + createdAuthSource = authSource + return nil + }, + updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { + assert.FailNow(t, "should not call updateAuthSource", "case: %d", n) + return nil + }, + getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { + assert.FailNow(t, "should not call getAuthSourceByID", "case: %d", n) + return nil, nil + }, + } + + // Create a copy of command to test + app := cli.Command{} + app.Flags = microcmdAuthAddOauth().Flags + app.Action = service.addOauth + + // Run it + err := app.Run(t.Context(), c.args) + if c.errMsg != "" { + assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) + } else { + require.NoError(t, err, "case %d: should have no errors", n) + assert.Equal(t, c.source, createdAuthSource, "case %d: wrong authSource", n) + } + } +} + +func TestUpdateOauth(t *testing.T) { + // Mock cli functions to do not exit on error + defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() + + // Test cases + cases := []struct { + args []string + id int64 + existingAuthSource *auth.Source + authSource *auth.Source + errMsg string + }{ + // case 0 + { + args: []string{ + "oauth-test", + "--id", "23", + "--name", "oauth2 (via openidConnect) source full", + "--provider", "openidConnect", + "--key", "client id", + "--secret", "client secret", + "--auto-discover-url", "https://example.com/.well-known/openid-configuration", + "--use-custom-urls", "", + "--custom-tenant-id", "tenant id", + "--custom-auth-url", "https://example.com/auth", + "--custom-token-url", "https://example.com/token", + "--custom-profile-url", "https://example.com/profile", + "--custom-email-url", "https://example.com/email", + "--icon-url", "https://example.com/icon.svg", + "--skip-local-2fa", + "--scopes", "address", + "--scopes", "email", + "--scopes", "phone", + "--scopes", "profile", + "--attribute-ssh-public-key", "ssh_public_key", + "--required-claim-name", "can_access", + "--required-claim-value", "yes", + "--group-claim-name", "groups", + "--admin-group", "admin", + "--restricted-group", "restricted", + "--group-team-map", `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`, + "--group-team-map-removal", + }, + id: 23, + existingAuthSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{}, + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Name: "oauth2 (via openidConnect) source full", + Cfg: &oauth2.Source{ + Provider: "openidConnect", + ClientID: "client id", + ClientSecret: "client secret", + OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration", + CustomURLMapping: &oauth2.CustomURLMapping{ + AuthURL: "https://example.com/auth", + TokenURL: "https://example.com/token", + ProfileURL: "https://example.com/profile", + EmailURL: "https://example.com/email", + Tenant: "tenant id", + }, + IconURL: "https://example.com/icon.svg", + Scopes: []string{"address", "email", "phone", "profile"}, + AttributeSSHPublicKey: "ssh_public_key", + RequiredClaimName: "can_access", + RequiredClaimValue: "yes", + GroupClaimName: "groups", + AdminGroup: "admin", + GroupTeamMap: `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`, + GroupTeamMapRemoval: true, + RestrictedGroup: "restricted", + // `--skip-local-2fa` is currently ignored. + // SkipLocalTwoFA: true, + }, + }, + }, + // case 1 + { + args: []string{ + "oauth-test", + "--id", "1", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + }, + }, + }, + // case 2 + { + args: []string{ + "oauth-test", + "--id", "1", + "--name", "oauth2 (via openidConnect) source full", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Name: "oauth2 (via openidConnect) source full", + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + }, + }, + }, + // case 3 + { + args: []string{ + "oauth-test", + "--id", "1", + "--provider", "openidConnect", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + Provider: "openidConnect", + CustomURLMapping: &oauth2.CustomURLMapping{}, + }, + }, + }, + // case 4 + { + args: []string{ + "oauth-test", + "--id", "1", + "--key", "client id", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + ClientID: "client id", + CustomURLMapping: &oauth2.CustomURLMapping{}, + }, + }, + }, + // case 5 + { + args: []string{ + "oauth-test", + "--id", "1", + "--secret", "client secret", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + ClientSecret: "client secret", + CustomURLMapping: &oauth2.CustomURLMapping{}, + }, + }, + }, + // case 6 + { + args: []string{ + "oauth-test", + "--id", "1", + "--auto-discover-url", "https://example.com/.well-known/openid-configuration", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + OpenIDConnectAutoDiscoveryURL: "https://example.com/.well-known/openid-configuration", + CustomURLMapping: &oauth2.CustomURLMapping{}, + }, + }, + }, + // case 7 + { + args: []string{ + "oauth-test", + "--id", "1", + "--use-custom-urls", "", + "--custom-tenant-id", "tenant id", + "--custom-auth-url", "https://example.com/auth", + "--custom-token-url", "https://example.com/token", + "--custom-profile-url", "https://example.com/profile", + "--custom-email-url", "https://example.com/email", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{ + AuthURL: "https://example.com/auth", + TokenURL: "https://example.com/token", + ProfileURL: "https://example.com/profile", + EmailURL: "https://example.com/email", + Tenant: "tenant id", + }, + }, + }, + }, + // case 8 + { + args: []string{ + "oauth-test", + "--id", "1", + "--name", "oauth2 (via openidConnect) source `--use-custom-urls` required for `--custom-*` flags", + "--custom-tenant-id", "tenant id", + "--custom-auth-url", "https://example.com/auth", + "--custom-token-url", "https://example.com/token", + "--custom-profile-url", "https://example.com/profile", + "--custom-email-url", "https://example.com/email", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Name: "oauth2 (via openidConnect) source `--use-custom-urls` required for `--custom-*` flags", + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + }, + }, + }, + // case 9 + { + args: []string{ + "oauth-test", + "--id", "1", + "--icon-url", "https://example.com/icon.svg", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + IconURL: "https://example.com/icon.svg", + }, + }, + }, + // case 10 + { + args: []string{ + "oauth-test", + "--id", "1", + "--name", "oauth2 (via openidConnect) source `--skip-local-2fa` is currently ignored", + "--skip-local-2fa", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Name: "oauth2 (via openidConnect) source `--skip-local-2fa` is currently ignored", + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + // `--skip-local-2fa` is currently ignored. + // SkipLocalTwoFA: true, + }, + }, + }, + // case 11 + { + args: []string{ + "oauth-test", + "--id", "1", + "--name", "oauth2 (via openidConnect) source `--scopes` aggregates multiple uses", + "--scopes", "address", + "--scopes", "email", + "--scopes", "phone", + "--scopes", "profile", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Name: "oauth2 (via openidConnect) source `--scopes` aggregates multiple uses", + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + Scopes: []string{"address", "email", "phone", "profile"}, + }, + }, + }, + // case 12 + { + args: []string{ + "oauth-test", + "--id", "1", + "--name", "oauth2 (via openidConnect) source `--scopes` supports commas as separators", + "--scopes", "address,email,phone,profile", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Name: "oauth2 (via openidConnect) source `--scopes` supports commas as separators", + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + Scopes: []string{"address", "email", "phone", "profile"}, + }, + }, + }, + // case 13 + { + args: []string{ + "oauth-test", + "--id", "1", + "--attribute-ssh-public-key", "ssh_public_key", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + AttributeSSHPublicKey: "ssh_public_key", + }, + }, + }, + // case 14 + { + args: []string{ + "oauth-test", + "--id", "1", + "--required-claim-name", "can_access", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + RequiredClaimName: "can_access", + }, + }, + }, + // case 15 + { + args: []string{ + "oauth-test", + "--id", "1", + "--required-claim-value", "yes", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + RequiredClaimValue: "yes", + }, + }, + }, + // case 16 + { + args: []string{ + "oauth-test", + "--id", "1", + "--group-claim-name", "groups", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + GroupClaimName: "groups", + }, + }, + }, + // case 17 + { + args: []string{ + "oauth-test", + "--id", "1", + "--admin-group", "admin", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + AdminGroup: "admin", + }, + }, + }, + // case 18 + { + args: []string{ + "oauth-test", + "--id", "1", + "--restricted-group", "restricted", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + RestrictedGroup: "restricted", + }, + }, + }, + // case 19 + { + args: []string{ + "oauth-test", + "--id", "1", + "--group-team-map", `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`, + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + GroupTeamMap: `{"org_a_team_1": {"organization-a": ["Team 1"]}, "org_a_all_teams": {"organization-a": ["Team 1", "Team 2", "Team 3"]}}`, + }, + }, + }, + // case 20 + { + args: []string{ + "oauth-test", + "--id", "1", + "--group-team-map-removal", + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + GroupTeamMapRemoval: true, + }, + }, + }, + // case 21 + { + args: []string{ + "oauth-test", + "--id", "23", + "--group-team-map-removal=false", + }, + id: 23, + existingAuthSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + GroupTeamMapRemoval: true, + }, + }, + authSource: &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + GroupTeamMapRemoval: false, + }, + }, + }, + // case 22 + { + args: []string{ + "oauth-test", + }, + errMsg: "--id flag is missing", + }, + } + + for n, c := range cases { + // Mock functions. + var updatedAuthSource *auth.Source + service := &authService{ + initDB: func(context.Context) error { + return nil + }, + createAuthSource: func(ctx context.Context, authSource *auth.Source) error { + assert.FailNow(t, "should not call createAuthSource", "case: %d", n) + return nil + }, + updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { + updatedAuthSource = authSource + return nil + }, + getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { + if c.id != 0 { + assert.Equal(t, c.id, id, "case %d: wrong id", n) + } + if c.existingAuthSource != nil { + return c.existingAuthSource, nil + } + return &auth.Source{ + Type: auth.OAuth2, + Cfg: &oauth2.Source{}, + }, nil + }, + } + + // Create a copy of command to test + app := cli.Command{} + app.Flags = microcmdAuthUpdateOauth().Flags + app.Action = service.updateOauth + + // Run it + err := app.Run(t.Context(), c.args) + if c.errMsg != "" { + assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) + } else { + require.NoError(t, err, "case %d: should have no errors", n) + assert.Equal(t, c.authSource, updatedAuthSource, "case %d: wrong authSource", n) + } + } +} diff --git a/cmd/dump.go b/cmd/dump.go index cb01e74196..ea141291f5 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -8,11 +8,12 @@ import ( "context" "errors" "fmt" - "io" + "io/fs" "os" "path" "path/filepath" "strings" + "sync" "time" "forgejo.org/models/db" @@ -23,36 +24,43 @@ import ( "forgejo.org/modules/util" "code.forgejo.org/go-chi/session" - "github.com/mholt/archiver/v3" + "github.com/mholt/archives" "github.com/urfave/cli/v3" ) -func addReader(w archiver.Writer, r io.ReadCloser, info os.FileInfo, customName string, verbose bool) error { +func addObject(archiveJobs chan archives.ArchiveAsyncJob, object fs.File, customName string, verbose bool) error { if verbose { log.Info("Adding file %s", customName) } - return w.Write(archiver.File{ - FileInfo: archiver.FileInfo{ - FileInfo: info, - CustomName: customName, + info, err := object.Stat() + if err != nil { + return err + } + + ch := make(chan error) + + archiveJobs <- archives.ArchiveAsyncJob{ + File: archives.FileInfo{ + FileInfo: info, + NameInArchive: customName, + Open: func() (fs.File, error) { + return object, nil + }, }, - ReadCloser: r, - }) + Result: ch, + } + + return <-ch } -func addFile(w archiver.Writer, filePath, absPath string, verbose bool) error { - file, err := os.Open(absPath) - if err != nil { - return err - } - defer file.Close() - fileInfo, err := file.Stat() +func addFile(archiveJobs chan archives.ArchiveAsyncJob, filePath, absPath string, verbose bool) error { + file, err := os.Open(absPath) // Closed by archiver if err != nil { return err } - return addReader(w, file, fileInfo, filePath, verbose) + return addObject(archiveJobs, file, filePath, verbose) } func isSubdir(upper, lower string) (bool, error) { @@ -101,6 +109,54 @@ var outputTypeEnum = &outputType{ Default: "zip", } +func getArchiverByType(outType string) (archives.ArchiverAsync, error) { + var archiver archives.ArchiverAsync + switch outType { + case "zip": + archiver = archives.Zip{} + case "tar": + archiver = archives.Tar{} + case "tar.sz": + archiver = archives.CompressedArchive{ + Archival: archives.Tar{}, + Compression: archives.Sz{}, + } + case "tar.gz": + archiver = archives.CompressedArchive{ + Archival: archives.Tar{}, + Compression: archives.Gz{}, + } + case "tar.xz": + archiver = archives.CompressedArchive{ + Archival: archives.Tar{}, + Compression: archives.Xz{}, + } + case "tar.bz2": + archiver = archives.CompressedArchive{ + Archival: archives.Tar{}, + Compression: archives.Bz2{}, + } + case "tar.br": + archiver = archives.CompressedArchive{ + Archival: archives.Tar{}, + Compression: archives.Brotli{}, + } + case "tar.lz4": + archiver = archives.CompressedArchive{ + Archival: archives.Tar{}, + Compression: archives.Lz4{}, + } + case "tar.zst": + archiver = archives.CompressedArchive{ + Archival: archives.Tar{}, + Compression: archives.Zstd{}, + } + default: + return nil, fmt.Errorf("unsupported output type: %s", outType) + } + return archiver, nil +} + // CmdDump represents the available dump sub-command. func cmdDump() *cli.Command { return &cli.Command{ @@ -254,46 +310,185 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error { return err } - var iface any - if fileName == "-" { - iface, err = archiver.ByExtension(fmt.Sprintf(".%s", outType)) - } else { - iface, err = archiver.ByExtension(fileName) - } + archiveJobs := make(chan archives.ArchiveAsyncJob) + wg := sync.WaitGroup{} + archiver, err := getArchiverByType(outType) if err != nil { fatal("Failed to get archiver for extension: %v", err) } - w, _ := iface.(archiver.Writer) - if err := w.Create(file); err != nil { - fatal("Creating archiver.Writer failed: %v", err) - } - defer w.Close() - if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") { log.Info("Skipping local repositories") } else { log.Info("Dumping local repositories... %s", setting.RepoRootPath) - if err := addRecursiveExclude(w, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil { - fatal("Failed to include repositories: %v", err) - } + wg.Add(1) + go dumpRepos(ctx, archiveJobs, &wg, absFileName, verbose) + } - if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") { - log.Info("Skipping LFS data") - } else if !setting.LFS.StartServer { - log.Info("LFS not enabled - skipping") - } else if err := storage.LFS.IterateObjects("", func(objPath string, object storage.Object) error { - info, err := object.Stat() - if err != nil { - return err + wg.Add(1) + go dumpDatabase(ctx, archiveJobs, &wg, verbose) + + if len(setting.CustomConf) > 0 { + wg.Add(1) + go func() { + defer wg.Done() + log.Info("Adding custom configuration file from %s", setting.CustomConf) + if err := addFile(archiveJobs, "app.ini", setting.CustomConf, verbose); err != nil { + fatal("Failed to include specified app.ini: %v", err) } + }() + } - return addReader(w, object, info, path.Join("data", "lfs", objPath), verbose) - }); err != nil { - fatal("Failed to dump LFS objects: %v", err) + if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") { + log.Info("Skipping custom directory") + } else { + wg.Add(1) + go dumpCustom(archiveJobs, &wg, absFileName, verbose) + } + + isExist, err := util.IsExist(setting.AppDataPath) + if err != nil { + log.Error("Failed to check if %s exists: %v", setting.AppDataPath, err) + } + if isExist { + log.Info("Packing data directory...%s", setting.AppDataPath) + + wg.Add(1) + go dumpData(ctx, archiveJobs, &wg, absFileName, verbose) + } + + if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") { + log.Info("Skipping attachment data") + } else { + wg.Add(1) + go func() { + defer wg.Done() + if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error { + return addObject(archiveJobs, object, path.Join("data", "attachments", objPath), verbose) + }); err != nil { + fatal("Failed to dump attachments: %v", err) + } + }() + } + + if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") { + log.Info("Skipping package data") + } else if !setting.Packages.Enabled { + log.Info("Package registry not enabled - skipping") + } else { + wg.Add(1) + go func() { + defer wg.Done() + if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error { + return addObject(archiveJobs, object, path.Join("data", "packages", objPath), verbose) + }); err != nil { + fatal("Failed to dump packages: %v", err) + } + }() + } + + // Doesn't check if LogRootPath exists before processing --skip-log intentionally, + // ensuring that it's clear the dump is skipped whether the directory's initialized + // yet or not. + if ctx.IsSet("skip-log") && ctx.Bool("skip-log") { + log.Info("Skipping log files") + } else { + isExist, err := util.IsExist(setting.Log.RootPath) + if err != nil { + log.Error("Failed to check if %s exists: %v", setting.Log.RootPath, err) + } + if isExist { + wg.Add(1) + go func() { + defer wg.Done() + if err := addRecursiveExclude(archiveJobs, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil { + fatal("Failed to include log: %v", err) + } + }() } } + // Wait for all jobs to finish before closing the channel + // ArchiveAsync will only return after the channel is closed + go func() { + wg.Wait() + close(archiveJobs) + }() + + if err := archiver.ArchiveAsync(stdCtx, file, archiveJobs); err != nil { + _ = util.Remove(fileName) + + fatal("Archiving failed: %v", err) + } + + if fileName != "-" { + if err := os.Chmod(fileName, 0o600); err != nil { + log.Info("Can't change file access permissions mask to 0600: %v", err) + } + + log.Info("Finished dumping in file %s", fileName) + } else { + log.Info("Finished dumping to stdout") + } + + return nil +} + +func dumpData(ctx *cli.Command, archiveJobs chan archives.ArchiveAsyncJob, wg *sync.WaitGroup, absFileName string, verbose bool) { + defer wg.Done() + + var excludes []string + if setting.SessionConfig.OriginalProvider == "file" { + var opts session.Options + if err := json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil { + fatal("Failed to parse session config: %v", err) + } + excludes = append(excludes, opts.ProviderConfig) + } + + if ctx.IsSet("skip-index") && ctx.Bool("skip-index") { + log.Info("Skipping bleve index data") + excludes = append(excludes, setting.Indexer.RepoPath) + excludes = append(excludes, setting.Indexer.IssuePath) + } + + if ctx.IsSet("skip-repo-archives") && ctx.Bool("skip-repo-archives") { + log.Info("Skipping repository archives data") + excludes = append(excludes, setting.RepoArchive.Storage.Path) + } + + excludes = append(excludes, setting.RepoRootPath) + excludes = append(excludes, setting.LFS.Storage.Path) + excludes = append(excludes, setting.Attachment.Storage.Path) + excludes = append(excludes, setting.Packages.Storage.Path) + excludes = append(excludes, setting.Log.RootPath) + excludes = append(excludes, absFileName) + if err := addRecursiveExclude(archiveJobs, "data", setting.AppDataPath, excludes, verbose); err != nil { + fatal("Failed to include data directory: %v", err) + } +} + +func dumpCustom(archiveJobs chan archives.ArchiveAsyncJob, wg *sync.WaitGroup, absFileName string, verbose bool) { + defer wg.Done() + + customDir, err := os.Stat(setting.CustomPath) + if err == nil && customDir.IsDir() { + if is, _ := isSubdir(setting.AppDataPath, setting.CustomPath); !is { + if err := addRecursiveExclude(archiveJobs, "custom", setting.CustomPath, []string{absFileName}, verbose); err != nil { + fatal("Failed to include custom: %v", err) + } + } else { + log.Info("Custom dir %s is inside data dir %s, skipping", setting.CustomPath, setting.AppDataPath) + } + } else { + log.Info("Custom dir %s does not exist, skipping", setting.CustomPath) + } +} + +func dumpDatabase(ctx *cli.Command, archiveJobs chan archives.ArchiveAsyncJob, wg *sync.WaitGroup, verbose bool) { + defer wg.Done() + + var err error tmpDir := ctx.String("tempdir") if tmpDir == "" { tmpDir, err = os.MkdirTemp("", "forgejo-dump-*") @@ -334,139 +529,32 @@ func runDump(stdCtx context.Context, ctx *cli.Command) error { fatal("Failed to dump database: %v", err) } - if err := addFile(w, "forgejo-db.sql", dbDump.Name(), verbose); err != nil { + if err := addFile(archiveJobs, "forgejo-db.sql", dbDump.Name(), verbose); err != nil { fatal("Failed to include forgejo-db.sql: %v", err) } +} - if len(setting.CustomConf) > 0 { - log.Info("Adding custom configuration file from %s", setting.CustomConf) - if err := addFile(w, "app.ini", setting.CustomConf, verbose); err != nil { - fatal("Failed to include specified app.ini: %v", err) - } +func dumpRepos(ctx *cli.Command, archiveJobs chan archives.ArchiveAsyncJob, wg *sync.WaitGroup, absFileName string, verbose bool) { + defer wg.Done() + + if err := addRecursiveExclude(archiveJobs, "repos", setting.RepoRootPath, []string{absFileName}, verbose); err != nil { + fatal("Failed to include repositories: %v", err) } - if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") { - log.Info("Skipping custom directory") - } else { - customDir, err := os.Stat(setting.CustomPath) - if err == nil && customDir.IsDir() { - if is, _ := isSubdir(setting.AppDataPath, setting.CustomPath); !is { - if err := addRecursiveExclude(w, "custom", setting.CustomPath, []string{absFileName}, verbose); err != nil { - fatal("Failed to include custom: %v", err) - } - } else { - log.Info("Custom dir %s is inside data dir %s, skipping", setting.CustomPath, setting.AppDataPath) - } - } else { - log.Info("Custom dir %s does not exist, skipping", setting.CustomPath) - } - } - - isExist, err := util.IsExist(setting.AppDataPath) - if err != nil { - log.Error("Failed to check if %s exists: %v", setting.AppDataPath, err) - } - if isExist { - log.Info("Packing data directory...%s", setting.AppDataPath) - - var excludes []string - if setting.SessionConfig.OriginalProvider == "file" { - var opts session.Options - if err = json.Unmarshal([]byte(setting.SessionConfig.ProviderConfig), &opts); err != nil { - return err - } - excludes = append(excludes, opts.ProviderConfig) - } - - if ctx.IsSet("skip-index") && ctx.Bool("skip-index") { - log.Info("Skipping bleve index data") - excludes = append(excludes, setting.Indexer.RepoPath) - excludes = append(excludes, setting.Indexer.IssuePath) - } - - if ctx.IsSet("skip-repo-archives") && ctx.Bool("skip-repo-archives") { - log.Info("Skipping repository archives data") - excludes = append(excludes, setting.RepoArchive.Storage.Path) - } - - excludes = append(excludes, setting.RepoRootPath) - excludes = append(excludes, setting.LFS.Storage.Path) - excludes = append(excludes, setting.Attachment.Storage.Path) - excludes = append(excludes, setting.Packages.Storage.Path) - excludes = append(excludes, setting.Log.RootPath) - excludes = append(excludes, absFileName) - if err := addRecursiveExclude(w, "data", setting.AppDataPath, excludes, verbose); err != nil { - fatal("Failed to include data directory: %v", err) - } - } - - if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") { - log.Info("Skipping attachment data") - } else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error { - info, err := object.Stat() - if err != nil { - return err - } - - return addReader(w, object, info, path.Join("data", "attachments", objPath), verbose) + if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") { + log.Info("Skipping LFS data") + } else if !setting.LFS.StartServer { + log.Info("LFS not enabled - skipping") + } else if err := storage.LFS.IterateObjects("", func(objPath string, object storage.Object) error { + return addObject(archiveJobs, object, path.Join("data", "lfs", objPath), verbose) }); err != nil { - fatal("Failed to dump attachments: %v", err) + fatal("Failed to dump LFS objects: %v", err) } - - if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") { - log.Info("Skipping package data") - } else if !setting.Packages.Enabled { - log.Info("Package registry not enabled - skipping") - } else if err := storage.Packages.IterateObjects("", func(objPath string, object storage.Object) error { - info, err := object.Stat() - if err != nil { - return err - } - - return addReader(w, object, info, path.Join("data", "packages", objPath), verbose) - }); err != nil { - fatal("Failed to dump packages: %v", err) - } - - // Doesn't check if LogRootPath exists before processing --skip-log intentionally, - // ensuring that it's clear the dump is skipped whether the directory's initialized - // yet or not. - if ctx.IsSet("skip-log") && ctx.Bool("skip-log") { - log.Info("Skipping log files") - } else { - isExist, err := util.IsExist(setting.Log.RootPath) - if err != nil { - log.Error("Failed to check if %s exists: %v", setting.Log.RootPath, err) - } - if isExist { - if err := addRecursiveExclude(w, "log", setting.Log.RootPath, []string{absFileName}, verbose); err != nil { - fatal("Failed to include log: %v", err) - } - } - } - - if fileName != "-" { - if err = w.Close(); err != nil { - _ = util.Remove(fileName) - fatal("Failed to save %s: %v", fileName, err) - } - - if err := os.Chmod(fileName, 0o600); err != nil { - log.Info("Can't change file access permissions mask to 0600: %v", err) - } - } - - if fileName != "-" { - log.Info("Finish dumping in file %s", fileName) - } else { - log.Info("Finish dumping to stdout") - } - - return nil } // addRecursiveExclude zips absPath to specified insidePath inside writer excluding excludeAbsPath -func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeAbsPath []string, verbose bool) error { +// archives.FilesFromDisk doesn't support excluding files, so we have to do it manually +func addRecursiveExclude(archiveJobs chan archives.ArchiveAsyncJob, insidePath, absPath string, excludeAbsPath []string, verbose bool) error { absPath, err := filepath.Abs(absPath) if err != nil { return err @@ -491,10 +579,11 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA } if file.IsDir() { - if err := addFile(w, currentInsidePath, currentAbsPath, false); err != nil { + if err := addFile(archiveJobs, currentInsidePath, currentAbsPath, false); err != nil { return err } - if err = addRecursiveExclude(w, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil { + + if err := addRecursiveExclude(archiveJobs, currentInsidePath, currentAbsPath, excludeAbsPath, verbose); err != nil { return err } } else { @@ -512,7 +601,7 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA shouldAdd = targetStat.Mode().IsRegular() } if shouldAdd { - if err = addFile(w, currentInsidePath, currentAbsPath, verbose); err != nil { + if err := addFile(archiveJobs, currentInsidePath, currentAbsPath, verbose); err != nil { return err } } diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go index eb89273e7f..7159d55e99 100644 --- a/cmd/dump_repo.go +++ b/cmd/dump_repo.go @@ -82,6 +82,11 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme } func runDumpRepository(stdCtx context.Context, ctx *cli.Command) error { + setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr) + + // setting.DisableLoggerInit() + setting.LoadSettings() // cannot access skip_tls_verify settings otherwise + stdCtx, cancel := installSignals(stdCtx) defer cancel() diff --git a/cmd/dump_test.go b/cmd/dump_test.go index 459386318f..3bdd9d68f8 100644 --- a/cmd/dump_test.go +++ b/cmd/dump_test.go @@ -4,40 +4,32 @@ package cmd import ( - "io" "os" "testing" - "github.com/mholt/archiver/v3" + "github.com/mholt/archives" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -type mockArchiver struct { - addedFiles []string -} - -func (mockArchiver) Create(out io.Writer) error { - return nil -} - -func (m *mockArchiver) Write(f archiver.File) error { - m.addedFiles = append(m.addedFiles, f.Name()) - return nil -} - -func (mockArchiver) Close() error { - return nil +func mockArchiverAsync(ch chan archives.ArchiveAsyncJob, files *[]string) { + for job := range ch { + *files = append(*files, job.File.NameInArchive) + job.Result <- nil + } } func TestAddRecursiveExclude(t *testing.T) { t.Run("Empty", func(t *testing.T) { - dir := t.TempDir() - archiver := &mockArchiver{} + ch := make(chan archives.ArchiveAsyncJob) + var files []string + go mockArchiverAsync(ch, &files) - err := addRecursiveExclude(archiver, "", dir, []string{}, false) + dir := t.TempDir() + + err := addRecursiveExclude(ch, "", dir, []string{}, false) require.NoError(t, err) - assert.Empty(t, archiver.addedFiles) + assert.Empty(t, files) }) t.Run("Single file", func(t *testing.T) { @@ -46,20 +38,25 @@ func TestAddRecursiveExclude(t *testing.T) { require.NoError(t, err) t.Run("No exclude", func(t *testing.T) { - archiver := &mockArchiver{} + ch := make(chan archives.ArchiveAsyncJob) + var files []string + go mockArchiverAsync(ch, &files) - err = addRecursiveExclude(archiver, "", dir, nil, false) + err := addRecursiveExclude(ch, "", dir, nil, false) require.NoError(t, err) - assert.Len(t, archiver.addedFiles, 1) - assert.Contains(t, archiver.addedFiles, "example") + + assert.Len(t, files, 1) + assert.Contains(t, files, "example") }) t.Run("With exclude", func(t *testing.T) { - archiver := &mockArchiver{} + ch := make(chan archives.ArchiveAsyncJob) + var files []string + go mockArchiverAsync(ch, &files) - err = addRecursiveExclude(archiver, "", dir, []string{dir + "/example"}, false) + err := addRecursiveExclude(ch, "", dir, []string{dir + "/example"}, false) require.NoError(t, err) - assert.Empty(t, archiver.addedFiles) + assert.Empty(t, files) }) }) @@ -73,46 +70,57 @@ func TestAddRecursiveExclude(t *testing.T) { require.NoError(t, err) t.Run("No exclude", func(t *testing.T) { - archiver := &mockArchiver{} + ch := make(chan archives.ArchiveAsyncJob) + var files []string + go mockArchiverAsync(ch, &files) - err = addRecursiveExclude(archiver, "", dir, nil, false) + err := addRecursiveExclude(ch, "", dir, nil, false) require.NoError(t, err) - assert.Len(t, archiver.addedFiles, 5) - assert.Contains(t, archiver.addedFiles, "deep") - assert.Contains(t, archiver.addedFiles, "deep/nested") - assert.Contains(t, archiver.addedFiles, "deep/nested/folder") - assert.Contains(t, archiver.addedFiles, "deep/nested/folder/example") - assert.Contains(t, archiver.addedFiles, "deep/nested/folder/another-file") + assert.Len(t, files, 5) + + assert.Contains(t, files, "deep") + assert.Contains(t, files, "deep/nested") + assert.Contains(t, files, "deep/nested/folder") + assert.Contains(t, files, "deep/nested/folder/example") + assert.Contains(t, files, "deep/nested/folder/another-file") }) t.Run("Exclude first directory", func(t *testing.T) { - archiver := &mockArchiver{} + ch := make(chan archives.ArchiveAsyncJob) + var files []string + go mockArchiverAsync(ch, &files) - err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep"}, false) + err := addRecursiveExclude(ch, "", dir, []string{dir + "/deep"}, false) require.NoError(t, err) - assert.Empty(t, archiver.addedFiles) + assert.Empty(t, files) }) t.Run("Exclude nested directory", func(t *testing.T) { - archiver := &mockArchiver{} + ch := make(chan archives.ArchiveAsyncJob) + var files []string + go mockArchiverAsync(ch, &files) - err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep/nested/folder"}, false) + err := addRecursiveExclude(ch, "", dir, []string{dir + "/deep/nested/folder"}, false) require.NoError(t, err) - assert.Len(t, archiver.addedFiles, 2) - assert.Contains(t, archiver.addedFiles, "deep") - assert.Contains(t, archiver.addedFiles, "deep/nested") + assert.Len(t, files, 2) + + assert.Contains(t, files, "deep") + assert.Contains(t, files, "deep/nested") }) t.Run("Exclude file", func(t *testing.T) { - archiver := &mockArchiver{} + ch := make(chan archives.ArchiveAsyncJob) + var files []string + go mockArchiverAsync(ch, &files) - err = addRecursiveExclude(archiver, "", dir, []string{dir + "/deep/nested/folder/example"}, false) + err := addRecursiveExclude(ch, "", dir, []string{dir + "/deep/nested/folder/example"}, false) require.NoError(t, err) - assert.Len(t, archiver.addedFiles, 4) - assert.Contains(t, archiver.addedFiles, "deep") - assert.Contains(t, archiver.addedFiles, "deep/nested") - assert.Contains(t, archiver.addedFiles, "deep/nested/folder") - assert.Contains(t, archiver.addedFiles, "deep/nested/folder/another-file") + assert.Len(t, files, 4) + + assert.Contains(t, files, "deep") + assert.Contains(t, files, "deep/nested") + assert.Contains(t, files, "deep/nested/folder") + assert.Contains(t, files, "deep/nested/folder/another-file") }) }) } diff --git a/cmd/hook.go b/cmd/hook.go index 909cdfdf84..7378dc21ad 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -231,8 +231,6 @@ Forgejo or set your environment appropriately.`, "") } } - supportProcReceive := git.CheckGitVersionAtLeast("2.29") == nil - for scanner.Scan() { // TODO: support news feeds for wiki if isWiki { @@ -250,31 +248,25 @@ Forgejo or set your environment appropriately.`, "") total++ lastline++ - // If the ref is a branch or tag, check if it's protected - // if supportProcReceive all ref should be checked because - // permission check was delayed - if supportProcReceive || refFullName.IsBranch() || refFullName.IsTag() { - oldCommitIDs[count] = oldCommitID - newCommitIDs[count] = newCommitID - refFullNames[count] = refFullName - count++ - fmt.Fprint(out, "*") + // All references should be checked because permission check was delayed. + oldCommitIDs[count] = oldCommitID + newCommitIDs[count] = newCommitID + refFullNames[count] = refFullName + count++ + fmt.Fprint(out, "*") - if count >= hookBatchSize { - fmt.Fprintf(out, " Checking %d references\n", count) + if count >= hookBatchSize { + fmt.Fprintf(out, " Checking %d references\n", count) - hookOptions.OldCommitIDs = oldCommitIDs - hookOptions.NewCommitIDs = newCommitIDs - hookOptions.RefFullNames = refFullNames - extra := private.HookPreReceive(ctx, username, reponame, hookOptions) - if extra.HasError() { - return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error) - } - count = 0 - lastline = 0 + hookOptions.OldCommitIDs = oldCommitIDs + hookOptions.NewCommitIDs = newCommitIDs + hookOptions.RefFullNames = refFullNames + extra := private.HookPreReceive(ctx, username, reponame, hookOptions) + if extra.HasError() { + return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error) } - } else { - fmt.Fprint(out, ".") + count = 0 + lastline = 0 } if lastline >= hookBatchSize { fmt.Fprint(out, "\n") @@ -513,10 +505,6 @@ Forgejo or set your environment appropriately.`, "") return nil } - if git.CheckGitVersionAtLeast("2.29") != nil { - return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.") - } - reader := bufio.NewReader(os.Stdin) repoUser := os.Getenv(repo_module.EnvRepoUsername) repoName := os.Getenv(repo_module.EnvRepoName) diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go index c543afe872..c18bfa919b 100644 --- a/cmd/manager_logging.go +++ b/cmd/manager_logging.go @@ -44,6 +44,11 @@ func defaultLoggingFlags() []cli.Flag { Aliases: []string{"e"}, Usage: "Matching expression for the logger", }, + &cli.StringFlag{ + Name: "exclusion", + Aliases: []string{"x"}, + Usage: "Exclusion for the logger", + }, &cli.StringFlag{ Name: "prefix", Aliases: []string{"p"}, @@ -286,6 +291,9 @@ func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[ if len(c.String("expression")) > 0 { vals["expression"] = c.String("expression") } + if len(c.String("exclusion")) > 0 { + vals["exclusion"] = c.String("exclusion") + } if len(c.String("prefix")) > 0 { vals["prefix"] = c.String("prefix") } diff --git a/cmd/serv.go b/cmd/serv.go index 1fac2d13f5..0e0551d297 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -88,6 +88,14 @@ var ( alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`) ) +func sshLog(ctx context.Context, level log.Level, message string) error { + if testing.Testing() || setting.InternalToken == "" { + return nil + } + + return private.SSHLog(ctx, level, message) +} + // fail prints message to stdout, it's mainly used for git serv and git hook commands. // The output will be passed to git client and shown to user. func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error { @@ -112,10 +120,7 @@ func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error logMsg = userMessage + ". " + logMsg } } - // Don't send an log if this is done in a test and no InternalToken is set. - if !testing.Testing() || setting.InternalToken != "" { - _ = private.SSHLog(ctx, true, logMsg) - } + _ = sshLog(ctx, log.ERROR, logMsg) } return cli.Exit("", 1) } @@ -193,12 +198,10 @@ func runServ(ctx context.Context, c *cli.Command) error { } if len(words) < 2 { - if git.CheckGitVersionAtLeast("2.29") == nil { - // for AGit Flow - if cmd == "ssh_info" { - fmt.Print(`{"type":"agit","version":1}`) - return nil - } + // for AGit Flow + if cmd == "ssh_info" { + fmt.Print(`{"type":"agit","version":1}`) + return nil } return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd) } diff --git a/cmd/web_acme.go b/cmd/web_acme.go index 03b3b9f0da..be6314addb 100644 --- a/cmd/web_acme.go +++ b/cmd/web_acme.go @@ -15,6 +15,7 @@ import ( "forgejo.org/modules/graceful" "forgejo.org/modules/log" "forgejo.org/modules/process" + "forgejo.org/modules/proxy" "forgejo.org/modules/setting" "github.com/caddyserver/certmagic" @@ -76,6 +77,12 @@ func runACME(listenAddr string, m http.Handler) error { ListenHost: setting.HTTPAddr, AltTLSALPNPort: altTLSALPNPort, AltHTTPPort: altHTTPPort, + HTTPProxy: proxy.Proxy(), + } + + // Preserve behavior to use Let's encrypt test CA when Let's encrypt is CA. + if certmagic.DefaultACME.CA == certmagic.LetsEncryptProductionCA { + certmagic.DefaultACME.TestCA = certmagic.LetsEncryptStagingCA } magic := certmagic.NewDefault() diff --git a/contrib/coverage-helper.sh b/contrib/coverage-helper.sh new file mode 100755 index 0000000000..ec9f5469ea --- /dev/null +++ b/contrib/coverage-helper.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +set -e +#set -x +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/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 +excluded+='FAKETERMINATOR' # do not modify + +: ${COVERAGEDIR:=$(pwd)/coverage/data} +: ${GO:=$(go env GOROOT)/bin/go} + +DEFAULT_TEST_PACKAGES=$($GO list ./... | grep -E -v "$excluded") + +COVERED_PACKAGES=$($GO list ./...) +COVERED_PACKAGES=$(echo $COVERED_PACKAGES | sed -e 's/ /,/g') + +function run_test() { + local package="$1" + if echo "$package" | grep --quiet --fixed-string ".."; then + echo "$package contains a suspicious .." + return 1 + fi + + local coverage="$COVERAGEDIR/$COVERAGE_TEST_DATABASE/$package" + rm -fr $coverage + mkdir -p $coverage + + # + # -race cannot be used because it requires -covermode atomic which is + # different from the end-to-end tests and would cause issues wen merging + # + $GO test -timeout=20m -tags='sqlite sqlite_unlock_notify' -cover $package -coverpkg $COVERED_PACKAGES $COVERAGE_TEST_ARGS -args -test.gocoverdir=$coverage |& grep -v 'warning: no packages being tested depend on matches for pattern' +} + +function test_packages() { + for package in ${@:-$DEFAULT_TEST_PACKAGES}; do + run_test $package + done +} + +"$@" diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 1b8d4c6697..6c8c85b87c 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -449,6 +449,9 @@ INTERNAL_TOKEN = ;; How long to remember that a user is logged in before requiring relogin (in days) ;LOGIN_REMEMBER_DAYS = 31 ;; +;; Require 2FA globally for none|all|admin. +;GLOBAL_TWO_FACTOR_REQUIREMENT = none +;; ;; Name of cookie used to store authentication information. ;COOKIE_REMEMBER_NAME = gitea_incredible ;; @@ -592,13 +595,10 @@ LEVEL = Info ;BUFFER_LEN = 10000 ;; ;; Sub logger modes, a single comma means use default MODE above, empty means disable it -;logger.access.MODE= -;logger.router.MODE=, -;logger.xorm.MODE=, -;; -;; Collect SSH logs (Creates log from ssh git request) -;; -;ENABLE_SSH_LOG = false +;LOGGER_ACCESS_MODE= +;LOGGER_ROUTER_MODE=, +;LOGGER_XORM_MODE=, +;LOGGER_SSH_MODE= ;; SSH logs from ssh git request ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; @@ -631,6 +631,7 @@ LEVEL = Info ;LEVEL= ;FLAGS = stdflags or journald ;EXPRESSION = +;EXCLUSION = ;PREFIX = ;COLORIZE = false ;; @@ -1585,6 +1586,11 @@ LEVEL = Info ;; If enabled it will be possible for users to report abusive content (new actions are added in the UI and /report_abuse route will be enabled) and a new Moderation section will be added to Admin settings where the reports can be reviewed. ;ENABLED = false +;; How long to keep resolved abuse reports for. +;; Applies to reports that have been marked as ignored or handled +;; Can be 1 hour, 7 days etc +;KEEP_RESOLVED_REPORTS_FOR = 0 + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[openid] @@ -1767,6 +1773,9 @@ LEVEL = Info ;; Use PASSWD = `your password` for quoting if you use special characters in the password. ;PASSWD = ;; +;; Alternative location to specify mailer password. You cannot specify both this and PASSWD, and must pick one +;PASSWD_URI = file:/etc/forgejo/mailer_passwd +;; ;; Send mails only in plain text, without HTML alternative ;SEND_AS_PLAIN_TEXT = false ;; @@ -1819,6 +1828,9 @@ LEVEL = Info ;; Password of the receiving account ;PASSWORD = ;; +;; Alternative location to specify password of the receiving account. You cannot specify both this and PASSWORD, and must pick one +;PASSWORD_URI = file:/etc/forgejo/email_incoming_password +;; ;; Whether the IMAP server uses TLS. ;USE_TLS = false ;; diff --git a/eslint.config.mjs b/eslint.config.mjs index 28cfa80089..83a6b6bee1 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -153,7 +153,7 @@ export default tseslint.config( '@stylistic/quotes': [2, 'single', { avoidEscape: true, - allowTemplateLiterals: true, + allowTemplateLiterals: 'always', }], '@stylistic/rest-spread-spacing': [2, 'never'], @@ -799,6 +799,7 @@ export default tseslint.config( 'unicorn/prefer-array-some': [2], 'unicorn/prefer-at': [0], 'unicorn/prefer-blob-reading-methods': [2], + 'unicorn/prefer-classlist-toggle': [2], 'unicorn/prefer-code-point': [0], 'unicorn/prefer-date-now': [2], 'unicorn/prefer-default-parameters': [0], diff --git a/go.mod b/go.mod index bb2be827eb..fcb20601e4 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module forgejo.org -go 1.24 +go 1.24.0 -toolchain go1.24.4 +toolchain go1.24.7 require ( code.forgejo.org/f3/gof3/v3 v3.11.0 @@ -10,6 +10,7 @@ require ( 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.0.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 @@ -24,11 +25,11 @@ require ( github.com/ProtonMail/go-crypto v1.3.0 github.com/PuerkitoBio/goquery v1.10.3 github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2 - github.com/alecthomas/chroma/v2 v2.18.0 + github.com/alecthomas/chroma/v2 v2.20.0 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/blevesearch/bleve/v2 v2.5.2 github.com/buildkite/terminal-to-html/v3 v3.16.8 - github.com/caddyserver/certmagic v0.23.0 + github.com/caddyserver/certmagic v0.24.0 github.com/chi-middleware/proxy v1.1.1 github.com/djherbis/buffer v1.2.0 github.com/djherbis/nio/v3 v3.0.1 @@ -40,46 +41,44 @@ require ( github.com/fsnotify/fsnotify v1.9.0 github.com/gliderlabs/ssh v0.3.8 github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 - github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 - github.com/go-chi/chi/v5 v5.2.2 - github.com/go-chi/cors v1.2.1 + github.com/go-ap/jsonld v0.0.0-20250905102310-8480b0fe24d9 + github.com/go-chi/chi/v5 v5.2.3 + github.com/go-chi/cors v1.2.2 github.com/go-co-op/gocron v1.37.0 github.com/go-enry/go-enry/v2 v2.9.2 - github.com/go-git/go-git/v5 v5.13.2 github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-openapi/spec v0.21.0 github.com/go-sql-driver/mysql v1.9.3 - github.com/go-webauthn/webauthn v0.13.0 + github.com/go-webauthn/webauthn v0.13.4 github.com/gobwas/glob v0.2.3 github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 - github.com/golang-jwt/jwt/v5 v5.2.2 + 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-20250317173921-a4b03ec1a45e + github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 github.com/google/uuid v1.6.0 github.com/gorilla/feeds v1.2.0 github.com/gorilla/sessions v1.4.0 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/huandu/xstrings v1.5.0 - github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 - github.com/jhillyerd/enmime/v2 v2.1.0 + github.com/inbucket/html2text v0.9.0 + 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/cpuid/v2 v2.2.10 + 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.28 - github.com/meilisearch/meilisearch-go v0.31.0 - github.com/mholt/archiver/v3 v3.5.1 + github.com/mattn/go-sqlite3 v1.14.32 + github.com/meilisearch/meilisearch-go v0.34.0 + github.com/mholt/archives v0.1.3 github.com/microcosm-cc/bluemonday v1.0.27 - github.com/minio/minio-go/v7 v7.0.94 + github.com/minio/minio-go/v7 v7.0.95 github.com/msteinert/pam/v2 v2.1.0 - github.com/nektos/act v0.2.52 - github.com/niklasfasching/go-org v1.8.0 + github.com/niklasfasching/go-org v1.9.1 github.com/olivere/elastic/v7 v7.0.32 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 @@ -89,27 +88,27 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/sergi/go-diff v1.4.0 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/syndtr/goleveldb v1.0.0 - github.com/ulikunitz/xz v0.5.12 - github.com/urfave/cli/v3 v3.3.3 + github.com/ulikunitz/xz v0.5.15 + github.com/urfave/cli/v3 v3.4.1 github.com/valyala/fastjson v1.6.4 github.com/yohcop/openid-go v1.0.1 - github.com/yuin/goldmark v1.7.12 + github.com/yuin/goldmark v1.7.13 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc gitlab.com/gitlab-org/api/client-go v0.130.1 - go.uber.org/mock v0.5.2 - golang.org/x/crypto v0.39.0 - golang.org/x/image v0.27.0 - golang.org/x/net v0.41.0 - golang.org/x/oauth2 v0.30.0 - golang.org/x/sync v0.15.0 - golang.org/x/sys v0.33.0 - golang.org/x/text v0.26.0 - google.golang.org/protobuf v1.36.4 + 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/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 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/ini.v1 v1.67.0 - gopkg.in/yaml.v3 v3.0.1 mvdan.cc/xurls/v2 v2.5.0 xorm.io/builder v0.3.13 xorm.io/xorm v1.3.9 @@ -117,12 +116,13 @@ require ( require ( cloud.google.com/go/compute/metadata v0.6.0 // indirect - dario.cat/mergo v1.0.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 github.com/Microsoft/go-winio v0.6.2 // indirect github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect - github.com/andybalholm/brotli v1.1.1 // indirect + github.com/STARRY-S/zip v0.2.1 // indirect + github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/aymerick/douceur v0.2.0 // indirect @@ -145,35 +145,40 @@ 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/bodgit/plumbing v1.3.0 // indirect + github.com/bodgit/sevenzip v1.6.0 // indirect + github.com/bodgit/windows v1.0.1 // indirect github.com/boombuler/barcode v1.0.1 // indirect github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/caddyserver/zerossl v0.1.3 // indirect github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect - github.com/cyphar/filepath-securejoin v0.3.6 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 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/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect github.com/emirpasic/gods v1.18.1 // indirect - github.com/fatih/color v1.16.0 // indirect - github.com/fxamacker/cbor/v2 v2.8.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect 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-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-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-webauthn/x v0.1.21 // indirect + github.com/go-webauthn/x v0.1.23 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // 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 github.com/google/btree v1.1.2 // indirect @@ -183,31 +188,38 @@ require ( github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/securecookie v1.1.2 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // 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 github.com/klauspost/pgzip v1.2.6 // indirect - github.com/libdns/libdns v1.0.0-beta.1 // indirect + github.com/libdns/libdns v1.0.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/markbates/going v1.0.3 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-runewidth v0.0.16 // 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 - github.com/minio/crc64nvme v1.0.1 // indirect + github.com/mikelolasagasti/xz v1.0.1 // indirect + github.com/minio/crc64nvme v1.0.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/minlz v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect 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 v1.1.3 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/nwaples/rardecode/v2 v2.1.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 github.com/onsi/ginkgo v1.16.5 // indirect - github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect + github.com/philhofer/fwd v1.2.0 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -215,35 +227,34 @@ 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.6.27 // indirect + github.com/rhysd/actionlint v1.7.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/skeema/knownhosts v1.3.0 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/sorairolake/lzip-go v0.3.5 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/tinylib/msgp v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/zeebo/assert v1.3.0 // indirect github.com/zeebo/blake3 v0.2.4 // indirect - go.etcd.io/bbolt v1.4.0 // indirect + go.etcd.io/bbolt v1.4.3 // indirect go.uber.org/atomic v1.11.0 // indirect 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 - golang.org/x/mod v0.25.0 // indirect - golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.34.0 // 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 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 -replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.28.0 - replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1 replace github.com/gliderlabs/ssh => code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616 diff --git a/go.sum b/go.sum index 639880e2ce..63af64f9ca 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,25 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +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/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU= code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM= -code.forgejo.org/forgejo/act v1.28.0 h1:96njNC7C1YNyjWq5OWvLZMF/nw0PMthzIA8Nwbnn7jo= -code.forgejo.org/forgejo/act v1.28.0/go.mod h1:dFuiwAmD5vyrzecysHB2kL/GM3wRpoVPl+WdbCTC8Bs= -code.forgejo.org/forgejo/archiver/v3 v3.5.1 h1:UmmbA7D5550uf71SQjarmrn6yKwOGxtEjb3jaYYtmSE= -code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= code.forgejo.org/forgejo/go-rpmutils v1.0.0 h1:RZGGeKt70p/WaIEL97pyT6uiiEIoN8/aLmS5Z6WmX0M= code.forgejo.org/forgejo/go-rpmutils v1.0.0/go.mod h1:cg+VbgLXfrDPza9T+kBsMb3TVmmzPN4XseT6gDGLSUk= code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:RArF5AsF9LH4nEoJxqRxcP5r8hhRfWcId84G82YbqzA= @@ -16,6 +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.0.0 h1:PDChRbwPzoflwP05QHn8YyvRDvnU5yVq/X3sdZojXr8= +code.forgejo.org/forgejo/runner/v11 v11.0.0/go.mod h1:ZJokNf9nZItjsfSjC/9zHbUvf5LXYUi+On+1Bg/pO3g= 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= @@ -34,8 +48,9 @@ codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtf 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= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +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= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= @@ -48,6 +63,8 @@ github.com/6543/go-version v1.3.1 h1:HvOp+Telns7HWJ2Xo/05YXQSB2bE0WmVgbHqwMPZT4U github.com/6543/go-version v1.3.1/go.mod h1:oqFAHCwtLVUTLdhQmVZWYvaHXTdsbB4SY85at64SQEo= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= @@ -57,21 +74,22 @@ github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiU github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg= github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0= +github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg= +github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4= github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2 h1:cSXom2MoKJ9KPPw29RoZtHvUETY4F4n/kXl8m9btnQ0= github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2/go.mod h1:JitQWJ8JuV4Y87l8VsHiiwhb3cgdyn68mX40s7NT6PA= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.18.0 h1:6h53Q4hW83SuF+jcsp7CVhLsMozzvQvO8HBbKQW+gn4= -github.com/alecthomas/chroma/v2 v2.18.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk= +github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw= +github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= -github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= -github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= +github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= -github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ= +github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -123,6 +141,14 @@ 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/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= +github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= +github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A= +github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc= +github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= +github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -134,10 +160,11 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/buildkite/terminal-to-html/v3 v3.16.8 h1:QN/daUob6cmK8GcdKnwn9+YTlPr1vNj+oeAIiJK6fPc= github.com/buildkite/terminal-to-html/v3 v3.16.8/go.mod h1:+k1KVKROZocrTLsEQ9PEf9A+8+X8uaVV5iO1ZIOwKYM= -github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= -github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= +github.com/caddyserver/certmagic v0.24.0 h1:EfXTWpxHAUKgDfOj6MHImJN8Jm4AMFfMT6ITuKhrDF0= +github.com/caddyserver/certmagic v0.24.0/go.mod h1:xPT7dC1DuHHnS2yuEQCEyks+b89sUkMENh8dJF+InLE= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI= github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -147,14 +174,18 @@ github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdi github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= -github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -173,7 +204,6 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= 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= @@ -181,8 +211,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp 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= github.com/editorconfig/editorconfig-core-go/v2 v2.6.3/go.mod h1:ThHVc+hqbUsmE1wmK/MASpQEhCleWu1JDJDNhUOMy0c= -github.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM= -github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA= github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY= github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= @@ -192,8 +222,10 @@ github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTe github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -202,21 +234,22 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= -github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 h1:j2TrkUG/NATGi/EQS+MvEoF79CxiRUmT16ErFroNcKI= github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9/go.mod h1:cJ9Ye0ZNSMN7RzZDBRY3E+8M3Bpf/R1JX22Ir9yX6WI= github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 h1:I2nuhyVI/48VXoRCCZR2hYBgnSXa+EuDJf/VyX06TC0= github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7/go.mod h1:5x8a6P/dhmMGFxWLcyYlyOuJ2lRNaHGhRv+yu8BaTSI= -github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 h1:GMKIYXyXPGIp+hYiWOhfqK4A023HdgisDT4YGgf99mw= github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA= +github.com/go-ap/jsonld v0.0.0-20250905102310-8480b0fe24d9 h1:gaBrU/E+usPHIafDIC2EwvZbehvgAEuu78Jk0zjxw5w= +github.com/go-ap/jsonld v0.0.0-20250905102310-8480b0fe24d9/go.mod h1:4h93IBxgfnE/DEleMLgJ/XCeu/RtQ+MUh3ucANseeXA= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= -github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= -github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= +github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE= +github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= github.com/go-enry/go-enry/v2 v2.9.2 h1:giOQAtCgBX08kosrX818DCQJTCNtKwoPBGu0qb6nKTY= @@ -231,8 +264,10 @@ 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.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= -github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= +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-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= @@ -250,10 +285,10 @@ github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI6 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-webauthn/webauthn v0.13.0 h1:cJIL1/1l+22UekVhipziAaSgESJxokYkowUqAIsWs0Y= -github.com/go-webauthn/webauthn v0.13.0/go.mod h1:Oy9o2o79dbLKRPZWWgRIOdtBGAhKnDIaBp2PFkICRHs= -github.com/go-webauthn/x v0.1.21 h1:nFbckQxudvHEJn2uy1VEi713MeSpApoAv9eRqsb9AdQ= -github.com/go-webauthn/x v0.1.21/go.mod h1:sEYohtg1zL4An1TXIUIQ5csdmoO+WO0R4R2pGKaHYKA= +github.com/go-webauthn/webauthn v0.13.4 h1:q68qusWPcqHbg9STSxBLBHnsKaLxNO0RnVKaAqMuAuQ= +github.com/go-webauthn/webauthn v0.13.4/go.mod h1:MglN6OH9ECxvhDqoq1wMoF6P6JRYDiQpC9nc5OomQmI= +github.com/go-webauthn/x v0.1.23 h1:9lEO0s+g8iTyz5Vszlg/rXTGrx3CjcD0RZQ1GPZCaxI= +github.com/go-webauthn/x v0.1.23/go.mod h1:AJd3hI7NfEp/4fI6T4CHD753u91l510lglU7/NMN6+E= 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= @@ -265,16 +300,26 @@ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7w github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14= github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0= github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +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/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +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= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -284,11 +329,13 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -306,13 +353,20 @@ github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +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-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +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/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= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= @@ -327,12 +381,19 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 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-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 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/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= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -340,30 +401,32 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= -github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA= -github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= +github.com/inbucket/html2text v0.9.0 h1:ULJmVcBEMAcmLE+/rN815KG1Fx6+a4HhbUxiDiN+qks= +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/jhillyerd/enmime/v2 v2.1.0 h1:c8Qwi5Xq5EdtMN6byQWoZ/8I2RMTo6OJ7Xay+s1oPO0= -github.com/jhillyerd/enmime/v2 v2.1.0/go.mod h1:EJ74dcRbBcqHSP2TBu08XRoy6y3Yx0cevwb1YkGMEmQ= +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= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 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.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 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/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.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU= +github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -380,8 +443,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ= -github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/libdns/libdns v1.0.0 h1:IvYaz07JNz6jUQ4h/fv2R4sVnRnm77J/aOuC9B+TQTA= +github.com/libdns/libdns v1.0.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= @@ -389,30 +452,36 @@ github.com/markbates/going v1.0.3 h1:mY45T5TvW+Xz5A6jY7lf4+NLg9D8+iuStIHyR7M8qsE github.com/markbates/going v1.0.3/go.mod h1:fQiT6v6yQar9UD6bd/D4Z5Afbk9J6BBVBtLiyY4gp2o= github.com/markbates/goth v1.80.0 h1:NnvatczZDzOs1hn9Ug+dVYf2Viwwkp/ZDX5K+GLjan8= github.com/markbates/goth v1.80.0/go.mod h1:4/GYHo+W6NWisrMPZnq0Yr2Q70UntNLn7KXEFhrIdAY= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +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.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 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-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= -github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/meilisearch/meilisearch-go v0.31.0 h1:yZRhY1qJqdH8h6GFZALGtkDLyj8f9v5aJpsNMyrUmnY= -github.com/meilisearch/meilisearch-go v0.31.0/go.mod h1:aNtyuwurDg/ggxQIcKqWH6G9g2ptc8GyY7PLY4zMn/g= +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= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/meilisearch/meilisearch-go v0.34.0 h1:P+Ohdx4/PCxXaoI5wNi0LMwPkuiNrF/kGIzBrKYS4tw= +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.3 h1:aEAaOtNra78G+TvV5ohmXrJOAzf++dIlYeDW3N9q458= +github.com/mholt/archives v0.1.3/go.mod h1:LUCGp++/IbV/I0Xq4SzcIR6uwgeh2yjnQWamjRQfLTU= 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= github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= -github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY= -github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= +github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0= +github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= +github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg= +github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.94 h1:1ZoksIKPyaSt64AVOyaQvhDOgVC3MfZsWM6mZXRUGtM= -github.com/minio/minio-go/v7 v7.0.94/go.mod h1:71t2CqDt3ThzESgZUlU1rBN54mksGGlkLcFgguDnnAc= +github.com/minio/minio-go/v7 v7.0.95 h1:ywOUPg+PebTMTzn9VDsoFJy32ZuARN9zhB+K3IYEvYU= +github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo= +github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ= +github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -428,16 +497,19 @@ github.com/msteinert/pam/v2 v2.1.0 h1:er5F9TKV5nGFuTt12ubtqPHEUdeBwReP7vd3wovidG github.com/msteinert/pam/v2 v2.1.0/go.mod h1:KT28NNIcDFf3PcBmNI2mIGO4zZJ+9RSs/At2PB3IDVc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/niklasfasching/go-org v1.8.0 h1:WyGLaajLLp8JbQzkmapZ1y0MOzKuKV47HkZRloi+HGY= -github.com/niklasfasching/go-org v1.8.0/go.mod h1:e2A9zJs7cdONrEGs3gvxCcaAEpwwPNPG7csDpXckMNg= -github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= -github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +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.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U= +github.com/nwaples/rardecode/v2 v2.1.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= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= +github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= +github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw= +github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs= github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E= github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -455,9 +527,8 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= -github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= -github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= -github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= +github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= @@ -472,6 +543,7 @@ github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= @@ -482,19 +554,21 @@ 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-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rhysd/actionlint v1.6.27 h1:xxwe8YmveBcC8lydW6GoHMGmB6H/MTqUU60F2p10wjw= -github.com/rhysd/actionlint v1.6.27/go.mod h1:m2nFUjAnOrxCMXuOMz9evYBRCLUsMnKY2IJl/N5umbk= +github.com/rhysd/actionlint v1.7.7 h1:0KgkoNTrYY7vmOCs9BW2AHxLvvpoY9nEUzgBHiPUr0k= +github.com/rhysd/actionlint v1.7.7/go.mod h1:AE6I6vJEkNaIfWqC2GNE5spIJNhxf8NCtLEKU4NnUXg= 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= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= @@ -503,8 +577,10 @@ github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepq github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= -github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg= +github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk= 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= @@ -519,26 +595,23 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww= github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= -github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I= -github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM= +github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js= @@ -546,8 +619,8 @@ github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBz github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY= -github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= +github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= @@ -558,22 +631,32 @@ 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= -go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= -go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= +go.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= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= -go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 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= +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= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= @@ -583,23 +666,59 @@ 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.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +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/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= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= -golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= +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/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= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= -golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +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/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= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +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-20200520004742-59133d7f0dd7/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= @@ -612,12 +731,21 @@ 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.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +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/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/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= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -625,15 +753,25 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -645,7 +783,6 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/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= @@ -653,8 +790,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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.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= @@ -664,9 +801,12 @@ 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.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +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= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -676,31 +816,88 @@ 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.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +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/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= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +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/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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 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.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= 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= @@ -728,6 +925,11 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +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= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= @@ -750,6 +952,9 @@ modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 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= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo= xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU= diff --git a/models/actions/artifact.go b/models/actions/artifact.go index 10cd3868a1..492e3f6cea 100644 --- a/models/actions/artifact.go +++ b/models/actions/artifact.go @@ -108,6 +108,7 @@ func UpdateArtifactByID(ctx context.Context, id int64, art *ActionArtifact) erro type FindArtifactsOptions struct { db.ListOptions + ID int64 RepoID int64 RunID int64 ArtifactName string @@ -116,6 +117,9 @@ type FindArtifactsOptions struct { func (opts FindArtifactsOptions) ToConds() builder.Cond { cond := builder.NewCond() + if opts.ID > 0 { + cond = cond.And(builder.Eq{"id": opts.ID}) + } if opts.RepoID > 0 { cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) } @@ -132,6 +136,13 @@ func (opts FindArtifactsOptions) ToConds() builder.Cond { return cond } +var _ db.FindOptionsOrder = FindArtifactsOptions{} + +// ToOrders implements db.FindOptionsOrder, to have a stable order +func (opts FindArtifactsOptions) ToOrders() string { + return "id" +} + // ActionArtifactMeta is the meta data of an artifact type ActionArtifactMeta struct { ArtifactName string diff --git a/models/actions/main_test.go b/models/actions/main_test.go index 2eb923d9d0..f551d39671 100644 --- a/models/actions/main_test.go +++ b/models/actions/main_test.go @@ -15,6 +15,10 @@ func TestMain(m *testing.M) { "action_runner.yml", "repository.yml", "action_runner_token.yml", + "user.yml", + "action_run.yml", + "action_run_job.yml", + "action_task.yml", }, }) } diff --git a/models/actions/run.go b/models/actions/run.go index 69592120e9..b5f79a0cb3 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -21,7 +21,7 @@ import ( "forgejo.org/modules/util" webhook_module "forgejo.org/modules/webhook" - "github.com/nektos/act/pkg/jobparser" + "code.forgejo.org/forgejo/runner/v11/act/jobparser" "xorm.io/builder" ) @@ -309,15 +309,26 @@ func GetLatestRunForBranchAndWorkflow(ctx context.Context, repoID int64, branch, } func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) { - var run ActionRun - has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run) + run, has, err := GetRunByIDWithHas(ctx, id) if err != nil { return nil, err } else if !has { return nil, fmt.Errorf("run with id %d: %w", id, util.ErrNotExist) } - return &run, nil + return run, nil +} + +func GetRunByIDWithHas(ctx context.Context, id int64) (*ActionRun, bool, error) { + var run ActionRun + has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run) + if err != nil { + return nil, false, err + } else if !has { + return nil, false, nil + } + + return &run, true, nil } func GetRunByIndex(ctx context.Context, repoID, index int64) (*ActionRun, error) { diff --git a/models/actions/run_job.go b/models/actions/run_job.go index 1fadb4b7c7..edd1170072 100644 --- a/models/actions/run_job.go +++ b/models/actions/run_job.go @@ -44,6 +44,38 @@ func init() { db.RegisterModel(new(ActionRunJob)) } +func (job *ActionRunJob) HTMLURL(ctx context.Context) (string, error) { + if job.Run == nil || job.Run.Repo == nil { + return "", fmt.Errorf("action_run_job: load run and repo before accessing HTMLURL") + } + + // Find the "index" of the currently selected job... kinda ugly that the URL uses the index rather than some other + // unique identifier of the job which could actually be stored upon it. But hard to change that now. + allJobs, err := GetRunJobsByRunID(ctx, job.RunID) + if err != nil { + return "", err + } + jobIndex := -1 + for i, otherJob := range allJobs { + if job.ID == otherJob.ID { + jobIndex = i + break + } + } + if jobIndex == -1 { + return "", fmt.Errorf("action_run_job: unable to find job on run: %d", job.ID) + } + + attempt := job.Attempt + // If a job has never been fetched by a runner yet, it will have attempt 0 -- but this attempt will never have a + // valid UI since attempt is incremented to 1 if it is picked up by a runner. + if attempt == 0 { + attempt = 1 + } + + return fmt.Sprintf("%s/actions/runs/%d/jobs/%d/attempt/%d", job.Run.Repo.HTMLURL(), job.Run.Index, jobIndex, attempt), nil +} + func (job *ActionRunJob) Duration() time.Duration { return calculateDuration(job.Started, job.Stopped, job.Status) } diff --git a/models/actions/run_job_test.go b/models/actions/run_job_test.go index 50a4ba10d8..6abdb2bf5c 100644 --- a/models/actions/run_job_test.go +++ b/models/actions/run_job_test.go @@ -3,9 +3,14 @@ package actions import ( + "fmt" "testing" + "forgejo.org/models/db" + "forgejo.org/models/unittest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestActionRunJob_ItRunsOn(t *testing.T) { @@ -27,3 +32,41 @@ func TestActionRunJob_ItRunsOn(t *testing.T) { assert.False(t, actionJob.ItRunsOn(agentLabels)) } + +func TestActionRunJob_HTMLURL(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + tests := []struct { + id int64 + expected string + }{ + { + id: 192, + expected: "https://try.gitea.io/user5/repo4/actions/runs/187/jobs/0/attempt/1", + }, + { + id: 393, + expected: "https://try.gitea.io/user2/repo1/actions/runs/187/jobs/1/attempt/1", + }, + { + id: 394, + expected: "https://try.gitea.io/user2/repo1/actions/runs/187/jobs/2/attempt/2", + }, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("id=%d", tt.id), func(t *testing.T) { + var job ActionRunJob + has, err := db.GetEngine(t.Context()).Where("id=?", tt.id).Get(&job) + require.NoError(t, err) + require.True(t, has, "load ActionRunJob from fixture") + + err = job.LoadAttributes(t.Context()) + require.NoError(t, err) + + url, err := job.HTMLURL(t.Context()) + require.NoError(t, err) + assert.Equal(t, tt.expected, url) + }) + } +} diff --git a/models/actions/task.go b/models/actions/task.go index 93369db7e8..6e77db1b18 100644 --- a/models/actions/task.go +++ b/models/actions/task.go @@ -17,8 +17,8 @@ import ( "forgejo.org/modules/timeutil" "forgejo.org/modules/util" + "code.forgejo.org/forgejo/runner/v11/act/jobparser" lru "github.com/hashicorp/golang-lru/v2" - "github.com/nektos/act/pkg/jobparser" "xorm.io/builder" ) @@ -148,6 +148,21 @@ func (task *ActionTask) GenerateToken() (err error) { return err } +// Retrieve all the attempts from the same job as the target `ActionTask`. Limited fields are queried to avoid loading +// the LogIndexes blob when not needed. +func (task *ActionTask) GetAllAttempts(ctx context.Context) ([]*ActionTask, error) { + var attempts []*ActionTask + err := db.GetEngine(ctx). + Cols("id", "attempt", "status", "started"). + Where("job_id=?", task.JobID). + Desc("attempt"). + Find(&attempts) + if err != nil { + return nil, err + } + return attempts, nil +} + func GetTaskByID(ctx context.Context, id int64) (*ActionTask, error) { var task ActionTask has, err := db.GetEngine(ctx).Where("id=?", id).Get(&task) @@ -160,6 +175,18 @@ func GetTaskByID(ctx context.Context, id int64) (*ActionTask, error) { return &task, nil } +func GetTaskByJobAttempt(ctx context.Context, jobID, attempt int64) (*ActionTask, error) { + var task ActionTask + has, err := db.GetEngine(ctx).Where("job_id=?", jobID).Where("attempt=?", attempt).Get(&task) + if err != nil { + return nil, err + } else if !has { + return nil, fmt.Errorf("task with job_id %d and attempt %d: %w", jobID, attempt, util.ErrNotExist) + } + + return &task, nil +} + func GetRunningTaskByToken(ctx context.Context, token string) (*ActionTask, error) { errNotExist := fmt.Errorf("task with token %q: %w", token, util.ErrNotExist) if token == "" { @@ -275,7 +302,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask } var workflowJob *jobparser.Job - if gots, err := jobparser.Parse(job.WorkflowPayload); err != nil { + if gots, err := jobparser.Parse(job.WorkflowPayload, false); err != nil { return nil, false, fmt.Errorf("parse workflow of job %d: %w", job.ID, err) } else if len(gots) != 1 { return nil, false, fmt.Errorf("workflow of job %d: not single workflow", job.ID) diff --git a/models/actions/task_test.go b/models/actions/task_test.go new file mode 100644 index 0000000000..73aff17a85 --- /dev/null +++ b/models/actions/task_test.go @@ -0,0 +1,48 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package actions + +import ( + "testing" + + "forgejo.org/models/db" + "forgejo.org/models/unittest" + "forgejo.org/modules/timeutil" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestActionTask_GetAllAttempts(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + var task ActionTask + has, err := db.GetEngine(t.Context()).Where("id=?", 47).Get(&task) + require.NoError(t, err) + require.True(t, has, "load ActionTask from fixture") + + allAttempts, err := task.GetAllAttempts(t.Context()) + require.NoError(t, err) + require.Len(t, allAttempts, 3) + assert.EqualValues(t, 47, allAttempts[0].ID, "ordered by attempt, 1") + assert.EqualValues(t, 53, allAttempts[1].ID, "ordered by attempt, 2") + assert.EqualValues(t, 52, allAttempts[2].ID, "ordered by attempt, 3") + + // GetAllAttempts doesn't populate all fields; so check expected fields from one of the records + assert.EqualValues(t, 3, allAttempts[0].Attempt, "read Attempt field") + assert.Equal(t, StatusRunning, allAttempts[0].Status, "read Status field") + assert.Equal(t, timeutil.TimeStamp(1683636528), allAttempts[0].Started, "read Started field") +} + +func TestActionTask_GetTaskByJobAttempt(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + task, err := GetTaskByJobAttempt(t.Context(), 192, 2) + require.NoError(t, err) + assert.EqualValues(t, 192, task.JobID) + assert.EqualValues(t, 2, task.Attempt) + + _, err = GetTaskByJobAttempt(t.Context(), 192, 100) + assert.ErrorContains(t, err, "task with job_id 192 and attempt 100: resource does not exist") +} diff --git a/models/activities/action.go b/models/activities/action.go index 8592f81414..5067109545 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -26,6 +26,7 @@ import ( "forgejo.org/modules/base" "forgejo.org/modules/container" "forgejo.org/modules/git" + "forgejo.org/modules/json" "forgejo.org/modules/log" "forgejo.org/modules/setting" "forgejo.org/modules/structs" @@ -386,8 +387,21 @@ func (a *Action) IsIssueEvent() bool { // GetIssueInfos returns a list of associated information with the action. func (a *Action) GetIssueInfos() []string { + // Previously multiple pieces of data used to be encoded into a.Content by pipe-separating them, but this doesn't + // work well if some of the user-entered pieces of content (issue titles, comments, etc.) contain pipes. The newer + // storage format is to json-encode a string array, which we check for and prefer... then fallback to assuming old. + var ret []string + if strings.HasPrefix(a.Content, "[") && strings.HasSuffix(a.Content, "]") { + ret = make([]string, 0, 3) + err := json.Unmarshal([]byte(a.Content), &ret) + if err != nil { + log.Error("GetIssueInfos json decoding error: %v", err) + } + } else { + ret = strings.SplitN(a.Content, "|", 3) + } + // make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length - ret := strings.SplitN(a.Content, "|", 3) for len(ret) < 3 { ret = append(ret, "") } @@ -473,8 +487,11 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err return nil, 0, err } + sess := db.GetEngine(ctx).Where(cond). + Select("`action`.*"). // this line will avoid select other joined table's columns + Join("INNER", "repository", "`repository`.id = `action`.repo_id") + opts.SetDefaultValues() - sess := db.GetEngine(ctx).Where(cond) sess = db.SetSessionPagination(sess, &opts) actions := make([]*Action, 0, opts.PageSize) @@ -767,7 +784,9 @@ func DeleteIssueActions(ctx context.Context, repoID, issueID, issueIndex int64) _, err := e.Where("repo_id = ?", repoID). In("op_type", ActionCreateIssue, ActionCreatePullRequest). - Where("content LIKE ?", strconv.FormatInt(issueIndex, 10)+"|%"). // "IssueIndex|content..." + Where(builder.Or( + builder.Like{"content", strconv.FormatInt(issueIndex, 10) + "|%"}, // "IssueIndex|content..." + builder.Like{"content", "[\"" + strconv.FormatInt(issueIndex, 10) + "\"%"})). // JSON, ["IssueIndex"... Delete(&Action{}) return err } diff --git a/models/activities/action_test.go b/models/activities/action_test.go index 47dbd8ac2d..6911733a09 100644 --- a/models/activities/action_test.go +++ b/models/activities/action_test.go @@ -227,6 +227,24 @@ func TestNotifyWatchers(t *testing.T) { }) } +func TestGetFeedsCorrupted(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + unittest.AssertExistsAndLoadBean(t, &activities_model.Action{ + ID: 8, + RepoID: 1700, + }) + + actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ + RequestedUser: user, + Actor: user, + IncludePrivate: true, + }) + require.NoError(t, err) + assert.Empty(t, actions) + assert.Equal(t, int64(0), count) +} + func TestConsistencyUpdateAction(t *testing.T) { if !setting.Database.Type.IsSQLite3() { t.Skip("Test is only for SQLite database.") @@ -290,14 +308,69 @@ func TestDeleteIssueActions(t *testing.T) { }) require.NoError(t, err) err = db.Insert(db.DefaultContext, &activities_model.Action{ - OpType: activities_model.ActionCreateIssue, - RepoID: issue.RepoID, + OpType: activities_model.ActionCreateIssue, + RepoID: issue.RepoID, + // Older Content format... Content: fmt.Sprintf("%d|content...", issue.Index), }) require.NoError(t, err) + err = db.Insert(db.DefaultContext, &activities_model.Action{ + OpType: activities_model.ActionCreateIssue, + RepoID: issue.RepoID, + // JSON-encoded Content format... + Content: fmt.Sprintf("[\"%d\",\"content...\"]", issue.Index), + }) + require.NoError(t, err) // assert that the actions exist, then delete them - unittest.AssertCount(t, &activities_model.Action{}, 2) + unittest.AssertCount(t, &activities_model.Action{}, 3) require.NoError(t, activities_model.DeleteIssueActions(db.DefaultContext, issue.RepoID, issue.ID, issue.Index)) unittest.AssertCount(t, &activities_model.Action{}, 0) } + +func TestGetIssueInfos(t *testing.T) { + tt := []struct { + content string + field1 string + field2 string + field3 string + }{ + { + content: "4|", + field1: "4", + }, + { + content: "2|docs: Add README w/ template sections", + field1: "2", + field2: "docs: Add README w/ template sections", + }, + { + content: "2|docs: Add README w/ template sections|Some comment...", + field1: "2", + field2: "docs: Add README w/ template sections", + field3: "Some comment...", + }, + { + content: "[\"4\"]", + field1: "4", + }, + { + content: "[\"2\", \"docs: Add README w/ | template sections\"]", + field1: "2", + field2: "docs: Add README w/ | template sections", + }, + { + content: "[\"2\", \"docs: Add README w/ | template sections\", \"Some | comment...\"]", + field1: "2", + field2: "docs: Add README w/ | template sections", + field3: "Some | comment...", + }, + } + for _, test := range tt { + action := &activities_model.Action{Content: test.content} + issueInfos := action.GetIssueInfos() + assert.Equal(t, test.field1, issueInfos[0]) + assert.Equal(t, test.field2, issueInfos[1]) + assert.Equal(t, test.field3, issueInfos[2]) + } +} diff --git a/models/asymkey/gpg_key_object_verification.go b/models/asymkey/gpg_key_object_verification.go index 745ed04869..10567dcd4f 100644 --- a/models/asymkey/gpg_key_object_verification.go +++ b/models/asymkey/gpg_key_object_verification.go @@ -36,6 +36,7 @@ type ObjectVerification struct { TrustStatus string } +// llu:TrKeys const ( // BadSignature is used as the reason when the signature has a KeyID that is in the db // but no key that has that ID verifies the signature. This is a suspicious failure. diff --git a/models/asymkey/main_test.go b/models/asymkey/main_test.go index 316e8f1d54..aa13a93f0a 100644 --- a/models/asymkey/main_test.go +++ b/models/asymkey/main_test.go @@ -15,8 +15,6 @@ func TestMain(m *testing.M) { "gpg_key.yml", "public_key.yml", "TestParseCommitWithSSHSignature/public_key.yml", - "deploy_key.yml", - "gpg_key_import.yml", "user.yml", "email_address.yml", }, diff --git a/models/db/engine.go b/models/db/engine.go index 05a119b08d..42b9150696 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -15,6 +15,7 @@ import ( "strings" "time" + "forgejo.org/modules/container" "forgejo.org/modules/log" "forgejo.org/modules/setting" @@ -385,6 +386,15 @@ func (TracingHook) BeforeProcess(c *contexts.ContextHook) (context.Context, erro } func (TracingHook) AfterProcess(c *contexts.ContextHook) error { + if c.Result != nil { + if rowsAffected, err := c.Result.RowsAffected(); err == nil { + trace.Logf(c.Ctx, "rows affected", "%d", rowsAffected) + } + if lastID, err := c.Result.LastInsertId(); err == nil { + trace.Logf(c.Ctx, "last insert id", "%d", lastID) + } + } + c.Ctx.Value(sqlTask{}).(*trace.Task).End() return nil } @@ -438,3 +448,12 @@ func GetMasterEngine(x Engine) (*xorm.Engine, error) { return engine, nil } + +// GetTableNames returns the table name of all registered models. +func GetTableNames() container.Set[string] { + names := make(container.Set[string]) + for _, table := range tables { + names.Add(x.TableName(table)) + } + return names +} diff --git a/models/db/index.go b/models/db/index.go index 4c15dbe8a1..c069f0febd 100644 --- a/models/db/index.go +++ b/models/db/index.go @@ -72,14 +72,19 @@ func postgresGetNextResourceIndex(ctx context.Context, tableName string, groupID } func mysqlGetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) { - if _, err := GetEngine(ctx).Exec(fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+ - "VALUES (?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1", - tableName), groupID); err != nil { + res, err := GetEngine(ctx).Query(fmt.Sprintf("INSERT INTO %s (group_id, max_index) "+ + "VALUES (?,1) ON DUPLICATE KEY UPDATE max_index = max_index+1 /*M!100500 RETURNING max_index */", + tableName), groupID) + if err != nil { return 0, err } + if len(res) > 0 { + return strconv.ParseInt(string(res[0]["max_index"]), 10, 64) + } + var idx int64 - _, err := GetEngine(ctx).SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ?", tableName), groupID).Get(&idx) + _, err = GetEngine(ctx).SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ?", tableName), groupID).Get(&idx) if err != nil { return 0, err } diff --git a/models/db/table_names_test.go b/models/db/table_names_test.go new file mode 100644 index 0000000000..176ce9905d --- /dev/null +++ b/models/db/table_names_test.go @@ -0,0 +1,40 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package db + +import ( + "slices" + "testing" + + "forgejo.org/modules/test" + + "github.com/stretchr/testify/assert" +) + +func TestGetTableNames(t *testing.T) { + t.Run("Simple", func(t *testing.T) { + defer test.MockVariableValue(&tables, []any{new(GPGKey)})() + + assert.Equal(t, []string{"gpg_key"}, GetTableNames().Values()) + }) + + t.Run("Multiple tables", func(t *testing.T) { + defer test.MockVariableValue(&tables, []any{new(GPGKey), new(User), new(BlockedUser)})() + + tableNames := GetTableNames().Values() + slices.Sort(tableNames) + + assert.Equal(t, []string{"forgejo_blocked_user", "gpg_key", "user"}, tableNames) + }) +} + +type GPGKey struct{} + +type User struct{} + +type BlockedUser struct{} + +func (*BlockedUser) TableName() string { + return "forgejo_blocked_user" +} diff --git a/models/error.go b/models/error.go index ebaa8a135d..99c8ded766 100644 --- a/models/error.go +++ b/models/error.go @@ -121,6 +121,7 @@ type ErrInvalidCloneAddr struct { IsInvalidPath bool IsProtocolInvalid bool IsPermissionDenied bool + HasCredentials bool LocalPath bool } @@ -143,6 +144,9 @@ func (err *ErrInvalidCloneAddr) Error() string { if err.IsURLError { return fmt.Sprintf("migration/cloning from '%s' is not allowed: the provided url is invalid", err.Host) } + if err.HasCredentials { + return fmt.Sprintf("migration/cloning from '%s' is not allowed: the provided url contains credentials", err.Host) + } return fmt.Sprintf("migration/cloning from '%s' is not allowed", err.Host) } diff --git a/models/fixtures/ModerationFeatures/abuse_report.yml b/models/fixtures/ModerationFeatures/abuse_report.yml new file mode 100644 index 0000000000..f2e371ee35 --- /dev/null +++ b/models/fixtures/ModerationFeatures/abuse_report.yml @@ -0,0 +1,21 @@ +- + id: 1 + status: 1 + reporter_id: 2 # @user2 + content_type: 4 # Comment + content_id: 18 # user2/repo2/issues/2#issuecomment-18 + category: 2 # Spam + remarks: The comment I'm reporting is pure SPAM. + shadow_copy_id: null + created_unix: 1752697980 # 2025-07-16 20:33:00 + +- + id: 2 + status: 1 # Open + reporter_id: 2 # @user2 + content_type: 1 # User (users or organizations) + content_id: 1002 # @alexsmith + category: 2 # Spam + remarks: This user just posted a spammy comment on my issue. + shadow_copy_id: null + created_unix: 1752698010 # 2025-07-16 20:33:30 diff --git a/models/fixtures/ModerationFeatures/comment.yml b/models/fixtures/ModerationFeatures/comment.yml new file mode 100644 index 0000000000..a4d41ad997 --- /dev/null +++ b/models/fixtures/ModerationFeatures/comment.yml @@ -0,0 +1,7 @@ +- # This is a spam comment (abusive content), created for testing moderation functionalities. + id: 18 + type: 0 # Standard comment + poster_id: 1002 # @alexsmith + issue_id: 7 # user2/repo2#2 + content: If anyone needs help for promoting their business online using SEO, just contact me (check my profile page). + created_unix: 1752697860 # 2025-07-16 20:31:00 diff --git a/models/fixtures/ModerationFeatures/user.yml b/models/fixtures/ModerationFeatures/user.yml new file mode 100644 index 0000000000..662c61a3e9 --- /dev/null +++ b/models/fixtures/ModerationFeatures/user.yml @@ -0,0 +1,22 @@ +- # This user is a spammer and will create abusive content (for testing moderation functionalities). + id: 1002 + lower_name: alexsmith + name: alexsmith + full_name: Alex Smith + email: alexsmith@example.org + keep_email_private: false + passwd: passwdSalt:password + passwd_hash_algo: dummy + type: 0 + location: '@master@seo.net' + website: http://promote-your-business.biz + pronouns: SEO + salt: passwdSalt + description: I can help you promote your business online using SEO. + created_unix: 1752697800 # 2025-07-16 20:30:00 + is_active: true + is_admin: false + is_restricted: false + avatar: avatar-hash-1002 + avatar_email: alexsmith@example.org + use_custom_avatar: false diff --git a/models/fixtures/action.yml b/models/fixtures/action.yml index a97e94fbf4..f1592d4569 100644 --- a/models/fixtures/action.yml +++ b/models/fixtures/action.yml @@ -59,6 +59,14 @@ created_unix: 1603011540 # grouped with id:7 - id: 8 + user_id: 1 + op_type: 12 # close issue + act_user_id: 1 + repo_id: 1700 # dangling intentional + is_private: false + created_unix: 1603011541 + +- id: 9 user_id: 34 op_type: 12 # close issue act_user_id: 34 diff --git a/models/fixtures/action_artifact.yml b/models/fixtures/action_artifact.yml index 2c51c11ebd..a591719aee 100644 --- a/models/fixtures/action_artifact.yml +++ b/models/fixtures/action_artifact.yml @@ -57,7 +57,7 @@ run_id: 792 runner_id: 1 repo_id: 4 - owner_id: 1 + owner_id: 5 commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 storage_path: "27/5/1730330775594233150.chunk" file_size: 1024 diff --git a/models/fixtures/action_run_job.yml b/models/fixtures/action_run_job.yml index 702c6bc832..911e99c076 100644 --- a/models/fixtures/action_run_job.yml +++ b/models/fixtures/action_run_job.yml @@ -106,7 +106,7 @@ commit_sha: 985f0301dba5e7b34be866819cd15ad3d8f508ee is_fork_pull_request: 0 name: job_2 - attempt: 1 + attempt: 2 job_id: job_2 task_id: 47 status: 5 @@ -128,3 +128,18 @@ runs_on: '["fedora"]' started: 1683636528 stopped: 1683636626 +- + id: 396 + run_id: 794 + repo_id: 4 + owner_id: 1 + commit_sha: 985f0301dba5e7b34be866819cd15ad3d8f508ee + is_fork_pull_request: 0 + name: job_2 + attempt: 0 + job_id: job_2 + task_id: null + status: 5 + runs_on: '["fedora"]' + started: 1683636528 + stopped: 1683636626 diff --git a/models/fixtures/action_task.yml b/models/fixtures/action_task.yml index 506a47d8a0..e5fa35f0b3 100644 --- a/models/fixtures/action_task.yml +++ b/models/fixtures/action_task.yml @@ -117,3 +117,43 @@ log_length: 707 log_size: 90179 log_expired: 0 +- + id: 52 + job_id: 192 + attempt: 1 + runner_id: 1 + status: 1 # success + started: 1683636528 + stopped: 1683636626 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + is_fork_pull_request: 0 + token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784223 + token_salt: ffffffffff + token_last_eight: ffffffff + log_filename: artifact-test2/2f/47.log + log_in_storage: 1 + log_length: 707 + log_size: 90179 + log_expired: 0 +- + id: 53 + job_id: 192 + attempt: 2 + runner_id: 1 + status: 1 # success + started: 1683636528 + stopped: 1683636626 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + is_fork_pull_request: 0 + token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784224 + token_salt: ffffffffff + token_last_eight: ffffffff + log_filename: artifact-test2/2f/47.log + log_in_storage: 1 + log_length: 707 + log_size: 90179 + log_expired: 0 diff --git a/models/fixtures/action_variable.yml b/models/fixtures/action_variable.yml deleted file mode 100644 index ca780a73aa..0000000000 --- a/models/fixtures/action_variable.yml +++ /dev/null @@ -1 +0,0 @@ -[] # empty diff --git a/models/fixtures/deploy_key.yml b/models/fixtures/deploy_key.yml deleted file mode 100644 index ca780a73aa..0000000000 --- a/models/fixtures/deploy_key.yml +++ /dev/null @@ -1 +0,0 @@ -[] # empty diff --git a/models/fixtures/external_login_user.yml b/models/fixtures/external_login_user.yml deleted file mode 100644 index ca780a73aa..0000000000 --- a/models/fixtures/external_login_user.yml +++ /dev/null @@ -1 +0,0 @@ -[] # empty diff --git a/models/fixtures/federated_user.yml b/models/fixtures/federated_user.yml deleted file mode 100644 index ca780a73aa..0000000000 --- a/models/fixtures/federated_user.yml +++ /dev/null @@ -1 +0,0 @@ -[] # empty diff --git a/models/fixtures/federation_host.yml b/models/fixtures/federation_host.yml deleted file mode 100644 index ca780a73aa..0000000000 --- a/models/fixtures/federation_host.yml +++ /dev/null @@ -1 +0,0 @@ -[] # empty diff --git a/models/fixtures/gpg_key_import.yml b/models/fixtures/gpg_key_import.yml deleted file mode 100644 index ca780a73aa..0000000000 --- a/models/fixtures/gpg_key_import.yml +++ /dev/null @@ -1 +0,0 @@ -[] # empty diff --git a/models/fixtures/label.yml b/models/fixtures/label.yml index acfac74968..84c2a7f418 100644 --- a/models/fixtures/label.yml +++ b/models/fixtures/label.yml @@ -3,6 +3,7 @@ repo_id: 1 org_id: 0 name: label1 + description: 'First label' color: '#abcdef' exclusive: false num_issues: 2 @@ -107,3 +108,26 @@ num_issues: 0 num_closed_issues: 0 archived_unix: 0 + +- + id: 11 + repo_id: 3 + org_id: 0 + name: " /'?&" + description: "Malicious label ' " + color: '#000000' + exclusive: true + num_issues: 0 + num_closed_issues: 0 + archived_unix: 0 + +- + id: 12 + repo_id: 3 + org_id: 0 + name: 'archived label<>' + color: '#000000' + exclusive: false + num_issues: 0 + num_closed_issues: 0 + archived_unix: 2991092130 diff --git a/models/fixtures/login_source.yml b/models/fixtures/login_source.yml deleted file mode 100644 index ca780a73aa..0000000000 --- a/models/fixtures/login_source.yml +++ /dev/null @@ -1 +0,0 @@ -[] # empty diff --git a/models/fixtures/protected_branch.yml b/models/fixtures/protected_branch.yml deleted file mode 100644 index ca780a73aa..0000000000 --- a/models/fixtures/protected_branch.yml +++ /dev/null @@ -1 +0,0 @@ -[] # empty diff --git a/models/fixtures/pull_auto_merge.yml b/models/fixtures/pull_auto_merge.yml deleted file mode 100644 index ca780a73aa..0000000000 --- a/models/fixtures/pull_auto_merge.yml +++ /dev/null @@ -1 +0,0 @@ -[] # empty diff --git a/models/fixtures/push_mirror.yml b/models/fixtures/push_mirror.yml deleted file mode 100644 index ca780a73aa..0000000000 --- a/models/fixtures/push_mirror.yml +++ /dev/null @@ -1 +0,0 @@ -[] # empty diff --git a/models/fixtures/repo_archiver.yml b/models/fixtures/repo_archiver.yml deleted file mode 100644 index ca780a73aa..0000000000 --- a/models/fixtures/repo_archiver.yml +++ /dev/null @@ -1 +0,0 @@ -[] # empty diff --git a/models/fixtures/repo_indexer_status.yml b/models/fixtures/repo_indexer_status.yml deleted file mode 100644 index ca780a73aa..0000000000 --- a/models/fixtures/repo_indexer_status.yml +++ /dev/null @@ -1 +0,0 @@ -[] # empty diff --git a/models/fixtures/repo_redirect.yml b/models/fixtures/repo_redirect.yml index 8850c8d780..82d365c600 100644 --- a/models/fixtures/repo_redirect.yml +++ b/models/fixtures/repo_redirect.yml @@ -3,3 +3,9 @@ owner_id: 2 lower_name: oldrepo1 redirect_repo_id: 1 + +- + id: 2 + owner_id: 17 + lower_name: oldrepo24 + redirect_repo_id: 24 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index c383fa43ac..2f104eed65 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -32,7 +32,7 @@ created_unix: 1731254961 updated_unix: 1731254961 topics: '[]' - + - id: 2 owner_id: 2 diff --git a/models/fixtures/secret.yml b/models/fixtures/secret.yml deleted file mode 100644 index ca780a73aa..0000000000 --- a/models/fixtures/secret.yml +++ /dev/null @@ -1 +0,0 @@ -[] # empty diff --git a/models/fixtures/user_redirect.yml b/models/fixtures/user_redirect.yml index f471e94511..2f7a523c0c 100644 --- a/models/fixtures/user_redirect.yml +++ b/models/fixtures/user_redirect.yml @@ -3,3 +3,15 @@ lower_name: olduser1 redirect_user_id: 1 created_unix: 1730000000 + +- + id: 2 + lower_name: oldorg22 + redirect_user_id: 22 + created_unix: 1730000000 + +- + id: 3 + lower_name: oldorg23 + redirect_user_id: 23 + created_unix: 1730000000 diff --git a/models/forgejo_migrations/main_test.go b/models/forgejo_migrations/main_test.go index 031fe8090d..2246e327f0 100644 --- a/models/forgejo_migrations/main_test.go +++ b/models/forgejo_migrations/main_test.go @@ -1,7 +1,7 @@ // Copyright 2023 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "testing" diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go index 94469b7371..71fcf16e7a 100644 --- a/models/forgejo_migrations/migrate.go +++ b/models/forgejo_migrations/migrate.go @@ -1,7 +1,7 @@ // Copyright 2023 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "context" @@ -111,6 +111,14 @@ var migrations = []*Migration{ 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), } // GetCurrentDBVersion returns the current Forgejo database version. diff --git a/models/forgejo_migrations/migrate_test.go b/models/forgejo_migrations/migrate_test.go index 20653929a3..9d16c9fe1c 100644 --- a/models/forgejo_migrations/migrate_test.go +++ b/models/forgejo_migrations/migrate_test.go @@ -1,7 +1,7 @@ // Copyright 2023 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "testing" diff --git a/models/forgejo_migrations/v13.go b/models/forgejo_migrations/v13.go index 614f68249d..ba4183885e 100644 --- a/models/forgejo_migrations/v13.go +++ b/models/forgejo_migrations/v13.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v14.go b/models/forgejo_migrations/v14.go index 53f1ef2223..65b857d343 100644 --- a/models/forgejo_migrations/v14.go +++ b/models/forgejo_migrations/v14.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "forgejo.org/models/migrations/base" diff --git a/models/forgejo_migrations/v15.go b/models/forgejo_migrations/v15.go index 5e5588dd05..a63199ab19 100644 --- a/models/forgejo_migrations/v15.go +++ b/models/forgejo_migrations/v15.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "time" diff --git a/models/forgejo_migrations/v16.go b/models/forgejo_migrations/v16.go index f80bfc5268..a7d4d5d590 100644 --- a/models/forgejo_migrations/v16.go +++ b/models/forgejo_migrations/v16.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v17.go b/models/forgejo_migrations/v17.go index d6e2983d00..8ef6f2c681 100644 --- a/models/forgejo_migrations/v17.go +++ b/models/forgejo_migrations/v17.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v18.go b/models/forgejo_migrations/v18.go index e6c1493f0e..e39b0cbf10 100644 --- a/models/forgejo_migrations/v18.go +++ b/models/forgejo_migrations/v18.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v19.go b/models/forgejo_migrations/v19.go index 69b7746eb1..43d279dcb0 100644 --- a/models/forgejo_migrations/v19.go +++ b/models/forgejo_migrations/v19.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v1_20/v1.go b/models/forgejo_migrations/v1_20/v1.go index 72beaf23de..f0cb125557 100644 --- a/models/forgejo_migrations/v1_20/v1.go +++ b/models/forgejo_migrations/v1_20/v1.go @@ -1,7 +1,7 @@ // Copyright 2023 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_v1_20 //nolint:revive +package forgejo_v1_20 import ( "forgejo.org/modules/timeutil" diff --git a/models/forgejo_migrations/v1_20/v2.go b/models/forgejo_migrations/v1_20/v2.go index 39f3b58924..3f79ac3801 100644 --- a/models/forgejo_migrations/v1_20/v2.go +++ b/models/forgejo_migrations/v1_20/v2.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -package forgejo_v1_20 //nolint:revive +package forgejo_v1_20 import ( "xorm.io/xorm" diff --git a/models/forgejo_migrations/v1_20/v3.go b/models/forgejo_migrations/v1_20/v3.go index cce227e6eb..49530df556 100644 --- a/models/forgejo_migrations/v1_20/v3.go +++ b/models/forgejo_migrations/v1_20/v3.go @@ -1,7 +1,7 @@ // Copyright 2023 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_v1_20 //nolint:revive +package forgejo_v1_20 import ( "forgejo.org/modules/timeutil" diff --git a/models/forgejo_migrations/v1_22/main_test.go b/models/forgejo_migrations/v1_22/main_test.go index 03c4c5272c..d6a5bdacee 100644 --- a/models/forgejo_migrations/v1_22/main_test.go +++ b/models/forgejo_migrations/v1_22/main_test.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "testing" diff --git a/models/forgejo_migrations/v1_22/v10.go b/models/forgejo_migrations/v1_22/v10.go index 819800ae71..cf45abdd24 100644 --- a/models/forgejo_migrations/v1_22/v10.go +++ b/models/forgejo_migrations/v1_22/v10.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "xorm.io/xorm" diff --git a/models/forgejo_migrations/v1_22/v11.go b/models/forgejo_migrations/v1_22/v11.go index 17bb592379..f0f92bd04c 100644 --- a/models/forgejo_migrations/v1_22/v11.go +++ b/models/forgejo_migrations/v1_22/v11.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "forgejo.org/modules/timeutil" diff --git a/models/forgejo_migrations/v1_22/v12.go b/models/forgejo_migrations/v1_22/v12.go index 6822524705..51354bd3c2 100644 --- a/models/forgejo_migrations/v1_22/v12.go +++ b/models/forgejo_migrations/v1_22/v12.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v1_22/v4.go b/models/forgejo_migrations/v1_22/v4.go index f1195f5f66..499d377bb4 100644 --- a/models/forgejo_migrations/v1_22/v4.go +++ b/models/forgejo_migrations/v1_22/v4.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "xorm.io/xorm" diff --git a/models/forgejo_migrations/v1_22/v5.go b/models/forgejo_migrations/v1_22/v5.go index 55f9fe1338..1671d3eed2 100644 --- a/models/forgejo_migrations/v1_22/v5.go +++ b/models/forgejo_migrations/v1_22/v5.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "xorm.io/xorm" diff --git a/models/forgejo_migrations/v1_22/v6.go b/models/forgejo_migrations/v1_22/v6.go index 1a4874872c..072f8e6a15 100644 --- a/models/forgejo_migrations/v1_22/v6.go +++ b/models/forgejo_migrations/v1_22/v6.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "xorm.io/xorm" diff --git a/models/forgejo_migrations/v1_22/v7.go b/models/forgejo_migrations/v1_22/v7.go index b42dd1af67..e7f6eb412b 100644 --- a/models/forgejo_migrations/v1_22/v7.go +++ b/models/forgejo_migrations/v1_22/v7.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "xorm.io/xorm" diff --git a/models/forgejo_migrations/v1_22/v8.go b/models/forgejo_migrations/v1_22/v8.go index 2d3c0c594b..f23b00d2ad 100644 --- a/models/forgejo_migrations/v1_22/v8.go +++ b/models/forgejo_migrations/v1_22/v8.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "strings" diff --git a/models/forgejo_migrations/v1_22/v8_test.go b/models/forgejo_migrations/v1_22/v8_test.go index baaba7290f..5117dd2dfb 100644 --- a/models/forgejo_migrations/v1_22/v8_test.go +++ b/models/forgejo_migrations/v1_22/v8_test.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "testing" diff --git a/models/forgejo_migrations/v1_22/v9.go b/models/forgejo_migrations/v1_22/v9.go index 34c2844c39..e3cdea97f2 100644 --- a/models/forgejo_migrations/v1_22/v9.go +++ b/models/forgejo_migrations/v1_22/v9.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v20.go b/models/forgejo_migrations/v20.go index 8ca9e91f73..91c7b8e911 100644 --- a/models/forgejo_migrations/v20.go +++ b/models/forgejo_migrations/v20.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v21.go b/models/forgejo_migrations/v21.go index 53f141b2ab..61d7950c5a 100644 --- a/models/forgejo_migrations/v21.go +++ b/models/forgejo_migrations/v21.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v22.go b/models/forgejo_migrations/v22.go index eeb738799c..8078591da6 100644 --- a/models/forgejo_migrations/v22.go +++ b/models/forgejo_migrations/v22.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v23.go b/models/forgejo_migrations/v23.go index 20a916a716..a79a4f3d6e 100644 --- a/models/forgejo_migrations/v23.go +++ b/models/forgejo_migrations/v23.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v24.go b/models/forgejo_migrations/v24.go index ebfb5fc1c4..084a57e1ce 100644 --- a/models/forgejo_migrations/v24.go +++ b/models/forgejo_migrations/v24.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v25.go b/models/forgejo_migrations/v25.go index 8e3032a40c..56cde499a3 100644 --- a/models/forgejo_migrations/v25.go +++ b/models/forgejo_migrations/v25.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "context" diff --git a/models/forgejo_migrations/v25_test.go b/models/forgejo_migrations/v25_test.go index e7402fd021..68e71da012 100644 --- a/models/forgejo_migrations/v25_test.go +++ b/models/forgejo_migrations/v25_test.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "testing" diff --git a/models/forgejo_migrations/v26.go b/models/forgejo_migrations/v26.go index 3292d93ffd..a0c47799c2 100644 --- a/models/forgejo_migrations/v26.go +++ b/models/forgejo_migrations/v26.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v27.go b/models/forgejo_migrations/v27.go index 2efa3485a8..9cfbc64370 100644 --- a/models/forgejo_migrations/v27.go +++ b/models/forgejo_migrations/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 //nolint:revive +package forgejo_migrations import ( "forgejo.org/modules/timeutil" diff --git a/models/forgejo_migrations/v28.go b/models/forgejo_migrations/v28.go index cba888d2ec..19f0dcd862 100644 --- a/models/forgejo_migrations/v28.go +++ b/models/forgejo_migrations/v28.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v29.go b/models/forgejo_migrations/v29.go index d0c2f723ae..92eb05e8b3 100644 --- a/models/forgejo_migrations/v29.go +++ b/models/forgejo_migrations/v29.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "database/sql" diff --git a/models/forgejo_migrations/v30.go b/models/forgejo_migrations/v30.go index 6c41a55316..05a1dff898 100644 --- a/models/forgejo_migrations/v30.go +++ b/models/forgejo_migrations/v30.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "time" diff --git a/models/forgejo_migrations/v30_test.go b/models/forgejo_migrations/v30_test.go index f826dab815..152fddeb47 100644 --- a/models/forgejo_migrations/v30_test.go +++ b/models/forgejo_migrations/v30_test.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "testing" diff --git a/models/forgejo_migrations/v31.go b/models/forgejo_migrations/v31.go index fdcab21b1a..23397c7c13 100644 --- a/models/forgejo_migrations/v31.go +++ b/models/forgejo_migrations/v31.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "xorm.io/xorm" diff --git a/models/forgejo_migrations/v31_test.go b/models/forgejo_migrations/v31_test.go index 5b4aac2a60..6d1690aae0 100644 --- a/models/forgejo_migrations/v31_test.go +++ b/models/forgejo_migrations/v31_test.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "testing" diff --git a/models/forgejo_migrations/v32.go b/models/forgejo_migrations/v32.go index bed335ab6b..ce3f855694 100644 --- a/models/forgejo_migrations/v32.go +++ b/models/forgejo_migrations/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 //nolint:revive +package forgejo_migrations import ( "encoding/xml" @@ -12,6 +12,7 @@ import ( "strconv" "strings" + "forgejo.org/models/db" "forgejo.org/models/packages" "forgejo.org/modules/json" "forgejo.org/modules/log" @@ -52,55 +53,50 @@ type mavenPackageResult struct { // ChangeMavenArtifactConcatenation resolves old dash-concatenated Maven coordinates and regenerates metadata. // Note: runs per-owner in a single transaction; failures roll back all owners. func ChangeMavenArtifactConcatenation(x *xorm.Engine) error { - sess := x.NewSession() - defer sess.Close() - - if err := sess.Begin(); err != nil { - return err - } - - // get unique owner IDs of Maven packages - var ownerIDs []*int64 - if err := sess. - Table("package"). - Select("package.owner_id"). - Where("package.type = 'maven'"). - GroupBy("package.owner_id"). - OrderBy("package.owner_id DESC"). - Find(&ownerIDs); err != nil { - return err - } - - for _, id := range ownerIDs { - if err := fixMavenArtifactPerOwner(sess, id); err != nil { - log.Error("owner %d migration failed: %v", id, err) - return err // rollback all + return db.WithTx(db.DefaultContext, func(ctx context.Context) error { + // get unique owner IDs of Maven packages + var ownerIDs []*int64 + if err := db.GetEngine(ctx). + Table("package"). + Select("package.owner_id"). + Where("package.type = 'maven'"). + GroupBy("package.owner_id"). + OrderBy("package.owner_id DESC"). + Find(&ownerIDs); err != nil { + return err } - } - return sess.Commit() + for _, id := range ownerIDs { + if err := fixMavenArtifactPerOwner(ctx, id); err != nil { + log.Error("owner %d migration failed: %v", id, err) + return err // rollback all + } + } + + return nil + }) } -func fixMavenArtifactPerOwner(sess *xorm.Session, ownerID *int64) error { - results, err := getMavenPackageResultsToUpdate(sess, ownerID) +func fixMavenArtifactPerOwner(ctx context.Context, ownerID *int64) error { + results, err := getMavenPackageResultsToUpdate(ctx, ownerID) if err != nil { return err } - if err = resolvePackageCollisions(results, sess); err != nil { + if err = resolvePackageCollisions(ctx, results); err != nil { return err } - if err = processPackageVersions(results, sess); err != nil { + if err = processPackageVersions(ctx, results); err != nil { return err } - return processPackageFiles(results, sess) + return processPackageFiles(ctx, results) } // processPackageFiles updates Maven package files and versions in the database // Returns an error if any database or processing operation fails. -func processPackageFiles(results []*mavenPackageResult, sess *xorm.Session) error { +func processPackageFiles(ctx context.Context, results []*mavenPackageResult) error { processedVersion := make(map[string][]*mavenPackageResult) for _, r := range results { @@ -113,7 +109,7 @@ func processPackageFiles(results []*mavenPackageResult, sess *xorm.Session) erro if r.PackageVersion.ID != r.PackageFile.VersionID { pattern := strings.TrimSuffix(r.PackageFile.Name, ".pom") + "%" // Per routers/api/packages/maven/maven.go:338, POM files already have the `IsLead`, so no update needed for this prop - if _, err := sess.Exec("UPDATE package_file SET version_id = ? WHERE version_id = ? and name like ?", r.PackageVersion.ID, r.PackageFile.VersionID, pattern); err != nil { + if _, err := db.GetEngine(ctx).Exec("UPDATE package_file SET version_id = ? WHERE version_id = ? and name like ?", r.PackageVersion.ID, r.PackageFile.VersionID, pattern); err != nil { return err } } @@ -128,14 +124,14 @@ func processPackageFiles(results []*mavenPackageResult, sess *xorm.Session) erro rs := packageResults[0] - pf, md, err := parseMetadata(sess, rs) + pf, md, err := parseMetadata(ctx, rs) if err != nil { return err } if pf != nil && md != nil && md.GroupID == rs.GroupID && md.ArtifactID == rs.ArtifactID { if pf.VersionID != rs.PackageFile.VersionID { - if _, err := sess.ID(pf.ID).Cols("version_id").Update(pf); err != nil { + if _, err := db.GetEngine(ctx).ID(pf.ID).Cols("version_id").Update(pf); err != nil { return err } } @@ -150,11 +146,9 @@ func processPackageFiles(results []*mavenPackageResult, sess *xorm.Session) erro // parseMetadata retrieves metadata for a Maven package file from the database and decodes it into a Metadata object. // Returns the associated PackageFile, Metadata, and any error encountered during processing. -func parseMetadata(sess *xorm.Session, snapshot *mavenPackageResult) (*packages.PackageFile, *Metadata, error) { - ctx := context.Background() - +func parseMetadata(ctx context.Context, snapshot *mavenPackageResult) (*packages.PackageFile, *Metadata, error) { var pf packages.PackageFile - found, err := sess.Table(pf). + found, err := db.GetEngine(ctx).Table(pf). Where("version_id = ?", snapshot.PackageFile.VersionID). // still the old id And("lower_name = ?", "maven-metadata.xml"). Get(&pf) @@ -183,7 +177,7 @@ func parseMetadata(sess *xorm.Session, snapshot *mavenPackageResult) (*packages. // processPackageVersions processes Maven package versions by updating metadata or inserting new records as necessary. // It avoids redundant updates by tracking already processed versions using a map. Returns an error on failure. -func processPackageVersions(results []*mavenPackageResult, sess *xorm.Session) error { +func processPackageVersions(ctx context.Context, results []*mavenPackageResult) error { processedVersion := make(map[string]int64) for _, r := range results { @@ -196,14 +190,14 @@ func processPackageVersions(results []*mavenPackageResult, sess *xorm.Session) e // for non collisions, just update the metadata if r.PackageVersion.PackageID == r.Package.ID { - if _, err := sess.ID(r.PackageVersion.ID).Cols("metadata_json").Update(r.PackageVersion); err != nil { + if _, err := db.GetEngine(ctx).ID(r.PackageVersion.ID).Cols("metadata_json").Update(r.PackageVersion); err != nil { return err } } else { log.Info("Create new maven package version for %s:%s", r.PackageName, r.PackageVersion.Version) r.PackageVersion.ID = 0 r.PackageVersion.PackageID = r.Package.ID - if _, err := sess.Insert(r.PackageVersion); err != nil { + if _, err := db.GetEngine(ctx).Insert(r.PackageVersion); err != nil { return err } } @@ -216,10 +210,9 @@ func processPackageVersions(results []*mavenPackageResult, sess *xorm.Session) e // getMavenPackageResultsToUpdate retrieves Maven package results that need updates based on the owner ID. // It processes POM metadata, fixes package inconsistencies, and filters corrupted package versions. -func getMavenPackageResultsToUpdate(sess *xorm.Session, ownerID *int64) ([]*mavenPackageResult, error) { - ctx := context.Background() +func getMavenPackageResultsToUpdate(ctx context.Context, ownerID *int64) ([]*mavenPackageResult, error) { var candidates []*mavenPackageResult - if err := sess. + if err := db.GetEngine(ctx). Table("package_file"). Select("package_file.*, package_version.*, package.*"). Join("INNER", "package_version", "package_version.id = package_file.version_id"). @@ -265,7 +258,7 @@ func getMavenPackageResultsToUpdate(sess *xorm.Session, ownerID *int64) ([]*mave // resolvePackageCollisions handles name collisions by keeping the first existing record and inserting new Package records for subsequent collisions. // Returns a map from PackageName to its resolved Package.ID. -func resolvePackageCollisions(results []*mavenPackageResult, sess *xorm.Session) error { +func resolvePackageCollisions(ctx context.Context, results []*mavenPackageResult) error { // Group new names by lowerName collisions := make(map[string][]string) for _, r := range results { @@ -292,7 +285,7 @@ func resolvePackageCollisions(results []*mavenPackageResult, sess *xorm.Session) } else if list[0] == r.PackageName { pkgIDByName[r.PackageName] = r.Package.ID - if _, err = sess.ID(r.Package.ID).Cols("name", "lower_name").Update(r.Package); err != nil { + if _, err = db.GetEngine(ctx).ID(r.Package.ID).Cols("name", "lower_name").Update(r.Package); err != nil { return err } // create a new entry @@ -300,7 +293,7 @@ func resolvePackageCollisions(results []*mavenPackageResult, sess *xorm.Session) log.Info("Create new maven package for %s", r.Package.Name) r.Package.ID = 0 - if _, err = sess.Insert(r.Package); err != nil { + if _, err = db.GetEngine(ctx).Insert(r.Package); err != nil { return err } diff --git a/models/forgejo_migrations/v32_test.go b/models/forgejo_migrations/v32_test.go index cd33de2608..24cda891bc 100644 --- a/models/forgejo_migrations/v32_test.go +++ b/models/forgejo_migrations/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 //nolint:revive +package forgejo_migrations import ( "bytes" diff --git a/models/forgejo_migrations/v33.go b/models/forgejo_migrations/v33.go index 272035fc23..b9ea8efe47 100644 --- a/models/forgejo_migrations/v33.go +++ b/models/forgejo_migrations/v33.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "fmt" diff --git a/models/forgejo_migrations/v33_test.go b/models/forgejo_migrations/v33_test.go index 664c704bbc..1d3298da15 100644 --- a/models/forgejo_migrations/v33_test.go +++ b/models/forgejo_migrations/v33_test.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. // SPDX-License-Identifier: GPL-3.0-or-later -package forgejo_migrations //nolint:revive +package forgejo_migrations import ( "testing" diff --git a/models/forgejo_migrations/v34.go b/models/forgejo_migrations/v34.go index 9e958b934f..d193d799e7 100644 --- a/models/forgejo_migrations/v34.go +++ b/models/forgejo_migrations/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 //nolint:revive +package forgejo_migrations import "xorm.io/xorm" diff --git a/models/forgejo_migrations/v35.go b/models/forgejo_migrations/v35.go index ca412d7951..9b389fcc12 100644 --- a/models/forgejo_migrations/v35.go +++ b/models/forgejo_migrations/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 //nolint:revive +package forgejo_migrations import ( "xorm.io/xorm" diff --git a/models/forgejo_migrations/v36.go b/models/forgejo_migrations/v36.go index 019c177a08..1a798147cf 100644 --- a/models/forgejo_migrations/v36.go +++ b/models/forgejo_migrations/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 //nolint:revive +package forgejo_migrations import ( "xorm.io/xorm" diff --git a/models/forgejo_migrations/v37.go b/models/forgejo_migrations/v37.go new file mode 100644 index 0000000000..89358991af --- /dev/null +++ b/models/forgejo_migrations/v37.go @@ -0,0 +1,16 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations + +import ( + "xorm.io/xorm" +) + +func AddPushMirrorBranchFilter(x *xorm.Engine) error { + type PushMirror struct { + ID int64 `xorm:"pk autoincr"` + BranchFilter string `xorm:"VARCHAR(255)"` + } + return x.Sync2(new(PushMirror)) +} diff --git a/models/forgejo_migrations/v38.go b/models/forgejo_migrations/v38.go new file mode 100644 index 0000000000..24240f15a0 --- /dev/null +++ b/models/forgejo_migrations/v38.go @@ -0,0 +1,19 @@ +// 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 AddResolvedUnixToAbuseReport(x *xorm.Engine) error { + type AbuseReport struct { + ID int64 `xorm:"pk autoincr"` + ResolvedUnix timeutil.TimeStamp `xorm:"DEFAULT NULL"` + } + + return x.Sync(&AbuseReport{}) +} diff --git a/models/forgejo_migrations/v39.go b/models/forgejo_migrations/v39.go new file mode 100644 index 0000000000..9af1c250b3 --- /dev/null +++ b/models/forgejo_migrations/v39.go @@ -0,0 +1,78 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations + +import ( + "context" + "fmt" + + "forgejo.org/models/db" + secret_model "forgejo.org/models/secret" + "forgejo.org/modules/log" + "forgejo.org/modules/secret" + "forgejo.org/modules/setting" + + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +func MigrateActionSecretsToKeying(x *xorm.Engine) error { + return db.WithTx(db.DefaultContext, func(ctx context.Context) error { + sess := db.GetEngine(ctx) + + switch x.Dialect().URI().DBType { + case schemas.MYSQL: + if _, err := sess.Exec("ALTER TABLE `secret` MODIFY `data` BLOB"); err != nil { + return err + } + case schemas.SQLITE: + if _, err := sess.Exec("ALTER TABLE `secret` RENAME COLUMN `data` TO `data_backup`"); err != nil { + return err + } + if _, err := sess.Exec("ALTER TABLE `secret` ADD COLUMN `data` BLOB"); err != nil { + return err + } + if _, err := sess.Exec("UPDATE `secret` SET `data` = `data_backup`"); err != nil { + return err + } + if _, err := sess.Exec("ALTER TABLE `secret` DROP COLUMN `data_backup`"); err != nil { + return err + } + case schemas.POSTGRES: + if _, err := sess.Exec("ALTER TABLE `secret` ALTER COLUMN `data` SET DATA TYPE bytea USING data::text::bytea"); err != nil { + return err + } + } + + oldEncryptionKey := setting.SecretKey + + messages := make([]string, 0, 100) + ids := make([]int64, 0, 100) + + err := db.Iterate(ctx, nil, func(ctx context.Context, bean *secret_model.Secret) error { + secretBytes, err := secret.DecryptSecret(oldEncryptionKey, string(bean.Data)) + if err != nil { + messages = append(messages, fmt.Sprintf("secret.id=%d, secret.name=%q, secret.repo_id=%d, secret.owner_id=%d: secret.DecryptSecret(): %v", bean.ID, bean.Name, bean.RepoID, bean.OwnerID, err)) + ids = append(ids, bean.ID) + return nil + } + + bean.SetSecret(secretBytes) + _, err = sess.Cols("data").ID(bean.ID).Update(bean) + return err + }) + + if err == nil { + if len(ids) > 0 { + log.Error("Forgejo migration[37]: The following action secrets were found to be corrupted and removed from the database.") + for _, message := range messages { + log.Error("Forgejo migration[37]: %s", message) + } + + _, err = sess.In("id", ids).NoAutoCondition().NoAutoTime().Delete(&secret_model.Secret{}) + } + } + return err + }) +} diff --git a/models/forgejo_migrations/v39_test.go b/models/forgejo_migrations/v39_test.go new file mode 100644 index 0000000000..42934d912f --- /dev/null +++ b/models/forgejo_migrations/v39_test.go @@ -0,0 +1,52 @@ +// 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" + "forgejo.org/models/secret" + "forgejo.org/modules/keying" + "forgejo.org/modules/timeutil" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_MigrateActionSecretToKeying(t *testing.T) { + type Secret struct { + ID int64 + OwnerID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL"` + RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL DEFAULT 0"` + Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"` + Data string `xorm:"LONGTEXT"` // encrypted data + CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"` + } + + // Prepare and load the testing database + x, deferable := migration_tests.PrepareTestEnv(t, 0, new(Secret)) + defer deferable() + if x == nil || t.Failed() { + return + } + + cnt, err := x.Table("secret").Count() + require.NoError(t, err) + assert.EqualValues(t, 2, cnt) + + require.NoError(t, MigrateActionSecretsToKeying(x)) + + cnt, err = x.Table("secret").Count() + require.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var secret secret.Secret + _, err = x.Table("secret").ID(1).Get(&secret) + require.NoError(t, err) + + secretBytes, err := keying.DeriveKey(keying.ContextActionSecret).Decrypt(secret.Data, keying.ColumnAndID("data", secret.ID)) + require.NoError(t, err) + assert.Equal(t, []byte("A deep dark secret"), secretBytes) +} diff --git a/models/forgejo_migrations/v40.go b/models/forgejo_migrations/v40.go new file mode 100644 index 0000000000..11e8fbd85e --- /dev/null +++ b/models/forgejo_migrations/v40.go @@ -0,0 +1,13 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package forgejo_migrations + +import "xorm.io/xorm" + +func AddIndexForReleaseSha1(x *xorm.Engine) error { + type Release struct { + Sha1 string `xorm:"INDEX VARCHAR(64)"` + } + return x.Sync(new(Release)) +} diff --git a/models/issues/action_aggregator.go b/models/issues/action_aggregator.go index d3643adeef..c4632fd4dd 100644 --- a/models/issues/action_aggregator.go +++ b/models/issues/action_aggregator.go @@ -4,6 +4,7 @@ package issues import ( + "context" "slices" "forgejo.org/models/organization" @@ -374,3 +375,10 @@ func (t *RequestReviewTarget) Type() string { } return "team" } + +func (t *RequestReviewTarget) Link(ctx context.Context) string { + if t.User != nil { + return t.User.HomeLink() + } + return t.Team.Link(ctx) +} diff --git a/models/issues/action_aggregator_test.go b/models/issues/action_aggregator_test.go new file mode 100644 index 0000000000..1962596d2d --- /dev/null +++ b/models/issues/action_aggregator_test.go @@ -0,0 +1,37 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package issues + +import ( + "testing" + + "forgejo.org/models/db" + org_model "forgejo.org/models/organization" + "forgejo.org/models/unittest" + user_model "forgejo.org/models/user" + + "github.com/stretchr/testify/assert" +) + +func TestRequestReviewTarget(t *testing.T) { + unittest.PrepareTestEnv(t) + + target := RequestReviewTarget{User: &user_model.User{ID: 1, Name: "user1"}} + assert.Equal(t, int64(1), target.ID()) + assert.Equal(t, "user1", target.Name()) + assert.Equal(t, "user", target.Type()) + assert.Equal(t, "/user1", target.Link(db.DefaultContext)) + + target = RequestReviewTarget{Team: &org_model.Team{ID: 2, Name: "Collaborators", OrgID: 3}} + assert.Equal(t, int64(2), target.ID()) + assert.Equal(t, "Collaborators", target.Name()) + assert.Equal(t, "team", target.Type()) + assert.Equal(t, "/org/org3/teams/Collaborators", target.Link(db.DefaultContext)) + + target = RequestReviewTarget{Team: org_model.NewGhostTeam()} + assert.Equal(t, int64(-1), target.ID()) + assert.Equal(t, "Ghost team", target.Name()) + assert.Equal(t, "team", target.Type()) + assert.Empty(t, target.Link(db.DefaultContext)) +} diff --git a/models/issues/comment.go b/models/issues/comment.go index a81221caf4..6afd1623f3 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -795,17 +795,16 @@ func (c *Comment) LoadPushCommits(ctx context.Context) (err error) { } c.OldCommit = data.CommitIDs[0] c.NewCommit = data.CommitIDs[1] - } else { - gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, c.Issue.Repo) - if err != nil { - return err - } - defer closer.Close() - - c.Commits = git_model.ParseCommitsWithStatus(ctx, gitRepo.GetCommitsFromIDs(data.CommitIDs), c.Issue.Repo) - c.CommitsNum = int64(len(c.Commits)) } + gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, c.Issue.Repo) + if err != nil { + return err + } + defer closer.Close() + c.Commits = git_model.ParseCommitsWithStatus(ctx, gitRepo.GetCommitsFromIDs(data.CommitIDs, c.IsForcePush), c.Issue.Repo) + c.CommitsNum = int64(len(c.Commits)) + return err } @@ -1156,7 +1155,7 @@ func UpdateComment(ctx context.Context, c *Comment, contentVersion int, doer *us defer committer.Close() // If the comment was reported as abusive, a shadow copy should be created before first update. - if err := IfNeededCreateShadowCopyForComment(ctx, c); err != nil { + if err := IfNeededCreateShadowCopyForComment(ctx, c, true); err != nil { return err } @@ -1197,7 +1196,7 @@ func DeleteComment(ctx context.Context, comment *Comment) error { e := db.GetEngine(ctx) // If the comment was reported as abusive, a shadow copy should be created before deletion. - if err := IfNeededCreateShadowCopyForComment(ctx, comment); err != nil { + if err := IfNeededCreateShadowCopyForComment(ctx, comment, false); err != nil { return err } diff --git a/models/issues/issue.go b/models/issues/issue.go index 5edebb4105..14848e4c98 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -237,7 +237,7 @@ func (issue *Issue) LoadPullRequest(ctx context.Context) (err error) { return nil } -func (issue *Issue) loadComments(ctx context.Context) (err error) { +func (issue *Issue) LoadComments(ctx context.Context) (err error) { return issue.loadCommentsByType(ctx, CommentTypeUndefined) } @@ -341,7 +341,7 @@ func (issue *Issue) LoadAttributes(ctx context.Context) (err error) { return err } - if err = issue.loadComments(ctx); err != nil { + if err = issue.LoadComments(ctx); err != nil { return err } @@ -469,6 +469,8 @@ func (issue *Issue) GetLastEventTimestamp() timeutil.TimeStamp { } // GetLastEventLabel returns the localization label for the current issue. +// +//llu:returnsTrKey func (issue *Issue) GetLastEventLabel() string { if issue.IsClosed { if issue.IsPull && issue.PullRequest.HasMerged { @@ -494,6 +496,8 @@ func (issue *Issue) GetLastComment(ctx context.Context) (*Comment, error) { } // GetLastEventLabelFake returns the localization label for the current issue without providing a link in the username. +// +//llu:returnsTrKey func (issue *Issue) GetLastEventLabelFake() string { if issue.IsClosed { if issue.IsPull && issue.PullRequest.HasMerged { diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go index 91a69c26a7..529f0c15d4 100644 --- a/models/issues/issue_search.go +++ b/models/issues/issue_search.go @@ -48,7 +48,9 @@ type IssuesOptions struct { //nolint UpdatedBeforeUnix int64 // prioritize issues from this repo PriorityRepoID int64 - IsArchived optional.Option[bool] + // if this issue index (not ID) exists and matches the filters, *and* priorityrepo sort is used, show it first + PriorityIssueIndex int64 + IsArchived optional.Option[bool] // If combined with AllPublic, then private as well as public issues // that matches the criteria will be returned, if AllPublic is false @@ -60,7 +62,7 @@ type IssuesOptions struct { //nolint // applySorts sort an issues-related session based on the provided // sortType string -func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) { +func applySorts(sess *xorm.Session, sortType string, priorityRepoID, priorityIssueIndex int64) { switch sortType { case "oldest": sess.Asc("issue.created_unix").Asc("issue.id") @@ -97,8 +99,11 @@ func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) { case "priorityrepo": sess.OrderBy("CASE "+ "WHEN issue.repo_id = ? THEN 1 "+ - "ELSE 2 END ASC", priorityRepoID). - Desc("issue.created_unix"). + "ELSE 2 END ASC", priorityRepoID) + if priorityIssueIndex != 0 { + sess.OrderBy("issue.index = ? DESC", priorityIssueIndex) + } + sess.Desc("issue.created_unix"). Desc("issue.id") case "project-column-sorting": sess.Asc("project_issue.sorting").Desc("issue.created_unix").Desc("issue.id") @@ -470,7 +475,7 @@ func Issues(ctx context.Context, opts *IssuesOptions) (IssueList, error) { Join("INNER", "repository", "`issue`.repo_id = `repository`.id") applyLimit(sess, opts) applyConditions(sess, opts) - applySorts(sess, opts.SortType, opts.PriorityRepoID) + applySorts(sess, opts.SortType, opts.PriorityRepoID, opts.PriorityIssueIndex) issues := IssueList{} if err := sess.Find(&issues); err != nil { @@ -494,7 +499,7 @@ func IssueIDs(ctx context.Context, opts *IssuesOptions, otherConds ...builder.Co } applyLimit(sess, opts) - applySorts(sess, opts.SortType, opts.PriorityRepoID) + applySorts(sess, opts.SortType, opts.PriorityRepoID, opts.PriorityIssueIndex) var res []int64 total, err := sess.Select("`issue`.id").Table(&Issue{}).FindAndCount(&res) diff --git a/models/issues/moderation.go b/models/issues/moderation.go index 635d295db0..9afb711d65 100644 --- a/models/issues/moderation.go +++ b/models/issues/moderation.go @@ -5,6 +5,7 @@ package issues import ( "context" + "strconv" "forgejo.org/models/moderation" "forgejo.org/modules/json" @@ -24,6 +25,21 @@ type IssueData struct { UpdatedUnix timeutil.TimeStamp } +// Implements GetFieldsMap() from ShadowCopyData interface, returning a list of pairs +// to be used when rendering the shadow copy for admins reviewing the corresponding abuse report(s). +func (cd IssueData) GetFieldsMap() []moderation.ShadowCopyField { + return []moderation.ShadowCopyField{ + {Key: "RepoID", Value: strconv.FormatInt(cd.RepoID, 10)}, + {Key: "Index", Value: strconv.FormatInt(cd.Index, 10)}, + {Key: "PosterID", Value: strconv.FormatInt(cd.PosterID, 10)}, + {Key: "Title", Value: cd.Title}, + {Key: "Content", Value: cd.Content}, + {Key: "ContentVersion", Value: strconv.Itoa(cd.ContentVersion)}, + {Key: "CreatedUnix", Value: cd.CreatedUnix.AsLocalTime().String()}, + {Key: "UpdatedUnix", Value: cd.UpdatedUnix.AsLocalTime().String()}, + } +} + // newIssueData creates a trimmed down issue to be used just to create a JSON structure // (keeping only the fields relevant for moderation purposes) func newIssueData(issue *Issue) IssueData { @@ -31,8 +47,8 @@ func newIssueData(issue *Issue) IssueData { RepoID: issue.RepoID, Index: issue.Index, PosterID: issue.PosterID, - Content: issue.Content, Title: issue.Title, + Content: issue.Content, ContentVersion: issue.ContentVersion, CreatedUnix: issue.CreatedUnix, UpdatedUnix: issue.UpdatedUnix, @@ -50,6 +66,19 @@ type CommentData struct { UpdatedUnix timeutil.TimeStamp } +// Implements GetFieldsMap() from ShadowCopyData interface, returning a list of pairs +// to be used when rendering the shadow copy for admins reviewing the corresponding abuse report(s). +func (cd CommentData) GetFieldsMap() []moderation.ShadowCopyField { + return []moderation.ShadowCopyField{ + {Key: "PosterID", Value: strconv.FormatInt(cd.PosterID, 10)}, + {Key: "IssueID", Value: strconv.FormatInt(cd.IssueID, 10)}, + {Key: "Content", Value: cd.Content}, + {Key: "ContentVersion", Value: strconv.Itoa(cd.ContentVersion)}, + {Key: "CreatedUnix", Value: cd.CreatedUnix.AsLocalTime().String()}, + {Key: "UpdatedUnix", Value: cd.UpdatedUnix.AsLocalTime().String()}, + } +} + // newCommentData creates a trimmed down comment to be used just to create a JSON structure // (keeping only the fields relevant for moderation purposes) func newCommentData(comment *Comment) CommentData { @@ -87,13 +116,19 @@ func IfNeededCreateShadowCopyForIssue(ctx context.Context, issue *Issue) error { // IfNeededCreateShadowCopyForComment checks if for the given comment there are any reports of abusive content submitted // and if found a shadow copy of relevant comment fields will be stored into DB and linked to the above report(s). // This function should be called before a comment is deleted or updated. -func IfNeededCreateShadowCopyForComment(ctx context.Context, comment *Comment) error { +func IfNeededCreateShadowCopyForComment(ctx context.Context, comment *Comment, forUpdates bool) error { shadowCopyNeeded, err := moderation.IsShadowCopyNeeded(ctx, moderation.ReportedContentTypeComment, comment.ID) if err != nil { return err } if shadowCopyNeeded { + if forUpdates { + // get the unaltered comment fields (for updates the provided variable is already altered but not yet saved) + if comment, err = GetCommentByID(ctx, comment.ID); err != nil { + return err + } + } commentData := newCommentData(comment) content, err := json.Marshal(commentData) if err != nil { diff --git a/models/issues/moderation_test.go b/models/issues/moderation_test.go new file mode 100644 index 0000000000..adb07bd63a --- /dev/null +++ b/models/issues/moderation_test.go @@ -0,0 +1,70 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package issues_test + +import ( + "testing" + + "forgejo.org/models/issues" + "forgejo.org/models/moderation" + "forgejo.org/modules/timeutil" + + "github.com/stretchr/testify/assert" +) + +const ( + tsCreated timeutil.TimeStamp = timeutil.TimeStamp(1753093500) // 2025-07-21 10:25:00 UTC + tsUpdated timeutil.TimeStamp = timeutil.TimeStamp(1753093525) // 2025-07-21 10:25:25 UTC +) + +func testShadowCopyField(t *testing.T, scField moderation.ShadowCopyField, key, value string) { + assert.Equal(t, key, scField.Key) + assert.Equal(t, value, scField.Value) +} + +func TestIssueDataGetFieldsMap(t *testing.T) { + id := issues.IssueData{ + RepoID: 2001, + Index: 2, + PosterID: 1002, + Title: "Professional marketing services", + Content: "Visit my website at promote-your-business.biz for a list of available services.", + ContentVersion: 0, + CreatedUnix: tsCreated, + UpdatedUnix: tsUpdated, + } + scFields := id.GetFieldsMap() + + if assert.Len(t, scFields, 8) { + testShadowCopyField(t, scFields[0], "RepoID", "2001") + testShadowCopyField(t, scFields[1], "Index", "2") + testShadowCopyField(t, scFields[2], "PosterID", "1002") + testShadowCopyField(t, scFields[3], "Title", "Professional marketing services") + testShadowCopyField(t, scFields[4], "Content", "Visit my website at promote-your-business.biz for a list of available services.") + testShadowCopyField(t, scFields[5], "ContentVersion", "0") + testShadowCopyField(t, scFields[6], "CreatedUnix", tsCreated.AsLocalTime().String()) + testShadowCopyField(t, scFields[7], "UpdatedUnix", tsUpdated.AsLocalTime().String()) + } +} + +func TestCommentDataGetFieldsMap(t *testing.T) { + cd := issues.CommentData{ + PosterID: 1002, + IssueID: 3001, + Content: "Check out [alexsmith/website](/alexsmith/website)", + ContentVersion: 0, + CreatedUnix: tsCreated, + UpdatedUnix: tsUpdated, + } + scFields := cd.GetFieldsMap() + + if assert.Len(t, scFields, 6) { + testShadowCopyField(t, scFields[0], "PosterID", "1002") + testShadowCopyField(t, scFields[1], "IssueID", "3001") + testShadowCopyField(t, scFields[2], "Content", "Check out [alexsmith/website](/alexsmith/website)") + testShadowCopyField(t, scFields[3], "ContentVersion", "0") + testShadowCopyField(t, scFields[4], "CreatedUnix", tsCreated.AsLocalTime().String()) + testShadowCopyField(t, scFields[5], "UpdatedUnix", tsUpdated.AsLocalTime().String()) + } +} diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go index 8fc0491026..ddb813cf44 100644 --- a/models/issues/pull_list.go +++ b/models/issues/pull_list.go @@ -149,7 +149,7 @@ func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptio } findSession := listPullRequestStatement(ctx, baseRepoID, opts) - applySorts(findSession, opts.SortType, 0) + applySorts(findSession, opts.SortType, 0, 0) findSession = db.SetSessionPagination(findSession, opts) prs := make([]*PullRequest, 0, opts.PageSize) found := findSession.Find(&prs) diff --git a/models/migrations/fixtures/Test_MigrateActionSecretToKeying/secret.yml b/models/migrations/fixtures/Test_MigrateActionSecretToKeying/secret.yml new file mode 100644 index 0000000000..908b428321 --- /dev/null +++ b/models/migrations/fixtures/Test_MigrateActionSecretToKeying/secret.yml @@ -0,0 +1,14 @@ +- + id: 1 + owner_id: 2 + repo_id: 1 + name: SECRET_1 + data: 02458e5f341b2d5081a31283559843b6b7543ab98ed213d2b15b5cef94385fa348afa7e0875122e9 + created_unix: 1753556968 +- + id: 2 + owner_id: 2 + repo_id: 1 + name: BADBAD + data: badbad + created_unix: 1753556968 diff --git a/models/migrations/test/tests.go b/models/migrations/test/tests.go index c1f0caf19b..6be3b3c2fc 100644 --- a/models/migrations/test/tests.go +++ b/models/migrations/test/tests.go @@ -95,7 +95,8 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu t.Logf("initializing fixtures from: %s", fixturesDir) if err := unittest.InitFixtures( unittest.FixturesOptions{ - Dir: fixturesDir, + Dir: fixturesDir, + SkipCleanRegistedModels: true, }, 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/migrations/v1_10/v100.go index 5d2fd8e244..1742bea296 100644 --- a/models/migrations/v1_10/v100.go +++ b/models/migrations/v1_10/v100.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import ( "net/url" diff --git a/models/migrations/v1_10/v101.go b/models/migrations/v1_10/v101.go index f023a2a0e7..6c8dfe2486 100644 --- a/models/migrations/v1_10/v101.go +++ b/models/migrations/v1_10/v101.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_10/v88.go b/models/migrations/v1_10/v88.go index 7e86ac364f..eb8e81c19e 100644 --- a/models/migrations/v1_10/v88.go +++ b/models/migrations/v1_10/v88.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import ( "crypto/sha1" diff --git a/models/migrations/v1_10/v89.go b/models/migrations/v1_10/v89.go index d5f27ffdc6..0df2a6e17b 100644 --- a/models/migrations/v1_10/v89.go +++ b/models/migrations/v1_10/v89.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import "xorm.io/xorm" diff --git a/models/migrations/v1_10/v90.go b/models/migrations/v1_10/v90.go index 295d4b1c1b..5521a97e32 100644 --- a/models/migrations/v1_10/v90.go +++ b/models/migrations/v1_10/v90.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import "xorm.io/xorm" diff --git a/models/migrations/v1_10/v91.go b/models/migrations/v1_10/v91.go index 48cac2de70..08db6c2742 100644 --- a/models/migrations/v1_10/v91.go +++ b/models/migrations/v1_10/v91.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import "xorm.io/xorm" diff --git a/models/migrations/v1_10/v92.go b/models/migrations/v1_10/v92.go index 9080108594..b6c04a9234 100644 --- a/models/migrations/v1_10/v92.go +++ b/models/migrations/v1_10/v92.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import ( "xorm.io/builder" diff --git a/models/migrations/v1_10/v93.go b/models/migrations/v1_10/v93.go index ee59a8db39..c131be9a8d 100644 --- a/models/migrations/v1_10/v93.go +++ b/models/migrations/v1_10/v93.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import "xorm.io/xorm" diff --git a/models/migrations/v1_10/v94.go b/models/migrations/v1_10/v94.go index c131af162b..13b7d7b303 100644 --- a/models/migrations/v1_10/v94.go +++ b/models/migrations/v1_10/v94.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import "xorm.io/xorm" diff --git a/models/migrations/v1_10/v95.go b/models/migrations/v1_10/v95.go index 3b1f67fd9c..86b52026bf 100644 --- a/models/migrations/v1_10/v95.go +++ b/models/migrations/v1_10/v95.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import "xorm.io/xorm" diff --git a/models/migrations/v1_10/v96.go b/models/migrations/v1_10/v96.go index 3bfb770f24..bcbd618b49 100644 --- a/models/migrations/v1_10/v96.go +++ b/models/migrations/v1_10/v96.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import ( "path/filepath" diff --git a/models/migrations/v1_10/v97.go b/models/migrations/v1_10/v97.go index dee45b32e3..5872bb63e5 100644 --- a/models/migrations/v1_10/v97.go +++ b/models/migrations/v1_10/v97.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import "xorm.io/xorm" diff --git a/models/migrations/v1_10/v98.go b/models/migrations/v1_10/v98.go index bdd9aed089..d21c326459 100644 --- a/models/migrations/v1_10/v98.go +++ b/models/migrations/v1_10/v98.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import "xorm.io/xorm" diff --git a/models/migrations/v1_10/v99.go b/models/migrations/v1_10/v99.go index 7f287b77aa..addae66be9 100644 --- a/models/migrations/v1_10/v99.go +++ b/models/migrations/v1_10/v99.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_10 //nolint +package v1_10 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_11/v102.go b/models/migrations/v1_11/v102.go index a585d9c423..15f0c83c36 100644 --- a/models/migrations/v1_11/v102.go +++ b/models/migrations/v1_11/v102.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_11/v103.go b/models/migrations/v1_11/v103.go index 53527dac58..a515710160 100644 --- a/models/migrations/v1_11/v103.go +++ b/models/migrations/v1_11/v103.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_11/v104.go b/models/migrations/v1_11/v104.go index af3578ca4a..7461f0cda3 100644 --- a/models/migrations/v1_11/v104.go +++ b/models/migrations/v1_11/v104.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_11/v105.go b/models/migrations/v1_11/v105.go index b91340c30a..d86973a0f6 100644 --- a/models/migrations/v1_11/v105.go +++ b/models/migrations/v1_11/v105.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_11/v106.go b/models/migrations/v1_11/v106.go index ecb11cdd1e..edffe18683 100644 --- a/models/migrations/v1_11/v106.go +++ b/models/migrations/v1_11/v106.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_11/v107.go b/models/migrations/v1_11/v107.go index f0bfe5862c..a158e3bb50 100644 --- a/models/migrations/v1_11/v107.go +++ b/models/migrations/v1_11/v107.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_11/v108.go b/models/migrations/v1_11/v108.go index a85096234d..8f14504ceb 100644 --- a/models/migrations/v1_11/v108.go +++ b/models/migrations/v1_11/v108.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_11/v109.go b/models/migrations/v1_11/v109.go index ea565ccda3..f7616aec7b 100644 --- a/models/migrations/v1_11/v109.go +++ b/models/migrations/v1_11/v109.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_11/v110.go b/models/migrations/v1_11/v110.go index fce9be847e..e94a738f67 100644 --- a/models/migrations/v1_11/v110.go +++ b/models/migrations/v1_11/v110.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_11/v111.go b/models/migrations/v1_11/v111.go index cc3dc0d545..6f531e4858 100644 --- a/models/migrations/v1_11/v111.go +++ b/models/migrations/v1_11/v111.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "fmt" diff --git a/models/migrations/v1_11/v112.go b/models/migrations/v1_11/v112.go index 6112ab51a5..22054e6f68 100644 --- a/models/migrations/v1_11/v112.go +++ b/models/migrations/v1_11/v112.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "fmt" diff --git a/models/migrations/v1_11/v113.go b/models/migrations/v1_11/v113.go index dea344a44f..a4d54f66fb 100644 --- a/models/migrations/v1_11/v113.go +++ b/models/migrations/v1_11/v113.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "fmt" diff --git a/models/migrations/v1_11/v114.go b/models/migrations/v1_11/v114.go index 95adcee989..9467a8a90c 100644 --- a/models/migrations/v1_11/v114.go +++ b/models/migrations/v1_11/v114.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "net/url" diff --git a/models/migrations/v1_11/v115.go b/models/migrations/v1_11/v115.go index 3d4b41017b..65094df93d 100644 --- a/models/migrations/v1_11/v115.go +++ b/models/migrations/v1_11/v115.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "crypto/md5" diff --git a/models/migrations/v1_11/v116.go b/models/migrations/v1_11/v116.go index 85aa76c1e0..729fbad18b 100644 --- a/models/migrations/v1_11/v116.go +++ b/models/migrations/v1_11/v116.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_11 //nolint +package v1_11 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_12/v117.go b/models/migrations/v1_12/v117.go index 8eadcdef2b..73b58ca34b 100644 --- a/models/migrations/v1_12/v117.go +++ b/models/migrations/v1_12/v117.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_12/v118.go b/models/migrations/v1_12/v118.go index eb022dc5e4..e8b4249743 100644 --- a/models/migrations/v1_12/v118.go +++ b/models/migrations/v1_12/v118.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_12/v119.go b/models/migrations/v1_12/v119.go index 60bfe6a57d..b4bf29a935 100644 --- a/models/migrations/v1_12/v119.go +++ b/models/migrations/v1_12/v119.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_12/v120.go b/models/migrations/v1_12/v120.go index 3f7ed8d373..14d515f5a7 100644 --- a/models/migrations/v1_12/v120.go +++ b/models/migrations/v1_12/v120.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_12/v121.go b/models/migrations/v1_12/v121.go index 175ec9164d..a28ae4e1c9 100644 --- a/models/migrations/v1_12/v121.go +++ b/models/migrations/v1_12/v121.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import "xorm.io/xorm" diff --git a/models/migrations/v1_12/v122.go b/models/migrations/v1_12/v122.go index 6e31d863a1..bc1b175f6a 100644 --- a/models/migrations/v1_12/v122.go +++ b/models/migrations/v1_12/v122.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_12/v123.go b/models/migrations/v1_12/v123.go index b0c3af07a3..52b10bb850 100644 --- a/models/migrations/v1_12/v123.go +++ b/models/migrations/v1_12/v123.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_12/v124.go b/models/migrations/v1_12/v124.go index d2ba03ffe0..9a93f436d4 100644 --- a/models/migrations/v1_12/v124.go +++ b/models/migrations/v1_12/v124.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_12/v125.go b/models/migrations/v1_12/v125.go index ec4ffaab25..7f582ecff5 100644 --- a/models/migrations/v1_12/v125.go +++ b/models/migrations/v1_12/v125.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "fmt" diff --git a/models/migrations/v1_12/v126.go b/models/migrations/v1_12/v126.go index ca9ec3aa3f..64fd7f7478 100644 --- a/models/migrations/v1_12/v126.go +++ b/models/migrations/v1_12/v126.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "xorm.io/builder" diff --git a/models/migrations/v1_12/v127.go b/models/migrations/v1_12/v127.go index 11a4042973..f686fa617c 100644 --- a/models/migrations/v1_12/v127.go +++ b/models/migrations/v1_12/v127.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "fmt" diff --git a/models/migrations/v1_12/v128.go b/models/migrations/v1_12/v128.go index 6d7307f470..8fca974616 100644 --- a/models/migrations/v1_12/v128.go +++ b/models/migrations/v1_12/v128.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "fmt" diff --git a/models/migrations/v1_12/v129.go b/models/migrations/v1_12/v129.go index cf228242b9..3e4d3aca68 100644 --- a/models/migrations/v1_12/v129.go +++ b/models/migrations/v1_12/v129.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_12/v130.go b/models/migrations/v1_12/v130.go index bfa856796a..383ef47492 100644 --- a/models/migrations/v1_12/v130.go +++ b/models/migrations/v1_12/v130.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "forgejo.org/modules/json" diff --git a/models/migrations/v1_12/v131.go b/models/migrations/v1_12/v131.go index 5184bc3590..1266c2f185 100644 --- a/models/migrations/v1_12/v131.go +++ b/models/migrations/v1_12/v131.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "fmt" diff --git a/models/migrations/v1_12/v132.go b/models/migrations/v1_12/v132.go index 3b2b28f7ab..8b1ae6db93 100644 --- a/models/migrations/v1_12/v132.go +++ b/models/migrations/v1_12/v132.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "fmt" diff --git a/models/migrations/v1_12/v133.go b/models/migrations/v1_12/v133.go index c9087fc8c1..69e20597d8 100644 --- a/models/migrations/v1_12/v133.go +++ b/models/migrations/v1_12/v133.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import "xorm.io/xorm" diff --git a/models/migrations/v1_12/v134.go b/models/migrations/v1_12/v134.go index bba996fd40..1fabdcae96 100644 --- a/models/migrations/v1_12/v134.go +++ b/models/migrations/v1_12/v134.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "fmt" diff --git a/models/migrations/v1_12/v135.go b/models/migrations/v1_12/v135.go index 8898011df5..5df0ad7fc4 100644 --- a/models/migrations/v1_12/v135.go +++ b/models/migrations/v1_12/v135.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "fmt" diff --git a/models/migrations/v1_12/v136.go b/models/migrations/v1_12/v136.go index e2557ae002..7d246a82be 100644 --- a/models/migrations/v1_12/v136.go +++ b/models/migrations/v1_12/v136.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "fmt" diff --git a/models/migrations/v1_12/v137.go b/models/migrations/v1_12/v137.go index 0d86b72010..9d38483488 100644 --- a/models/migrations/v1_12/v137.go +++ b/models/migrations/v1_12/v137.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_12/v138.go b/models/migrations/v1_12/v138.go index 8c8d353f40..4485adeb2d 100644 --- a/models/migrations/v1_12/v138.go +++ b/models/migrations/v1_12/v138.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "fmt" diff --git a/models/migrations/v1_12/v139.go b/models/migrations/v1_12/v139.go index cd7963524e..51e57b984a 100644 --- a/models/migrations/v1_12/v139.go +++ b/models/migrations/v1_12/v139.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_12 //nolint +package v1_12 import ( "forgejo.org/modules/setting" diff --git a/models/migrations/v1_13/v140.go b/models/migrations/v1_13/v140.go index d74f808e9f..5bb612c098 100644 --- a/models/migrations/v1_13/v140.go +++ b/models/migrations/v1_13/v140.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "fmt" diff --git a/models/migrations/v1_13/v141.go b/models/migrations/v1_13/v141.go index ae211e0e44..b54bc1727c 100644 --- a/models/migrations/v1_13/v141.go +++ b/models/migrations/v1_13/v141.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "fmt" diff --git a/models/migrations/v1_13/v142.go b/models/migrations/v1_13/v142.go index 7490e0f3b4..8939f6f2f8 100644 --- a/models/migrations/v1_13/v142.go +++ b/models/migrations/v1_13/v142.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "forgejo.org/modules/log" diff --git a/models/migrations/v1_13/v143.go b/models/migrations/v1_13/v143.go index 1f9120e2ba..6a8da8b06d 100644 --- a/models/migrations/v1_13/v143.go +++ b/models/migrations/v1_13/v143.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "forgejo.org/modules/log" diff --git a/models/migrations/v1_13/v144.go b/models/migrations/v1_13/v144.go index 7e801eab8a..f138338514 100644 --- a/models/migrations/v1_13/v144.go +++ b/models/migrations/v1_13/v144.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "forgejo.org/modules/log" diff --git a/models/migrations/v1_13/v145.go b/models/migrations/v1_13/v145.go index a01f577ed1..f7d3895c84 100644 --- a/models/migrations/v1_13/v145.go +++ b/models/migrations/v1_13/v145.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "fmt" diff --git a/models/migrations/v1_13/v146.go b/models/migrations/v1_13/v146.go index a1b54ee3aa..e6a476a288 100644 --- a/models/migrations/v1_13/v146.go +++ b/models/migrations/v1_13/v146.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_13/v147.go b/models/migrations/v1_13/v147.go index cc57504c74..831ef5842a 100644 --- a/models/migrations/v1_13/v147.go +++ b/models/migrations/v1_13/v147.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_13/v148.go b/models/migrations/v1_13/v148.go index 7bb8ab700b..d276db3d61 100644 --- a/models/migrations/v1_13/v148.go +++ b/models/migrations/v1_13/v148.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_13/v149.go b/models/migrations/v1_13/v149.go index 3a0c5909d5..c1bfe8b09e 100644 --- a/models/migrations/v1_13/v149.go +++ b/models/migrations/v1_13/v149.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "fmt" diff --git a/models/migrations/v1_13/v150.go b/models/migrations/v1_13/v150.go index be14fd130c..471a531024 100644 --- a/models/migrations/v1_13/v150.go +++ b/models/migrations/v1_13/v150.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_13/v151.go b/models/migrations/v1_13/v151.go index ff584fff67..691b86062d 100644 --- a/models/migrations/v1_13/v151.go +++ b/models/migrations/v1_13/v151.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "context" diff --git a/models/migrations/v1_13/v152.go b/models/migrations/v1_13/v152.go index 502c82a40d..648e26446f 100644 --- a/models/migrations/v1_13/v152.go +++ b/models/migrations/v1_13/v152.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import "xorm.io/xorm" diff --git a/models/migrations/v1_13/v153.go b/models/migrations/v1_13/v153.go index 0b2dd3eb62..e5462fc162 100644 --- a/models/migrations/v1_13/v153.go +++ b/models/migrations/v1_13/v153.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_13/v154.go b/models/migrations/v1_13/v154.go index cf31190781..89dc7821b2 100644 --- a/models/migrations/v1_13/v154.go +++ b/models/migrations/v1_13/v154.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_13 //nolint +package v1_13 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_14/main_test.go b/models/migrations/v1_14/main_test.go index c01faedc35..57cf995be1 100644 --- a/models/migrations/v1_14/main_test.go +++ b/models/migrations/v1_14/main_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "testing" diff --git a/models/migrations/v1_14/v155.go b/models/migrations/v1_14/v155.go index e814f59938..505a9ae033 100644 --- a/models/migrations/v1_14/v155.go +++ b/models/migrations/v1_14/v155.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "fmt" diff --git a/models/migrations/v1_14/v156.go b/models/migrations/v1_14/v156.go index b6dc91a054..7bbd9f4c85 100644 --- a/models/migrations/v1_14/v156.go +++ b/models/migrations/v1_14/v156.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "fmt" diff --git a/models/migrations/v1_14/v157.go b/models/migrations/v1_14/v157.go index 7187278d29..ba69f71130 100644 --- a/models/migrations/v1_14/v157.go +++ b/models/migrations/v1_14/v157.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_14/v158.go b/models/migrations/v1_14/v158.go index 3fa27cfecd..2ab3c8a1f0 100644 --- a/models/migrations/v1_14/v158.go +++ b/models/migrations/v1_14/v158.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "errors" diff --git a/models/migrations/v1_14/v159.go b/models/migrations/v1_14/v159.go index fdd7e12449..4e921ea1c6 100644 --- a/models/migrations/v1_14/v159.go +++ b/models/migrations/v1_14/v159.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_14/v160.go b/models/migrations/v1_14/v160.go index 4dea91b514..73f3798954 100644 --- a/models/migrations/v1_14/v160.go +++ b/models/migrations/v1_14/v160.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_14/v161.go b/models/migrations/v1_14/v161.go index 6e904cfab6..9c850ad0c2 100644 --- a/models/migrations/v1_14/v161.go +++ b/models/migrations/v1_14/v161.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "context" diff --git a/models/migrations/v1_14/v162.go b/models/migrations/v1_14/v162.go index 5d6d7c2e3f..ead63f16f4 100644 --- a/models/migrations/v1_14/v162.go +++ b/models/migrations/v1_14/v162.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_14/v163.go b/models/migrations/v1_14/v163.go index 60fc98c0a4..06ac36cbc7 100644 --- a/models/migrations/v1_14/v163.go +++ b/models/migrations/v1_14/v163.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_14/v164.go b/models/migrations/v1_14/v164.go index 54f6951427..d2fd9b8464 100644 --- a/models/migrations/v1_14/v164.go +++ b/models/migrations/v1_14/v164.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "fmt" diff --git a/models/migrations/v1_14/v165.go b/models/migrations/v1_14/v165.go index 9315e44197..90fd2b1e46 100644 --- a/models/migrations/v1_14/v165.go +++ b/models/migrations/v1_14/v165.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_14/v166.go b/models/migrations/v1_14/v166.go index e5731582fd..4c106bd7da 100644 --- a/models/migrations/v1_14/v166.go +++ b/models/migrations/v1_14/v166.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "crypto/sha256" diff --git a/models/migrations/v1_14/v167.go b/models/migrations/v1_14/v167.go index 9d416f6a32..d77bbc401e 100644 --- a/models/migrations/v1_14/v167.go +++ b/models/migrations/v1_14/v167.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "fmt" diff --git a/models/migrations/v1_14/v168.go b/models/migrations/v1_14/v168.go index a30a8859f7..aa93eec19b 100644 --- a/models/migrations/v1_14/v168.go +++ b/models/migrations/v1_14/v168.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import "xorm.io/xorm" diff --git a/models/migrations/v1_14/v169.go b/models/migrations/v1_14/v169.go index 5b81bb58b1..4f9df0d96f 100644 --- a/models/migrations/v1_14/v169.go +++ b/models/migrations/v1_14/v169.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_14/v170.go b/models/migrations/v1_14/v170.go index 7b6498a3e9..a2ff4623e1 100644 --- a/models/migrations/v1_14/v170.go +++ b/models/migrations/v1_14/v170.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "fmt" diff --git a/models/migrations/v1_14/v171.go b/models/migrations/v1_14/v171.go index 51a35a02ad..7b200e960a 100644 --- a/models/migrations/v1_14/v171.go +++ b/models/migrations/v1_14/v171.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "fmt" diff --git a/models/migrations/v1_14/v172.go b/models/migrations/v1_14/v172.go index d49b70f5ad..c410d393f1 100644 --- a/models/migrations/v1_14/v172.go +++ b/models/migrations/v1_14/v172.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_14/v173.go b/models/migrations/v1_14/v173.go index 2d9eee9197..7752fbe966 100644 --- a/models/migrations/v1_14/v173.go +++ b/models/migrations/v1_14/v173.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "fmt" diff --git a/models/migrations/v1_14/v174.go b/models/migrations/v1_14/v174.go index c839e15db8..4049e43070 100644 --- a/models/migrations/v1_14/v174.go +++ b/models/migrations/v1_14/v174.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "fmt" diff --git a/models/migrations/v1_14/v175.go b/models/migrations/v1_14/v175.go index 3cda5772a0..49fa17d046 100644 --- a/models/migrations/v1_14/v175.go +++ b/models/migrations/v1_14/v175.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "fmt" diff --git a/models/migrations/v1_14/v176.go b/models/migrations/v1_14/v176.go index 1ed49f75fa..ef5dce9a02 100644 --- a/models/migrations/v1_14/v176.go +++ b/models/migrations/v1_14/v176.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_14/v176_test.go b/models/migrations/v1_14/v176_test.go index d88ff207e7..d56b3e0470 100644 --- a/models/migrations/v1_14/v176_test.go +++ b/models/migrations/v1_14/v176_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "testing" diff --git a/models/migrations/v1_14/v177.go b/models/migrations/v1_14/v177.go index 6e1838f369..96676bf8d9 100644 --- a/models/migrations/v1_14/v177.go +++ b/models/migrations/v1_14/v177.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "fmt" diff --git a/models/migrations/v1_14/v177_test.go b/models/migrations/v1_14/v177_test.go index bffc6f92e3..0e0a67fd33 100644 --- a/models/migrations/v1_14/v177_test.go +++ b/models/migrations/v1_14/v177_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_14 //nolint +package v1_14 import ( "testing" diff --git a/models/migrations/v1_15/main_test.go b/models/migrations/v1_15/main_test.go index 6c04d3f5ee..4cf6d6f695 100644 --- a/models/migrations/v1_15/main_test.go +++ b/models/migrations/v1_15/main_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import ( "testing" diff --git a/models/migrations/v1_15/v178.go b/models/migrations/v1_15/v178.go index 6d236eb049..ca3a5c262e 100644 --- a/models/migrations/v1_15/v178.go +++ b/models/migrations/v1_15/v178.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_15/v179.go b/models/migrations/v1_15/v179.go index b990583303..ce514cc4a9 100644 --- a/models/migrations/v1_15/v179.go +++ b/models/migrations/v1_15/v179.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_15/v180.go b/models/migrations/v1_15/v180.go index 02fbd57cdb..0b68c3ceb7 100644 --- a/models/migrations/v1_15/v180.go +++ b/models/migrations/v1_15/v180.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import ( "forgejo.org/modules/json" diff --git a/models/migrations/v1_15/v181.go b/models/migrations/v1_15/v181.go index 2185ed0213..fb1d3d7a75 100644 --- a/models/migrations/v1_15/v181.go +++ b/models/migrations/v1_15/v181.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import ( "strings" diff --git a/models/migrations/v1_15/v181_test.go b/models/migrations/v1_15/v181_test.go index 4154e0b1e9..8196f751e5 100644 --- a/models/migrations/v1_15/v181_test.go +++ b/models/migrations/v1_15/v181_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import ( "strings" diff --git a/models/migrations/v1_15/v182.go b/models/migrations/v1_15/v182.go index 9ca500c0f9..f53ff11df9 100644 --- a/models/migrations/v1_15/v182.go +++ b/models/migrations/v1_15/v182.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_15/v182_test.go b/models/migrations/v1_15/v182_test.go index 6865cafac4..2baf90d06a 100644 --- a/models/migrations/v1_15/v182_test.go +++ b/models/migrations/v1_15/v182_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import ( "testing" diff --git a/models/migrations/v1_15/v183.go b/models/migrations/v1_15/v183.go index aaad64c220..5684e35699 100644 --- a/models/migrations/v1_15/v183.go +++ b/models/migrations/v1_15/v183.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import ( "fmt" diff --git a/models/migrations/v1_15/v184.go b/models/migrations/v1_15/v184.go index 41b64d4743..fbe0dcd780 100644 --- a/models/migrations/v1_15/v184.go +++ b/models/migrations/v1_15/v184.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import ( "context" diff --git a/models/migrations/v1_15/v185.go b/models/migrations/v1_15/v185.go index e5878ec193..60af59edca 100644 --- a/models/migrations/v1_15/v185.go +++ b/models/migrations/v1_15/v185.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_15/v186.go b/models/migrations/v1_15/v186.go index ad75822de5..55d3199335 100644 --- a/models/migrations/v1_15/v186.go +++ b/models/migrations/v1_15/v186.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_15/v187.go b/models/migrations/v1_15/v187.go index b573fc52ef..fabef14779 100644 --- a/models/migrations/v1_15/v187.go +++ b/models/migrations/v1_15/v187.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_15/v188.go b/models/migrations/v1_15/v188.go index 71e45cab0e..4494e6ff05 100644 --- a/models/migrations/v1_15/v188.go +++ b/models/migrations/v1_15/v188.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_15 //nolint +package v1_15 import "xorm.io/xorm" diff --git a/models/migrations/v1_16/main_test.go b/models/migrations/v1_16/main_test.go index 6f891f3e94..8c0a043be6 100644 --- a/models/migrations/v1_16/main_test.go +++ b/models/migrations/v1_16/main_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "testing" diff --git a/models/migrations/v1_16/v189.go b/models/migrations/v1_16/v189.go index 1ee72d9c39..19bfcb2423 100644 --- a/models/migrations/v1_16/v189.go +++ b/models/migrations/v1_16/v189.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "encoding/binary" diff --git a/models/migrations/v1_16/v189_test.go b/models/migrations/v1_16/v189_test.go index 90b721d5f1..9d74462a92 100644 --- a/models/migrations/v1_16/v189_test.go +++ b/models/migrations/v1_16/v189_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "testing" diff --git a/models/migrations/v1_16/v190.go b/models/migrations/v1_16/v190.go index 5953802849..1eb6b6ddb4 100644 --- a/models/migrations/v1_16/v190.go +++ b/models/migrations/v1_16/v190.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "fmt" diff --git a/models/migrations/v1_16/v191.go b/models/migrations/v1_16/v191.go index 567f88d6d1..427476b70b 100644 --- a/models/migrations/v1_16/v191.go +++ b/models/migrations/v1_16/v191.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "forgejo.org/modules/setting" diff --git a/models/migrations/v1_16/v192.go b/models/migrations/v1_16/v192.go index 731b9fb43a..31e8c36346 100644 --- a/models/migrations/v1_16/v192.go +++ b/models/migrations/v1_16/v192.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_16/v193.go b/models/migrations/v1_16/v193.go index 8d3ce7a558..a5af2de380 100644 --- a/models/migrations/v1_16/v193.go +++ b/models/migrations/v1_16/v193.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_16/v193_test.go b/models/migrations/v1_16/v193_test.go index 8260acf32d..bf8d8a7dc6 100644 --- a/models/migrations/v1_16/v193_test.go +++ b/models/migrations/v1_16/v193_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "testing" diff --git a/models/migrations/v1_16/v194.go b/models/migrations/v1_16/v194.go index 6aa13c50cf..2e4ed8340e 100644 --- a/models/migrations/v1_16/v194.go +++ b/models/migrations/v1_16/v194.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "fmt" diff --git a/models/migrations/v1_16/v195.go b/models/migrations/v1_16/v195.go index 6d7e94141e..4fd42b7bd2 100644 --- a/models/migrations/v1_16/v195.go +++ b/models/migrations/v1_16/v195.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "fmt" diff --git a/models/migrations/v1_16/v195_test.go b/models/migrations/v1_16/v195_test.go index 71234a6fb3..1fc7b51f3c 100644 --- a/models/migrations/v1_16/v195_test.go +++ b/models/migrations/v1_16/v195_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "testing" diff --git a/models/migrations/v1_16/v196.go b/models/migrations/v1_16/v196.go index 7cbafc61e5..6c9caa100f 100644 --- a/models/migrations/v1_16/v196.go +++ b/models/migrations/v1_16/v196.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "fmt" diff --git a/models/migrations/v1_16/v197.go b/models/migrations/v1_16/v197.go index 97888b2847..862bdfdcbd 100644 --- a/models/migrations/v1_16/v197.go +++ b/models/migrations/v1_16/v197.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_16/v198.go b/models/migrations/v1_16/v198.go index 8b3c73addc..5d3043eb46 100644 --- a/models/migrations/v1_16/v198.go +++ b/models/migrations/v1_16/v198.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "fmt" diff --git a/models/migrations/v1_16/v199.go b/models/migrations/v1_16/v199.go index 6adcf890af..4020352f2b 100644 --- a/models/migrations/v1_16/v199.go +++ b/models/migrations/v1_16/v199.go @@ -1,6 +1,6 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 // We used to use a table `remote_version` to store information for updater, now we use `AppState`, so this migration task is a no-op now. diff --git a/models/migrations/v1_16/v200.go b/models/migrations/v1_16/v200.go index c08c20e51d..de57fad8fe 100644 --- a/models/migrations/v1_16/v200.go +++ b/models/migrations/v1_16/v200.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "fmt" diff --git a/models/migrations/v1_16/v201.go b/models/migrations/v1_16/v201.go index 35e0c9f2fb..2c43698b0c 100644 --- a/models/migrations/v1_16/v201.go +++ b/models/migrations/v1_16/v201.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_16/v202.go b/models/migrations/v1_16/v202.go index 6ba36152f1..d8c8fdcadc 100644 --- a/models/migrations/v1_16/v202.go +++ b/models/migrations/v1_16/v202.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "fmt" diff --git a/models/migrations/v1_16/v203.go b/models/migrations/v1_16/v203.go index e8e6b52453..c3241cba57 100644 --- a/models/migrations/v1_16/v203.go +++ b/models/migrations/v1_16/v203.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_16/v204.go b/models/migrations/v1_16/v204.go index ece03e1305..4d375307e7 100644 --- a/models/migrations/v1_16/v204.go +++ b/models/migrations/v1_16/v204.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import "xorm.io/xorm" diff --git a/models/migrations/v1_16/v205.go b/models/migrations/v1_16/v205.go index a064b9830d..cb452dfd7f 100644 --- a/models/migrations/v1_16/v205.go +++ b/models/migrations/v1_16/v205.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_16/v206.go b/models/migrations/v1_16/v206.go index 581a7d76e9..01a9c386eb 100644 --- a/models/migrations/v1_16/v206.go +++ b/models/migrations/v1_16/v206.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "fmt" diff --git a/models/migrations/v1_16/v207.go b/models/migrations/v1_16/v207.go index 91208f066c..19126ead1f 100644 --- a/models/migrations/v1_16/v207.go +++ b/models/migrations/v1_16/v207.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_16/v208.go b/models/migrations/v1_16/v208.go index 1a11ef096a..fb643324f4 100644 --- a/models/migrations/v1_16/v208.go +++ b/models/migrations/v1_16/v208.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_16/v209.go b/models/migrations/v1_16/v209.go index be3100e02a..230838647b 100644 --- a/models/migrations/v1_16/v209.go +++ b/models/migrations/v1_16/v209.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_16/v210.go b/models/migrations/v1_16/v210.go index 375a008e18..f48ab11db6 100644 --- a/models/migrations/v1_16/v210.go +++ b/models/migrations/v1_16/v210.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "crypto/ecdh" diff --git a/models/migrations/v1_16/v210_test.go b/models/migrations/v1_16/v210_test.go index f6423a5821..8454920aa0 100644 --- a/models/migrations/v1_16/v210_test.go +++ b/models/migrations/v1_16/v210_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_16 //nolint +package v1_16 import ( "encoding/hex" diff --git a/models/migrations/v1_17/main_test.go b/models/migrations/v1_17/main_test.go index 0a8e05ab5f..166860b3b1 100644 --- a/models/migrations/v1_17/main_test.go +++ b/models/migrations/v1_17/main_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( "testing" diff --git a/models/migrations/v1_17/v211.go b/models/migrations/v1_17/v211.go index 9b72c8610b..517cf19388 100644 --- a/models/migrations/v1_17/v211.go +++ b/models/migrations/v1_17/v211.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_17/v212.go b/models/migrations/v1_17/v212.go index 2337adcc80..23868c0bb2 100644 --- a/models/migrations/v1_17/v212.go +++ b/models/migrations/v1_17/v212.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_17/v213.go b/models/migrations/v1_17/v213.go index bb3f466e52..b2bbdf7279 100644 --- a/models/migrations/v1_17/v213.go +++ b/models/migrations/v1_17/v213.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_17/v214.go b/models/migrations/v1_17/v214.go index 2268164919..1925324f0f 100644 --- a/models/migrations/v1_17/v214.go +++ b/models/migrations/v1_17/v214.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_17/v215.go b/models/migrations/v1_17/v215.go index 5aae798562..431103c98e 100644 --- a/models/migrations/v1_17/v215.go +++ b/models/migrations/v1_17/v215.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( "forgejo.org/models/pull" diff --git a/models/migrations/v1_17/v216.go b/models/migrations/v1_17/v216.go index 268f472a42..37aeacb6fc 100644 --- a/models/migrations/v1_17/v216.go +++ b/models/migrations/v1_17/v216.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 // This migration added non-ideal indices to the action table which on larger datasets slowed things down // it has been superseded by v218.go diff --git a/models/migrations/v1_17/v217.go b/models/migrations/v1_17/v217.go index 5f096d4824..fef48b7a5b 100644 --- a/models/migrations/v1_17/v217.go +++ b/models/migrations/v1_17/v217.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( "forgejo.org/modules/setting" diff --git a/models/migrations/v1_17/v218.go b/models/migrations/v1_17/v218.go index 5e3dcd0841..412d124286 100644 --- a/models/migrations/v1_17/v218.go +++ b/models/migrations/v1_17/v218.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( "forgejo.org/modules/setting" diff --git a/models/migrations/v1_17/v219.go b/models/migrations/v1_17/v219.go index e90656090f..7ca6a26be6 100644 --- a/models/migrations/v1_17/v219.go +++ b/models/migrations/v1_17/v219.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( "time" diff --git a/models/migrations/v1_17/v220.go b/models/migrations/v1_17/v220.go index 61bbf19725..4e010e5b76 100644 --- a/models/migrations/v1_17/v220.go +++ b/models/migrations/v1_17/v220.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( packages_model "forgejo.org/models/packages" diff --git a/models/migrations/v1_17/v221.go b/models/migrations/v1_17/v221.go index 84e9a238af..3ef34e3f06 100644 --- a/models/migrations/v1_17/v221.go +++ b/models/migrations/v1_17/v221.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( "encoding/base32" diff --git a/models/migrations/v1_17/v221_test.go b/models/migrations/v1_17/v221_test.go index 02607d6b32..a9c47136b2 100644 --- a/models/migrations/v1_17/v221_test.go +++ b/models/migrations/v1_17/v221_test.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( "encoding/base32" diff --git a/models/migrations/v1_17/v222.go b/models/migrations/v1_17/v222.go index ae910cbcb6..873769881e 100644 --- a/models/migrations/v1_17/v222.go +++ b/models/migrations/v1_17/v222.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( "context" diff --git a/models/migrations/v1_17/v223.go b/models/migrations/v1_17/v223.go index 7d92dcf5ae..4f5d34d841 100644 --- a/models/migrations/v1_17/v223.go +++ b/models/migrations/v1_17/v223.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_17 //nolint +package v1_17 import ( "context" diff --git a/models/migrations/v1_18/main_test.go b/models/migrations/v1_18/main_test.go index 33f5c51222..0c20934cea 100644 --- a/models/migrations/v1_18/main_test.go +++ b/models/migrations/v1_18/main_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_18 //nolint +package v1_18 import ( "testing" diff --git a/models/migrations/v1_18/v224.go b/models/migrations/v1_18/v224.go index f3d522b91a..6dc12020ea 100644 --- a/models/migrations/v1_18/v224.go +++ b/models/migrations/v1_18/v224.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_18 //nolint +package v1_18 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_18/v225.go b/models/migrations/v1_18/v225.go index 86bcb1323d..266eccfff8 100644 --- a/models/migrations/v1_18/v225.go +++ b/models/migrations/v1_18/v225.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_18 //nolint +package v1_18 import ( "forgejo.org/modules/setting" diff --git a/models/migrations/v1_18/v226.go b/models/migrations/v1_18/v226.go index f87e24b11d..8ed9761476 100644 --- a/models/migrations/v1_18/v226.go +++ b/models/migrations/v1_18/v226.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_18 //nolint +package v1_18 import ( "xorm.io/builder" diff --git a/models/migrations/v1_18/v227.go b/models/migrations/v1_18/v227.go index b6250fb76c..d39a010159 100644 --- a/models/migrations/v1_18/v227.go +++ b/models/migrations/v1_18/v227.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_18 //nolint +package v1_18 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_18/v228.go b/models/migrations/v1_18/v228.go index 1161c8a4c9..3f5b69734d 100644 --- a/models/migrations/v1_18/v228.go +++ b/models/migrations/v1_18/v228.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_18 //nolint +package v1_18 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_18/v229.go b/models/migrations/v1_18/v229.go index f96dde9840..00d794725f 100644 --- a/models/migrations/v1_18/v229.go +++ b/models/migrations/v1_18/v229.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_18 //nolint +package v1_18 import ( "fmt" diff --git a/models/migrations/v1_18/v229_test.go b/models/migrations/v1_18/v229_test.go index ac5e726a79..903a60c851 100644 --- a/models/migrations/v1_18/v229_test.go +++ b/models/migrations/v1_18/v229_test.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_18 //nolint +package v1_18 import ( "testing" diff --git a/models/migrations/v1_18/v230.go b/models/migrations/v1_18/v230.go index ea5b4d02e1..078fce7643 100644 --- a/models/migrations/v1_18/v230.go +++ b/models/migrations/v1_18/v230.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_18 //nolint +package v1_18 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_18/v230_test.go b/models/migrations/v1_18/v230_test.go index 7dd6675673..da31b0dc9b 100644 --- a/models/migrations/v1_18/v230_test.go +++ b/models/migrations/v1_18/v230_test.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_18 //nolint +package v1_18 import ( "testing" diff --git a/models/migrations/v1_19/main_test.go b/models/migrations/v1_19/main_test.go index 7c56926f4c..9d1c3a57ea 100644 --- a/models/migrations/v1_19/main_test.go +++ b/models/migrations/v1_19/main_test.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "testing" diff --git a/models/migrations/v1_19/v231.go b/models/migrations/v1_19/v231.go index 79e46132f0..8ef1e4e743 100644 --- a/models/migrations/v1_19/v231.go +++ b/models/migrations/v1_19/v231.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_19/v232.go b/models/migrations/v1_19/v232.go index 7fb4a5ac8d..2aab2cf830 100644 --- a/models/migrations/v1_19/v232.go +++ b/models/migrations/v1_19/v232.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "forgejo.org/modules/setting" diff --git a/models/migrations/v1_19/v233.go b/models/migrations/v1_19/v233.go index 191afd4868..e62e8a9356 100644 --- a/models/migrations/v1_19/v233.go +++ b/models/migrations/v1_19/v233.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "fmt" diff --git a/models/migrations/v1_19/v233_test.go b/models/migrations/v1_19/v233_test.go index 4dc35d1e27..3d5eac9887 100644 --- a/models/migrations/v1_19/v233_test.go +++ b/models/migrations/v1_19/v233_test.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "testing" diff --git a/models/migrations/v1_19/v234.go b/models/migrations/v1_19/v234.go index c610a423dd..e00b1cc2b6 100644 --- a/models/migrations/v1_19/v234.go +++ b/models/migrations/v1_19/v234.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_19/v235.go b/models/migrations/v1_19/v235.go index 3715de3920..297d90f65a 100644 --- a/models/migrations/v1_19/v235.go +++ b/models/migrations/v1_19/v235.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_19/v236.go b/models/migrations/v1_19/v236.go index fa01a6ab80..c453f95e04 100644 --- a/models/migrations/v1_19/v236.go +++ b/models/migrations/v1_19/v236.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_19/v237.go b/models/migrations/v1_19/v237.go index b23c765aa5..cf30226ccd 100644 --- a/models/migrations/v1_19/v237.go +++ b/models/migrations/v1_19/v237.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_19/v238.go b/models/migrations/v1_19/v238.go index 7c912a8341..b257315319 100644 --- a/models/migrations/v1_19/v238.go +++ b/models/migrations/v1_19/v238.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_19/v239.go b/models/migrations/v1_19/v239.go index 10076f2401..8f4a65be95 100644 --- a/models/migrations/v1_19/v239.go +++ b/models/migrations/v1_19/v239.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_19/v240.go b/models/migrations/v1_19/v240.go index 4ca5becede..c49ce2f49a 100644 --- a/models/migrations/v1_19/v240.go +++ b/models/migrations/v1_19/v240.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "forgejo.org/models/db" diff --git a/models/migrations/v1_19/v241.go b/models/migrations/v1_19/v241.go index a617d6fd2f..e35801a057 100644 --- a/models/migrations/v1_19/v241.go +++ b/models/migrations/v1_19/v241.go @@ -1,7 +1,7 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_19/v242.go b/models/migrations/v1_19/v242.go index bbf227ef77..87ca9cf214 100644 --- a/models/migrations/v1_19/v242.go +++ b/models/migrations/v1_19/v242.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "forgejo.org/modules/setting" diff --git a/models/migrations/v1_19/v243.go b/models/migrations/v1_19/v243.go index 55bbfafb2f..9c3f372594 100644 --- a/models/migrations/v1_19/v243.go +++ b/models/migrations/v1_19/v243.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_19 //nolint +package v1_19 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_20/main_test.go b/models/migrations/v1_20/main_test.go index f870dca429..ee5eec5ef6 100644 --- a/models/migrations/v1_20/main_test.go +++ b/models/migrations/v1_20/main_test.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "testing" diff --git a/models/migrations/v1_20/v244.go b/models/migrations/v1_20/v244.go index 977566ad7d..76cdccaca5 100644 --- a/models/migrations/v1_20/v244.go +++ b/models/migrations/v1_20/v244.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_20/v245.go b/models/migrations/v1_20/v245.go index 7e6585388b..5e034568c4 100644 --- a/models/migrations/v1_20/v245.go +++ b/models/migrations/v1_20/v245.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "context" diff --git a/models/migrations/v1_20/v246.go b/models/migrations/v1_20/v246.go index e6340ef079..22bf723404 100644 --- a/models/migrations/v1_20/v246.go +++ b/models/migrations/v1_20/v246.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_20/v247.go b/models/migrations/v1_20/v247.go index 9ed810a623..056699d744 100644 --- a/models/migrations/v1_20/v247.go +++ b/models/migrations/v1_20/v247.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "forgejo.org/modules/log" diff --git a/models/migrations/v1_20/v248.go b/models/migrations/v1_20/v248.go index 40555210e7..4f2091e4bc 100644 --- a/models/migrations/v1_20/v248.go +++ b/models/migrations/v1_20/v248.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import "xorm.io/xorm" diff --git a/models/migrations/v1_20/v249.go b/models/migrations/v1_20/v249.go index d2b096bf58..0aebb2a343 100644 --- a/models/migrations/v1_20/v249.go +++ b/models/migrations/v1_20/v249.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_20/v250.go b/models/migrations/v1_20/v250.go index cfcde2fc9b..e12223691f 100644 --- a/models/migrations/v1_20/v250.go +++ b/models/migrations/v1_20/v250.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "strings" diff --git a/models/migrations/v1_20/v251.go b/models/migrations/v1_20/v251.go index c8665ba7eb..7d2d259df6 100644 --- a/models/migrations/v1_20/v251.go +++ b/models/migrations/v1_20/v251.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "forgejo.org/modules/log" diff --git a/models/migrations/v1_20/v252.go b/models/migrations/v1_20/v252.go index bb85c78309..435cce7ebe 100644 --- a/models/migrations/v1_20/v252.go +++ b/models/migrations/v1_20/v252.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "forgejo.org/modules/log" diff --git a/models/migrations/v1_20/v253.go b/models/migrations/v1_20/v253.go index 5f4057e9d9..73354fd485 100644 --- a/models/migrations/v1_20/v253.go +++ b/models/migrations/v1_20/v253.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "forgejo.org/modules/log" diff --git a/models/migrations/v1_20/v254.go b/models/migrations/v1_20/v254.go index 1e26979a5b..9cdbfb3916 100644 --- a/models/migrations/v1_20/v254.go +++ b/models/migrations/v1_20/v254.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_20/v255.go b/models/migrations/v1_20/v255.go index 49b0ecf220..baa3c4b6d8 100644 --- a/models/migrations/v1_20/v255.go +++ b/models/migrations/v1_20/v255.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_20/v256.go b/models/migrations/v1_20/v256.go index 822153b93e..7b84c1e154 100644 --- a/models/migrations/v1_20/v256.go +++ b/models/migrations/v1_20/v256.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_20/v257.go b/models/migrations/v1_20/v257.go index 70f229d73f..8045909dba 100644 --- a/models/migrations/v1_20/v257.go +++ b/models/migrations/v1_20/v257.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_20/v258.go b/models/migrations/v1_20/v258.go index 47174ce805..1d3faffdae 100644 --- a/models/migrations/v1_20/v258.go +++ b/models/migrations/v1_20/v258.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_20/v259.go b/models/migrations/v1_20/v259.go index f10b94fa9c..9b2b68263e 100644 --- a/models/migrations/v1_20/v259.go +++ b/models/migrations/v1_20/v259.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "fmt" diff --git a/models/migrations/v1_20/v259_test.go b/models/migrations/v1_20/v259_test.go index 32e4aa3050..b41b6c7995 100644 --- a/models/migrations/v1_20/v259_test.go +++ b/models/migrations/v1_20/v259_test.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_20 //nolint +package v1_20 import ( "sort" diff --git a/models/migrations/v1_21/main_test.go b/models/migrations/v1_21/main_test.go index 7104887afb..3f10a39a94 100644 --- a/models/migrations/v1_21/main_test.go +++ b/models/migrations/v1_21/main_test.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "testing" diff --git a/models/migrations/v1_21/v260.go b/models/migrations/v1_21/v260.go index 245f3011ab..b73b53bd61 100644 --- a/models/migrations/v1_21/v260.go +++ b/models/migrations/v1_21/v260.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_21/v261.go b/models/migrations/v1_21/v261.go index 743bef152d..83a4927704 100644 --- a/models/migrations/v1_21/v261.go +++ b/models/migrations/v1_21/v261.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_21/v262.go b/models/migrations/v1_21/v262.go index 23e900572a..6e88e29b9d 100644 --- a/models/migrations/v1_21/v262.go +++ b/models/migrations/v1_21/v262.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_21/v263.go b/models/migrations/v1_21/v263.go index 2c7cbadf0d..55c418bde0 100644 --- a/models/migrations/v1_21/v263.go +++ b/models/migrations/v1_21/v263.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "fmt" diff --git a/models/migrations/v1_21/v264.go b/models/migrations/v1_21/v264.go index 5615600072..acd2c9bb48 100644 --- a/models/migrations/v1_21/v264.go +++ b/models/migrations/v1_21/v264.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "context" diff --git a/models/migrations/v1_21/v265.go b/models/migrations/v1_21/v265.go index 800eb95f72..b6892acc27 100644 --- a/models/migrations/v1_21/v265.go +++ b/models/migrations/v1_21/v265.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_21/v266.go b/models/migrations/v1_21/v266.go index 79a5f5e14c..440549e868 100644 --- a/models/migrations/v1_21/v266.go +++ b/models/migrations/v1_21/v266.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_21/v267.go b/models/migrations/v1_21/v267.go index f94696a22b..13992d8776 100644 --- a/models/migrations/v1_21/v267.go +++ b/models/migrations/v1_21/v267.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_21/v268.go b/models/migrations/v1_21/v268.go index 332793ff07..b677d2383e 100644 --- a/models/migrations/v1_21/v268.go +++ b/models/migrations/v1_21/v268.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_21/v269.go b/models/migrations/v1_21/v269.go index 475ec02380..042040927d 100644 --- a/models/migrations/v1_21/v269.go +++ b/models/migrations/v1_21/v269.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_21/v270.go b/models/migrations/v1_21/v270.go index b9cc84d3ac..ab7c5660ba 100644 --- a/models/migrations/v1_21/v270.go +++ b/models/migrations/v1_21/v270.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_21/v271.go b/models/migrations/v1_21/v271.go index f45c113c1f..e3ce2d4b74 100644 --- a/models/migrations/v1_21/v271.go +++ b/models/migrations/v1_21/v271.go @@ -1,7 +1,8 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 + import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_21/v272.go b/models/migrations/v1_21/v272.go index a729c49f1b..14c1e0c4b0 100644 --- a/models/migrations/v1_21/v272.go +++ b/models/migrations/v1_21/v272.go @@ -1,7 +1,8 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 + import ( "xorm.io/xorm" ) diff --git a/models/migrations/v1_21/v273.go b/models/migrations/v1_21/v273.go index 1ec6ade566..d6ec80d3d5 100644 --- a/models/migrations/v1_21/v273.go +++ b/models/migrations/v1_21/v273.go @@ -1,7 +1,8 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 + import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_21/v274.go b/models/migrations/v1_21/v274.go index b74e5fed51..a1211d1fdd 100644 --- a/models/migrations/v1_21/v274.go +++ b/models/migrations/v1_21/v274.go @@ -1,7 +1,8 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 + import ( "time" diff --git a/models/migrations/v1_21/v275.go b/models/migrations/v1_21/v275.go index 78804a59d6..2bfe5c72fa 100644 --- a/models/migrations/v1_21/v275.go +++ b/models/migrations/v1_21/v275.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go index 0830c3bd92..3b0bc23da7 100644 --- a/models/migrations/v1_21/v276.go +++ b/models/migrations/v1_21/v276.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( repo_model "forgejo.org/models/repo" diff --git a/models/migrations/v1_21/v277.go b/models/migrations/v1_21/v277.go index 12529160b7..0c102eddde 100644 --- a/models/migrations/v1_21/v277.go +++ b/models/migrations/v1_21/v277.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_21/v278.go b/models/migrations/v1_21/v278.go index d6a462d1e7..846f228678 100644 --- a/models/migrations/v1_21/v278.go +++ b/models/migrations/v1_21/v278.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_21/v279.go b/models/migrations/v1_21/v279.go index 2abd1bbe84..beb39effe1 100644 --- a/models/migrations/v1_21/v279.go +++ b/models/migrations/v1_21/v279.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_21 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_22/main_test.go b/models/migrations/v1_22/main_test.go index dc991b78fe..7b05993e09 100644 --- a/models/migrations/v1_22/main_test.go +++ b/models/migrations/v1_22/main_test.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "testing" diff --git a/models/migrations/v1_22/v280.go b/models/migrations/v1_22/v280.go index a8ee4a3bf7..2271cb6089 100644 --- a/models/migrations/v1_22/v280.go +++ b/models/migrations/v1_22/v280.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_22/v281.go b/models/migrations/v1_22/v281.go index 5271c786be..2eeca9be82 100644 --- a/models/migrations/v1_22/v281.go +++ b/models/migrations/v1_22/v281.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_22/v282.go b/models/migrations/v1_22/v282.go index baad9e0916..eed64c30f7 100644 --- a/models/migrations/v1_22/v282.go +++ b/models/migrations/v1_22/v282.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_22/v283.go b/models/migrations/v1_22/v283.go index 86946d1c39..33a2513069 100644 --- a/models/migrations/v1_22/v283.go +++ b/models/migrations/v1_22/v283.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_22/v283_test.go b/models/migrations/v1_22/v283_test.go index d8e147a131..652d96ac16 100644 --- a/models/migrations/v1_22/v283_test.go +++ b/models/migrations/v1_22/v283_test.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "testing" diff --git a/models/migrations/v1_22/v284.go b/models/migrations/v1_22/v284.go index 2b95078980..31b38f6aed 100644 --- a/models/migrations/v1_22/v284.go +++ b/models/migrations/v1_22/v284.go @@ -1,7 +1,8 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 + import ( "xorm.io/xorm" ) diff --git a/models/migrations/v1_22/v285.go b/models/migrations/v1_22/v285.go index a55cc17c04..fed89f670e 100644 --- a/models/migrations/v1_22/v285.go +++ b/models/migrations/v1_22/v285.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "time" diff --git a/models/migrations/v1_22/v286.go b/models/migrations/v1_22/v286.go index d0489e7aeb..05247bb436 100644 --- a/models/migrations/v1_22/v286.go +++ b/models/migrations/v1_22/v286.go @@ -1,6 +1,6 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "fmt" diff --git a/models/migrations/v1_22/v286_test.go b/models/migrations/v1_22/v286_test.go index c63deef495..5bb3334df2 100644 --- a/models/migrations/v1_22/v286_test.go +++ b/models/migrations/v1_22/v286_test.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "testing" diff --git a/models/migrations/v1_22/v287.go b/models/migrations/v1_22/v287.go index c8b1593286..5fd901f9de 100644 --- a/models/migrations/v1_22/v287.go +++ b/models/migrations/v1_22/v287.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_22/v288.go b/models/migrations/v1_22/v288.go index 44e4991851..78be3b6ef2 100644 --- a/models/migrations/v1_22/v288.go +++ b/models/migrations/v1_22/v288.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_22/v289.go b/models/migrations/v1_22/v289.go index b9941aadd9..78689a4ffa 100644 --- a/models/migrations/v1_22/v289.go +++ b/models/migrations/v1_22/v289.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import "xorm.io/xorm" diff --git a/models/migrations/v1_22/v290.go b/models/migrations/v1_22/v290.go index 594e417644..ebafab6567 100644 --- a/models/migrations/v1_22/v290.go +++ b/models/migrations/v1_22/v290.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_22/v290_test.go b/models/migrations/v1_22/v290_test.go index 569d77bc16..a1907cf4d6 100644 --- a/models/migrations/v1_22/v290_test.go +++ b/models/migrations/v1_22/v290_test.go @@ -1,7 +1,7 @@ // Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "strconv" diff --git a/models/migrations/v1_22/v291.go b/models/migrations/v1_22/v291.go index 74726fae96..823a644a95 100644 --- a/models/migrations/v1_22/v291.go +++ b/models/migrations/v1_22/v291.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import "xorm.io/xorm" diff --git a/models/migrations/v1_22/v292.go b/models/migrations/v1_22/v292.go index beca556aee..440f48ce80 100644 --- a/models/migrations/v1_22/v292.go +++ b/models/migrations/v1_22/v292.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 // NOTE: noop the original migration has bug which some projects will be skip, so // these projects will have no default board. diff --git a/models/migrations/v1_22/v293.go b/models/migrations/v1_22/v293.go index 9f38c3db56..e9c9746b26 100644 --- a/models/migrations/v1_22/v293.go +++ b/models/migrations/v1_22/v293.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "forgejo.org/modules/setting" diff --git a/models/migrations/v1_22/v293_test.go b/models/migrations/v1_22/v293_test.go index 444146737d..6b1931b761 100644 --- a/models/migrations/v1_22/v293_test.go +++ b/models/migrations/v1_22/v293_test.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "testing" diff --git a/models/migrations/v1_22/v294.go b/models/migrations/v1_22/v294.go index 314b4519f1..6c52372306 100644 --- a/models/migrations/v1_22/v294.go +++ b/models/migrations/v1_22/v294.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_22/v294_test.go b/models/migrations/v1_22/v294_test.go index ef7b67ca5b..e87a4bc85f 100644 --- a/models/migrations/v1_22/v294_test.go +++ b/models/migrations/v1_22/v294_test.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import ( "slices" @@ -45,7 +45,8 @@ func Test_AddUniqueIndexForProjectIssue(t *testing.T) { for _, index := range tables[0].Indexes { if index.Type == schemas.UniqueType { found = true - slices.Equal(index.Cols, []string{"project_id", "issue_id"}) + slices.Sort(index.Cols) + assert.Equal(t, []string{"issue_id", "project_id"}, index.Cols) break } } diff --git a/models/migrations/v1_22/v295.go b/models/migrations/v1_22/v295.go index 17bdadb4ad..319b1a399b 100644 --- a/models/migrations/v1_22/v295.go +++ b/models/migrations/v1_22/v295.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import "xorm.io/xorm" diff --git a/models/migrations/v1_22/v296.go b/models/migrations/v1_22/v296.go index 1ecacab95f..75350f9f65 100644 --- a/models/migrations/v1_22/v296.go +++ b/models/migrations/v1_22/v296.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import "xorm.io/xorm" diff --git a/models/migrations/v1_22/v298.go b/models/migrations/v1_22/v298.go index b9f3b95ade..7700173a00 100644 --- a/models/migrations/v1_22/v298.go +++ b/models/migrations/v1_22/v298.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_22 //nolint +package v1_22 import "xorm.io/xorm" diff --git a/models/migrations/v1_23/main_test.go b/models/migrations/v1_23/main_test.go index 0fd90a4a67..5fb4fec999 100644 --- a/models/migrations/v1_23/main_test.go +++ b/models/migrations/v1_23/main_test.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_23 //nolint +package v1_23 import ( "testing" diff --git a/models/migrations/v1_23/v299.go b/models/migrations/v1_23/v299.go index f6db960c3b..73ce19c875 100644 --- a/models/migrations/v1_23/v299.go +++ b/models/migrations/v1_23/v299.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_23 //nolint +package v1_23 import "xorm.io/xorm" diff --git a/models/migrations/v1_23/v300.go b/models/migrations/v1_23/v300.go index f1f1cccdbf..404d8dbea8 100644 --- a/models/migrations/v1_23/v300.go +++ b/models/migrations/v1_23/v300.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_23 //nolint +package v1_23 import "xorm.io/xorm" diff --git a/models/migrations/v1_23/v301.go b/models/migrations/v1_23/v301.go index b7797f6c6b..f2a4d8c559 100644 --- a/models/migrations/v1_23/v301.go +++ b/models/migrations/v1_23/v301.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_23 //nolint +package v1_23 import "xorm.io/xorm" diff --git a/models/migrations/v1_23/v302.go b/models/migrations/v1_23/v302.go index c8ed786d63..1b056993bd 100644 --- a/models/migrations/v1_23/v302.go +++ b/models/migrations/v1_23/v302.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_23 //nolint +package v1_23 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_23/v303.go b/models/migrations/v1_23/v303.go index fae0131bdd..03197d2857 100644 --- a/models/migrations/v1_23/v303.go +++ b/models/migrations/v1_23/v303.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. // SPDX-License-Identifier: GPL-3.0-or-later -package v1_23 //nolint +package v1_23 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_23/v303_test.go b/models/migrations/v1_23/v303_test.go index f105d11830..f2c764bae3 100644 --- a/models/migrations/v1_23/v303_test.go +++ b/models/migrations/v1_23/v303_test.go @@ -1,7 +1,7 @@ // Copyright 2025 The Forgejo Authors. // SPDX-License-Identifier: GPL-3.0-or-later -package v1_23 //nolint +package v1_23 import ( "testing" diff --git a/models/migrations/v1_6/v70.go b/models/migrations/v1_6/v70.go index ec6bd09bb5..eb669f57b6 100644 --- a/models/migrations/v1_6/v70.go +++ b/models/migrations/v1_6/v70.go @@ -1,7 +1,7 @@ // Copyright 2018 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_6 //nolint +package v1_6 import ( "fmt" diff --git a/models/migrations/v1_6/v71.go b/models/migrations/v1_6/v71.go index 3706ad4406..42fe8cd1ba 100644 --- a/models/migrations/v1_6/v71.go +++ b/models/migrations/v1_6/v71.go @@ -1,7 +1,7 @@ // Copyright 2018 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_6 //nolint +package v1_6 import ( "fmt" diff --git a/models/migrations/v1_6/v72.go b/models/migrations/v1_6/v72.go index 4df2a0f6e9..7cd2331376 100644 --- a/models/migrations/v1_6/v72.go +++ b/models/migrations/v1_6/v72.go @@ -1,7 +1,7 @@ // Copyright 2018 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_6 //nolint +package v1_6 import ( "fmt" diff --git a/models/migrations/v1_7/v73.go b/models/migrations/v1_7/v73.go index b5a748aae3..e0b7a28537 100644 --- a/models/migrations/v1_7/v73.go +++ b/models/migrations/v1_7/v73.go @@ -1,7 +1,7 @@ // Copyright 2018 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_7 //nolint +package v1_7 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_7/v74.go b/models/migrations/v1_7/v74.go index f0567e3c9b..376be37a24 100644 --- a/models/migrations/v1_7/v74.go +++ b/models/migrations/v1_7/v74.go @@ -1,7 +1,7 @@ // Copyright 2018 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_7 //nolint +package v1_7 import "xorm.io/xorm" diff --git a/models/migrations/v1_7/v75.go b/models/migrations/v1_7/v75.go index fa7430970c..ef11575466 100644 --- a/models/migrations/v1_7/v75.go +++ b/models/migrations/v1_7/v75.go @@ -1,7 +1,7 @@ // Copyright 2018 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_7 //nolint +package v1_7 import ( "xorm.io/builder" diff --git a/models/migrations/v1_8/v76.go b/models/migrations/v1_8/v76.go index 61ad006a47..8d47280b41 100644 --- a/models/migrations/v1_8/v76.go +++ b/models/migrations/v1_8/v76.go @@ -1,7 +1,7 @@ // Copyright 2018 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_8 //nolint +package v1_8 import ( "fmt" diff --git a/models/migrations/v1_8/v77.go b/models/migrations/v1_8/v77.go index 8b19993924..4fe5ebe635 100644 --- a/models/migrations/v1_8/v77.go +++ b/models/migrations/v1_8/v77.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_8 //nolint +package v1_8 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_8/v78.go b/models/migrations/v1_8/v78.go index 8102b19335..840fc20d96 100644 --- a/models/migrations/v1_8/v78.go +++ b/models/migrations/v1_8/v78.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_8 //nolint +package v1_8 import ( "forgejo.org/models/migrations/base" diff --git a/models/migrations/v1_8/v79.go b/models/migrations/v1_8/v79.go index f7d2d68f96..c8e0db531f 100644 --- a/models/migrations/v1_8/v79.go +++ b/models/migrations/v1_8/v79.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_8 //nolint +package v1_8 import ( "forgejo.org/modules/setting" diff --git a/models/migrations/v1_8/v80.go b/models/migrations/v1_8/v80.go index cebbbead28..6f9df47a93 100644 --- a/models/migrations/v1_8/v80.go +++ b/models/migrations/v1_8/v80.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_8 //nolint +package v1_8 import "xorm.io/xorm" diff --git a/models/migrations/v1_8/v81.go b/models/migrations/v1_8/v81.go index 734fc24641..8152a47ad7 100644 --- a/models/migrations/v1_8/v81.go +++ b/models/migrations/v1_8/v81.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_8 //nolint +package v1_8 import ( "fmt" diff --git a/models/migrations/v1_9/v82.go b/models/migrations/v1_9/v82.go index 78a90bdde9..235c73c504 100644 --- a/models/migrations/v1_9/v82.go +++ b/models/migrations/v1_9/v82.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_9 //nolint +package v1_9 import ( "fmt" diff --git a/models/migrations/v1_9/v83.go b/models/migrations/v1_9/v83.go index fa24a92d28..9640564a44 100644 --- a/models/migrations/v1_9/v83.go +++ b/models/migrations/v1_9/v83.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_9 //nolint +package v1_9 import ( "forgejo.org/modules/timeutil" diff --git a/models/migrations/v1_9/v84.go b/models/migrations/v1_9/v84.go index c7155fe9cf..423915ae57 100644 --- a/models/migrations/v1_9/v84.go +++ b/models/migrations/v1_9/v84.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_9 //nolint +package v1_9 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_9/v85.go b/models/migrations/v1_9/v85.go index d8e9d91840..9d5adc82dd 100644 --- a/models/migrations/v1_9/v85.go +++ b/models/migrations/v1_9/v85.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_9 //nolint +package v1_9 import ( "fmt" diff --git a/models/migrations/v1_9/v86.go b/models/migrations/v1_9/v86.go index cf2725d158..9464ff0cf6 100644 --- a/models/migrations/v1_9/v86.go +++ b/models/migrations/v1_9/v86.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_9 //nolint +package v1_9 import ( "xorm.io/xorm" diff --git a/models/migrations/v1_9/v87.go b/models/migrations/v1_9/v87.go index fa01b6e5e3..81a4ebf80d 100644 --- a/models/migrations/v1_9/v87.go +++ b/models/migrations/v1_9/v87.go @@ -1,7 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_9 //nolint +package v1_9 import ( "xorm.io/xorm" diff --git a/models/moderation/abuse_report.go b/models/moderation/abuse_report.go index 3a6244ef4c..152b81bb51 100644 --- a/models/moderation/abuse_report.go +++ b/models/moderation/abuse_report.go @@ -8,6 +8,7 @@ import ( "database/sql" "errors" "slices" + "time" "forgejo.org/models/db" "forgejo.org/modules/log" @@ -47,14 +48,22 @@ const ( AbuseCategoryTypeIllegalContent // 4 ) +// llu:TrKeys +var AbuseCategoriesTranslationKeys = map[AbuseCategoryType]string{ + AbuseCategoryTypeSpam: "moderation.abuse_category.spam", + AbuseCategoryTypeMalware: "moderation.abuse_category.malware", + AbuseCategoryTypeIllegalContent: "moderation.abuse_category.illegal_content", + AbuseCategoryTypeOther: "moderation.abuse_category.other_violations", +} + // GetAbuseCategoriesList returns a list of pairs with the available abuse category types // and their corresponding translation keys func GetAbuseCategoriesList() []AbuseCategoryItem { return []AbuseCategoryItem{ - {AbuseCategoryTypeSpam, "moderation.abuse_category.spam"}, - {AbuseCategoryTypeMalware, "moderation.abuse_category.malware"}, - {AbuseCategoryTypeIllegalContent, "moderation.abuse_category.illegal_content"}, - {AbuseCategoryTypeOther, "moderation.abuse_category.other_violations"}, + {AbuseCategoryTypeSpam, AbuseCategoriesTranslationKeys[AbuseCategoryTypeSpam]}, + {AbuseCategoryTypeMalware, AbuseCategoriesTranslationKeys[AbuseCategoryTypeMalware]}, + {AbuseCategoryTypeIllegalContent, AbuseCategoriesTranslationKeys[AbuseCategoryTypeIllegalContent]}, + {AbuseCategoryTypeOther, AbuseCategoriesTranslationKeys[AbuseCategoryTypeOther]}, } } @@ -104,6 +113,7 @@ type AbuseReport struct { // The ID of the corresponding shadow-copied content when exists; otherwise null. ShadowCopyID sql.NullInt64 `xorm:"DEFAULT NULL"` CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"` + ResolvedUnix timeutil.TimeStamp `xorm:"DEFAULT NULL"` } var ErrSelfReporting = errors.New("reporting yourself is not allowed") @@ -154,6 +164,25 @@ func ReportAbuse(ctx context.Context, report *AbuseReport) error { return err } +// GetResolvedReports gets all resolved reports +func GetResolvedReports(ctx context.Context, keepReportsFor time.Duration) ([]*AbuseReport, error) { + cond := builder.And( + builder.Or( + builder.Eq{"`status`": ReportStatusTypeHandled}, + builder.Eq{"`status`": ReportStatusTypeIgnored}, + ), + ) + + if keepReportsFor > 0 { + cond = cond.And(builder.Lt{"resolved_unix": time.Now().Add(-keepReportsFor).Unix()}) + } + + abuseReports := make([]*AbuseReport, 0, 30) + return abuseReports, db.GetEngine(ctx). + Where(cond). + Find(&abuseReports) +} + /* // MarkAsHandled will change the status to 'Handled' for all reports linked to the same item (user, repository, issue or comment). func MarkAsHandled(ctx context.Context, contentType ReportedContentType, contentID int64) error { diff --git a/models/moderation/abuse_report_detailed.go b/models/moderation/abuse_report_detailed.go new file mode 100644 index 0000000000..265d143709 --- /dev/null +++ b/models/moderation/abuse_report_detailed.go @@ -0,0 +1,135 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package moderation + +import ( + "context" + "fmt" + "strings" + + "forgejo.org/models/db" + "forgejo.org/modules/setting" + "forgejo.org/modules/timeutil" + + "xorm.io/builder" +) + +type AbuseReportDetailed struct { + AbuseReport `xorm:"extends"` + ReportedTimes int // only for overview + ReporterName string + ContentReference string + ShadowCopyDate timeutil.TimeStamp // only for details + ShadowCopyRawValue string // only for details +} + +func (ard AbuseReportDetailed) ContentTypeIconName() string { + switch ard.ContentType { + case ReportedContentTypeUser: + return "octicon-person" + case ReportedContentTypeRepository: + return "octicon-repo" + case ReportedContentTypeIssue: + return "octicon-issue-opened" + case ReportedContentTypeComment: + return "octicon-comment" + default: + return "octicon-question" + } +} + +func (ard AbuseReportDetailed) ContentURL() string { + switch ard.ContentType { + case ReportedContentTypeUser: + return strings.TrimLeft(ard.ContentReference, "@") + case ReportedContentTypeIssue: + return strings.ReplaceAll(ard.ContentReference, "#", "/issues/") + default: + return ard.ContentReference + } +} + +func GetOpenReports(ctx context.Context) ([]*AbuseReportDetailed, error) { + var reports []*AbuseReportDetailed + + // - For PostgreSQL user table name should be escaped. + // - Escaping can be done with double quotes (") but this doesn't work for MariaDB. + // - For SQLite index column name should be escaped. + // - Escaping can be done with double quotes (") or backticks (`). + // - For MariaDB/MySQL there is no need to escape the above. + // - Therefore we will use double quotes (") but only for PostgreSQL and SQLite. + identifierEscapeChar := `` + if setting.Database.Type.IsPostgreSQL() || setting.Database.Type.IsSQLite3() { + identifierEscapeChar = `"` + } + + err := db.GetEngine(ctx).SQL(fmt.Sprintf(`SELECT AR.*, ARD.reported_times, U.name AS reporter_name, REFS.ref AS content_reference + FROM abuse_report AR + INNER JOIN ( + SELECT min(id) AS id, count(id) AS reported_times + FROM abuse_report + WHERE status = %[2]d + GROUP BY content_type, content_id + ) ARD ON ARD.id = AR.id + LEFT JOIN %[1]suser%[1]s U ON U.id = AR.reporter_id + LEFT JOIN ( + SELECT %[3]d AS type, id, concat('@', name) AS "ref" + FROM %[1]suser%[1]s WHERE id IN ( + SELECT content_id FROM abuse_report WHERE status = %[2]d AND content_type = %[3]d + ) + UNION + SELECT %[4]d AS "type", id, concat(owner_name, '/', name) AS "ref" + FROM repository WHERE id IN ( + SELECT content_id FROM abuse_report WHERE status = %[2]d AND content_type = %[4]d + ) + UNION + SELECT %[5]d AS "type", I.id, concat(IR.owner_name, '/', IR.name, '#', I.%[1]sindex%[1]s) AS "ref" + FROM issue I + LEFT JOIN repository IR ON IR.id = I.repo_id + WHERE I.id IN ( + SELECT content_id FROM abuse_report WHERE status = %[2]d AND content_type = %[5]d + ) + UNION + SELECT %[6]d AS "type", C.id, concat(CIR.owner_name, '/', CIR.name, '/issues/', CI.%[1]sindex%[1]s, '#issuecomment-', C.id) AS "ref" + FROM comment C + LEFT JOIN issue CI ON CI.id = C.issue_id + LEFT JOIN repository CIR ON CIR.id = CI.repo_id + WHERE C.id IN ( + SELECT content_id FROM abuse_report WHERE status = %[2]d AND content_type = %[6]d + ) + ) REFS ON REFS.type = AR.content_type AND REFS.id = AR.content_id + ORDER BY AR.created_unix ASC`, identifierEscapeChar, ReportStatusTypeOpen, + ReportedContentTypeUser, ReportedContentTypeRepository, ReportedContentTypeIssue, ReportedContentTypeComment)). + Find(&reports) + if err != nil { + return nil, err + } + return reports, nil +} + +func GetOpenReportsByTypeAndContentID(ctx context.Context, contentType ReportedContentType, contentID int64) ([]*AbuseReportDetailed, error) { + var reports []*AbuseReportDetailed + + // Some remarks concerning PostgreSQL: + // - user table should be escaped (e.g. `user`); + // - tried to use aliases for table names but errors like 'invalid reference to FROM-clause entry' + // or 'missing FROM-clause entry' were returned; + err := db.GetEngine(ctx). + Select("abuse_report.*, `user`.name AS reporter_name, abuse_report_shadow_copy.created_unix AS shadow_copy_date, abuse_report_shadow_copy.raw_value AS shadow_copy_raw_value"). + Table("abuse_report"). + Join("LEFT", "user", "`user`.id = abuse_report.reporter_id"). + Join("LEFT", "abuse_report_shadow_copy", "abuse_report_shadow_copy.id = abuse_report.shadow_copy_id"). + Where(builder.Eq{ + "content_type": contentType, + "content_id": contentID, + "status": ReportStatusTypeOpen, + }). + Asc("abuse_report.created_unix"). + Find(&reports) + if err != nil { + return nil, err + } + + return reports, nil +} diff --git a/models/moderation/shadow_copy.go b/models/moderation/shadow_copy.go index d363610a48..8abb32e8ec 100644 --- a/models/moderation/shadow_copy.go +++ b/models/moderation/shadow_copy.go @@ -26,6 +26,22 @@ func (sc AbuseReportShadowCopy) NullableID() sql.NullInt64 { return sql.NullInt64{Int64: sc.ID, Valid: sc.ID > 0} } +// ShadowCopyField defines a pair of a value stored within the shadow copy +// (of some content reported as abusive) and a corresponding key (caption). +// A list of such pairs is used when rendering shadow copies for admins reviewing abuse reports. +type ShadowCopyField struct { + Key string + Value string +} + +// ShadowCopyData interface should be implemented by the type structs used for marshaling/unmarshaling the fields +// preserved as shadow copies for abusive content reports (i.e. UserData, RepositoryData, IssueData, CommentData). +type ShadowCopyData interface { + // GetFieldsMap returns a list of pairs with the fields stored within shadow copies + // of content reported as abusive, to be used when rendering a shadow copy in the admin UI. + GetFieldsMap() []ShadowCopyField +} + func init() { // RegisterModel will create the table if does not already exist // or any missing columns if the table was previously created. diff --git a/models/organization/org.go b/models/organization/org.go index ff95261051..c4df5d4fe1 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -186,6 +186,11 @@ func (org *Organization) CanCreateRepo() bool { return org.AsUser().CanCreateRepo() } +// IsGhost returns if the organization is a ghost +func (org *Organization) IsGhost() bool { + return org.AsUser().IsGhost() +} + // FindOrgMembersOpts represensts find org members conditions type FindOrgMembersOpts struct { db.ListOptions diff --git a/models/organization/org_list.go b/models/organization/org_list.go index e387936473..371993cdee 100644 --- a/models/organization/org_list.go +++ b/models/organization/org_list.go @@ -71,11 +71,8 @@ func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organiz Find(&orgs) } -// MinimalOrg represents a simple organization with only the needed columns -type MinimalOrg = Organization - // GetUserOrgsList returns all organizations the given user has access to -func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, error) { +func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*Organization, error) { schema, err := db.TableInfo(new(user_model.User)) if err != nil { return nil, err @@ -100,7 +97,7 @@ func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, } columnsStr := selectColumns.String() - var orgs []*MinimalOrg + var orgs []*Organization if err := db.GetEngine(ctx).Select(columnsStr). Table("user"). Where(builder.In("`user`.`id`", queryUserOrgIDs(user.ID, true))). @@ -138,6 +135,7 @@ func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, for _, org := range orgs { org.NumRepos = orgCountMap[org.ID] + org.Type = user_model.UserTypeOrganization } return orgs, nil diff --git a/models/organization/org_list_test.go b/models/organization/org_list_test.go index 170e2bf131..6e8c0bac26 100644 --- a/models/organization/org_list_test.go +++ b/models/organization/org_list_test.go @@ -85,11 +85,11 @@ func TestGetUserOrgsList(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4}) require.NoError(t, err) - if assert.Len(t, orgs, 1) { - assert.EqualValues(t, 3, orgs[0].ID) - // repo_id: 3 is in the team, 32 is public, 5 is private with no team - assert.Equal(t, 2, orgs[0].NumRepos) - } + assert.Len(t, orgs, 1) + assert.EqualValues(t, 3, orgs[0].ID) + // repo_id: 3 is in the team, 32 is public, 5 is private with no team + assert.Equal(t, 2, orgs[0].NumRepos) + assert.Equal(t, user_model.UserTypeOrganization, orgs[0].Type) } func TestGetUserOrgsListSorting(t *testing.T) { @@ -97,7 +97,7 @@ func TestGetUserOrgsListSorting(t *testing.T) { orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 1}) require.NoError(t, err) - isSorted := slices.IsSortedFunc(orgs, func(a, b *organization.MinimalOrg) int { + isSorted := slices.IsSortedFunc(orgs, func(a, b *organization.Organization) int { return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name)) }) diff --git a/models/organization/team.go b/models/organization/team.go index c78eff39fb..209471e013 100644 --- a/models/organization/team.go +++ b/models/organization/team.go @@ -1,5 +1,6 @@ -// Copyright 2018 The Gitea Authors. All rights reserved. // Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2018 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package organization @@ -7,6 +8,7 @@ package organization import ( "context" "fmt" + "net/url" "strings" "forgejo.org/models/db" @@ -20,13 +22,6 @@ import ( "xorm.io/builder" ) -// ___________ -// \__ ___/___ _____ _____ -// | |_/ __ \\__ \ / \ -// | |\ ___/ / __ \| Y Y \ -// |____| \___ >____ /__|_| / -// \/ \/ \/ - // ErrTeamAlreadyExist represents a "TeamAlreadyExist" kind of error. type ErrTeamAlreadyExist struct { OrgID int64 @@ -193,6 +188,27 @@ func (t *Team) UnitAccessMode(ctx context.Context, tp unit.Type) perm.AccessMode return perm.AccessModeNone } +// GetOrg returns the team's organization +func (t *Team) GetOrg(ctx context.Context) *Organization { + org, err := GetOrgByID(ctx, t.OrgID) + if err != nil { + return OrgFromUser(user_model.NewGhostUser()) + } + return org +} + +// Link returns the team's page link +func (t *Team) Link(ctx context.Context) string { + if t.IsGhost() { + return "" + } + org := t.GetOrg(ctx) + if org.IsGhost() { + return "" + } + return org.OrganisationLink() + "/teams/" + url.PathEscape(t.Name) +} + // IsUsableTeamName tests if a name could be as team name func IsUsableTeamName(name string) error { switch name { @@ -293,10 +309,22 @@ func FixInconsistentOwnerTeams(ctx context.Context) (int64, error) { return int64(len(teamIDs)), nil } +const ( + GhostTeamID = -1 + GhostTeamName = "Ghost team" + GhostTeamLowerName = "ghost team" +) + +// NewGhostTeam creates ghost team (for deleted team) func NewGhostTeam() *Team { return &Team{ - ID: -1, - Name: "Ghost team", - LowerName: "ghost team", + ID: GhostTeamID, + Name: GhostTeamName, + LowerName: GhostTeamLowerName, } } + +// IsGhost returns if a team is a ghost team +func (t *Team) IsGhost() bool { + return t.ID == GhostTeamID +} diff --git a/models/organization/team_test.go b/models/organization/team_test.go index 60c500e7ec..768ccdf5be 100644 --- a/models/organization/team_test.go +++ b/models/organization/team_test.go @@ -1,4 +1,5 @@ // Copyright 2017 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package organization_test @@ -15,14 +16,33 @@ import ( "github.com/stretchr/testify/require" ) -func TestTeam_IsOwnerTeam(t *testing.T) { +func TestTeam(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}) - assert.True(t, team.IsOwnerTeam()) + owners := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}) + assert.Equal(t, int64(3), owners.GetOrg(db.DefaultContext).ID) + assert.Equal(t, "/org/org3/teams/Owners", owners.Link(db.DefaultContext)) + assert.False(t, owners.IsGhost()) + assert.True(t, owners.IsOwnerTeam()) - team = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) - assert.False(t, team.IsOwnerTeam()) + team1 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) + assert.Equal(t, int64(3), team1.GetOrg(db.DefaultContext).ID) + assert.Equal(t, "/org/org3/teams/team1", team1.Link(db.DefaultContext)) + assert.False(t, team1.IsGhost()) + assert.False(t, team1.IsOwnerTeam()) + + ghost := organization.NewGhostTeam() + assert.Equal(t, int64(-1), ghost.ID) + assert.Equal(t, int64(-1), ghost.GetOrg(db.DefaultContext).ID) + assert.Empty(t, ghost.Link(db.DefaultContext)) + assert.True(t, ghost.IsGhost()) + assert.False(t, ghost.IsOwnerTeam()) + + ghosted := organization.Team{ID: 10, Name: "Ghosted"} + assert.Equal(t, int64(-1), ghosted.GetOrg(db.DefaultContext).ID) + assert.Empty(t, ghosted.Link(db.DefaultContext)) + assert.False(t, ghosted.IsGhost()) + assert.False(t, ghosted.IsOwnerTeam()) } func TestTeam_IsMember(t *testing.T) { diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index ce9963b83a..f7daf38e5c 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -7,6 +7,7 @@ import ( "context" "fmt" + actions_model "forgejo.org/models/actions" "forgejo.org/models/db" "forgejo.org/models/organization" perm_model "forgejo.org/models/perm" @@ -136,6 +137,33 @@ func (p *Permission) LogString() string { return fmt.Sprintf(format, args...) } +func GetActionRepoPermission(ctx context.Context, repo *repo_model.Repository, task *actions_model.ActionTask) (Permission, error) { + // straight forward case: an actions task is attempting to access its own repo + if task.RepoID == repo.ID { + var mode perm_model.AccessMode + + // determine default access mode for repo: + if task.IsForkPullRequest { + mode = perm_model.AccessModeRead + } else { + mode = perm_model.AccessModeWrite + } + + if err := repo.LoadUnits(ctx); err != nil { + return Permission{}, err + } + + perm := Permission{ + AccessMode: mode, + Units: repo.Units, + } + + return perm, nil + } + + return GetUserRepoPermission(ctx, repo, user_model.NewActionsUser()) +} + // GetUserRepoPermission returns the user permissions to the repository func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (Permission, error) { var perm Permission diff --git a/models/perm/access/repo_permission_test.go b/models/perm/access/repo_permission_test.go new file mode 100644 index 0000000000..55bc975421 --- /dev/null +++ b/models/perm/access/repo_permission_test.go @@ -0,0 +1,78 @@ +package access_test + +import ( + "testing" + + actions_model "forgejo.org/models/actions" + "forgejo.org/models/db" + perm_model "forgejo.org/models/perm" + "forgejo.org/models/perm/access" + repo_model "forgejo.org/models/repo" + "forgejo.org/models/unittest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func assertAccess(t *testing.T, expectedMode perm_model.AccessMode, perm *access.Permission) { + assert.Equal(t, expectedMode, perm.AccessMode) + + for _, unit := range perm.Units { + assert.Equal(t, expectedMode, perm.UnitAccessMode(unit.Type)) + } +} + +func TestActionTaskCanAccessOwnRepo(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 47}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: actionTask.RepoID}) + + perm, err := access.GetActionRepoPermission(db.DefaultContext, repo, actionTask) + require.NoError(t, err) + assertAccess(t, perm_model.AccessModeWrite, &perm) +} + +func TestActionTaskCanAccessPublicRepo(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 47}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + perm, err := access.GetActionRepoPermission(db.DefaultContext, repo, actionTask) + require.NoError(t, err) + assertAccess(t, perm_model.AccessModeRead, &perm) +} + +func TestActionTaskCanAccessPublicRepoOfLimitedOrg(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 47}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 38}) + + perm, err := access.GetActionRepoPermission(db.DefaultContext, repo, actionTask) + require.NoError(t, err) + assertAccess(t, perm_model.AccessModeRead, &perm) +} + +func TestActionTaskNoAccessPublicRepoOfPrivateOrg(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 47}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 40}) + + perm, err := access.GetActionRepoPermission(db.DefaultContext, repo, actionTask) + require.NoError(t, err) + assertAccess(t, perm_model.AccessModeNone, &perm) +} + +func TestActionTaskNoAccessPrivateRepo(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 47}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + + perm, err := access.GetActionRepoPermission(db.DefaultContext, repo, actionTask) + require.NoError(t, err) + assertAccess(t, perm_model.AccessModeNone, &perm) +} diff --git a/models/project/project.go b/models/project/project.go index b9813fda91..18c647c8ac 100644 --- a/models/project/project.go +++ b/models/project/project.go @@ -182,6 +182,8 @@ func init() { } // GetCardConfig retrieves the types of configurations project column cards could have +// +//llu:returnsTrKey func GetCardConfig() []CardConfig { return []CardConfig{ {CardTypeTextOnly, "repo.projects.card_type.text_only"}, diff --git a/models/project/template.go b/models/project/template.go index 06d5d2af14..278cf5b781 100644 --- a/models/project/template.go +++ b/models/project/template.go @@ -26,6 +26,8 @@ const ( ) // GetTemplateConfigs retrieves the template configs of configurations project columns could have +// +//llu:returnsTrKey func GetTemplateConfigs() []TemplateConfig { return []TemplateConfig{ {TemplateTypeNone, "repo.projects.type.none"}, diff --git a/models/quota/default.go b/models/quota/default.go index 9f655d7847..37b23739ad 100644 --- a/models/quota/default.go +++ b/models/quota/default.go @@ -7,7 +7,7 @@ import ( "forgejo.org/modules/setting" ) -func EvaluateDefault(used Used, forSubject LimitSubject) (bool, int64) { +func EvaluateDefault(used Used, forSubject LimitSubject) bool { groups := GroupList{ &Group{ Name: "builtin-default-group", diff --git a/models/quota/group.go b/models/quota/group.go index 7ddc20b2d6..a4ec8d0e14 100644 --- a/models/quota/group.go +++ b/models/quota/group.go @@ -5,7 +5,6 @@ package quota import ( "context" - "math" "forgejo.org/models/db" user_model "forgejo.org/models/user" @@ -179,78 +178,40 @@ func (g *Group) RemoveRuleByName(ctx context.Context, ruleName string) error { return committer.Commit() } -var affectsMap = map[LimitSubject]LimitSubjects{ - LimitSubjectSizeAll: { - LimitSubjectSizeReposAll, - LimitSubjectSizeGitLFS, - LimitSubjectSizeAssetsAll, - }, - LimitSubjectSizeReposAll: { - LimitSubjectSizeReposPublic, - LimitSubjectSizeReposPrivate, - }, - LimitSubjectSizeAssetsAll: { - LimitSubjectSizeAssetsAttachmentsAll, - LimitSubjectSizeAssetsArtifacts, - LimitSubjectSizeAssetsPackagesAll, - }, - LimitSubjectSizeAssetsAttachmentsAll: { - LimitSubjectSizeAssetsAttachmentsIssues, - LimitSubjectSizeAssetsAttachmentsReleases, - }, -} - -// Evaluate returns whether the size used is acceptable for the topic if a rule -// was found, and returns the smallest limit of all applicable rules or the -// first limit found to be unacceptable for the size used. -func (g *Group) Evaluate(used Used, forSubject LimitSubject) (bool, bool, int64) { - var found bool - foundLimit := int64(math.MaxInt64) +// Group.Evaluate returns whether the group contains a matching rule for the subject +// and if so, whether the group allows the action given the size used +func (g *Group) Evaluate(used Used, forSubject LimitSubject) (match, allow bool) { for _, rule := range g.Rules { - ok, has := rule.Evaluate(used, forSubject) - if has { - if !ok { - return false, true, rule.Limit + ruleMatch, ruleAllow := rule.Evaluate(used, forSubject) + if ruleMatch { + // evaluation stops as soon as we find a matching rule that denies the action + if !ruleAllow { + return true, false } - found = true - foundLimit = min(foundLimit, rule.Limit) + + match = true + allow = true } } - if !found { - // If Evaluation for forSubject did not succeed, try evaluating against - // subjects below - - for _, subject := range affectsMap[forSubject] { - ok, has, limit := g.Evaluate(used, subject) - if has { - if !ok { - return false, true, limit - } - found = true - foundLimit = min(foundLimit, limit) - } - } - } - - return true, found, foundLimit + return match, allow } -// Evaluate returns if the used size is acceptable for the subject and the -// lowest limit that is acceptable for the subject. -func (gl *GroupList) Evaluate(used Used, forSubject LimitSubject) (bool, int64) { +// GroupList.Evaluate returns whether the grouplist allows the action given the size used +func (gl *GroupList) Evaluate(used Used, forSubject LimitSubject) (pass bool) { // If there are no groups, use the configured defaults: if gl == nil || len(*gl) == 0 { return EvaluateDefault(used, forSubject) } for _, group := range *gl { - ok, has, limit := group.Evaluate(used, forSubject) - if has && ok { - return true, limit + groupMatch, groupAllow := group.Evaluate(used, forSubject) + if groupMatch && groupAllow { + // evaluation stops as soon as we find a matching group that allows the action + return true } } - return false, 0 + return false } func GetGroupByName(ctx context.Context, name string) (*Group, error) { diff --git a/models/quota/quota.go b/models/quota/quota.go index 9f1c3ca949..9869e9acab 100644 --- a/models/quota/quota.go +++ b/models/quota/quota.go @@ -32,6 +32,6 @@ func EvaluateForUser(ctx context.Context, userID int64, subject LimitSubject) (b return false, err } - acceptable, _ := groups.Evaluate(*used, subject) - return acceptable, nil + allow := groups.Evaluate(*used, subject) + return allow, nil } diff --git a/models/quota/quota_group_test.go b/models/quota/quota_group_test.go index 7f693b391b..7085682bfe 100644 --- a/models/quota/quota_group_test.go +++ b/models/quota/quota_group_test.go @@ -4,15 +4,16 @@ package quota_test import ( - "math" "testing" quota_model "forgejo.org/models/quota" + "forgejo.org/modules/setting" + "forgejo.org/modules/test" "github.com/stretchr/testify/assert" ) -func TestQuotaGroupAllRulesMustPass(t *testing.T) { +func TestQuotaGroupAllRulesMustAllow(t *testing.T) { unlimitedRule := quota_model.Rule{ Limit: -1, Subjects: quota_model.LimitSubjects{ @@ -35,12 +36,11 @@ func TestQuotaGroupAllRulesMustPass(t *testing.T) { used := quota_model.Used{} used.Size.Repos.Public = 1024 - // Within a group, *all* rules must pass. Thus, if we have a deny-all rule, - // and an unlimited rule, that will always fail. - ok, has, limit := group.Evaluate(used, quota_model.LimitSubjectSizeAll) - assert.True(t, has) - assert.False(t, ok) - assert.EqualValues(t, 0, limit) + // Within a group, *all* matching rules must allow. Thus, if we have a deny-all rule, + // and an unlimited rule, the deny rule wins. + match, allow := group.Evaluate(used, quota_model.LimitSubjectSizeAll) + assert.True(t, match) + assert.False(t, allow) } func TestQuotaGroupRuleScenario1(t *testing.T) { @@ -68,25 +68,21 @@ func TestQuotaGroupRuleScenario1(t *testing.T) { used.Size.Assets.Packages.All = 256 used.Size.Git.LFS = 16 - ok, has, limit := group.Evaluate(used, quota_model.LimitSubjectSizeAssetsAttachmentsReleases) - assert.True(t, has, "size:assets:attachments:releases is covered") - assert.True(t, ok, "size:assets:attachments:releases passes") - assert.EqualValues(t, 1024, limit) + match, allow := group.Evaluate(used, quota_model.LimitSubjectSizeAssetsAttachmentsReleases) + assert.True(t, match, "size:assets:attachments:releases is covered") + assert.True(t, allow, "size:assets:attachments:releases is allowed") - ok, has, limit = group.Evaluate(used, quota_model.LimitSubjectSizeAssetsPackagesAll) - assert.True(t, has, "size:assets:packages:all is covered") - assert.True(t, ok, "size:assets:packages:all passes") - assert.EqualValues(t, 1024, limit) + match, allow = group.Evaluate(used, quota_model.LimitSubjectSizeAssetsPackagesAll) + assert.True(t, match, "size:assets:packages:all is covered") + assert.True(t, allow, "size:assets:packages:all is allowed") - ok, has, limit = group.Evaluate(used, quota_model.LimitSubjectSizeGitLFS) - assert.True(t, has, "size:git:lfs is covered") - assert.False(t, ok, "size:git:lfs fails") - assert.EqualValues(t, 0, limit) + match, allow = group.Evaluate(used, quota_model.LimitSubjectSizeGitLFS) + assert.True(t, match, "size:git:lfs is covered") + assert.False(t, allow, "size:git:lfs is denied") - ok, has, limit = group.Evaluate(used, quota_model.LimitSubjectSizeAll) - assert.True(t, has, "size:all is covered") - assert.False(t, ok, "size:all fails") - assert.EqualValues(t, 0, limit) + match, allow = group.Evaluate(used, quota_model.LimitSubjectSizeAll) + assert.False(t, match, "size:all is not covered") + assert.False(t, allow, "size:all is not allowed (not covered)") } func TestQuotaGroupRuleCombination(t *testing.T) { @@ -114,31 +110,23 @@ func TestQuotaGroupRuleCombination(t *testing.T) { }, } - // Git LFS isn't covered by any rule - _, has, limit := group.Evaluate(used, quota_model.LimitSubjectSizeGitLFS) - assert.False(t, has) - assert.EqualValues(t, math.MaxInt, limit) + // Git LFS does not match any rule + match, allow := group.Evaluate(used, quota_model.LimitSubjectSizeGitLFS) + assert.False(t, match) + assert.False(t, allow) - // repos:all is covered, and is passing - ok, has, limit := group.Evaluate(used, quota_model.LimitSubjectSizeReposAll) - assert.True(t, has) - assert.True(t, ok) - assert.EqualValues(t, 4096, limit) + // repos:all has a matching rule and is allowed + match, allow = group.Evaluate(used, quota_model.LimitSubjectSizeReposAll) + assert.True(t, match) + assert.True(t, allow) - // packages:all is covered, and is failing - ok, has, limit = group.Evaluate(used, quota_model.LimitSubjectSizeAssetsPackagesAll) - assert.True(t, has) - assert.False(t, ok) - assert.EqualValues(t, 0, limit) - - // size:all is covered, and is failing (due to packages:all being over quota) - ok, has, limit = group.Evaluate(used, quota_model.LimitSubjectSizeAll) - assert.True(t, has, "size:all should be covered") - assert.False(t, ok, "size:all should fail") - assert.EqualValues(t, 0, limit) + // packages:all has a matching rule and is denied + match, allow = group.Evaluate(used, quota_model.LimitSubjectSizeAssetsPackagesAll) + assert.True(t, match) + assert.False(t, allow) } -func TestQuotaGroupListsRequireOnlyOnePassing(t *testing.T) { +func TestQuotaGroupListsRequireOnlyOneAllow(t *testing.T) { unlimitedRule := quota_model.Rule{ Limit: -1, Subjects: quota_model.LimitSubjects{ @@ -168,13 +156,12 @@ func TestQuotaGroupListsRequireOnlyOnePassing(t *testing.T) { used := quota_model.Used{} used.Size.Repos.Public = 1024 - // In a group list, if any group passes, the entire evaluation passes. - ok, limit := groups.Evaluate(used, quota_model.LimitSubjectSizeAll) - assert.True(t, ok) - assert.EqualValues(t, -1, limit) + // In a group list, an action is allowed if any group matches and allows it. + allow := groups.Evaluate(used, quota_model.LimitSubjectSizeAll) + assert.True(t, allow) } -func TestQuotaGroupListAllFailing(t *testing.T) { +func TestQuotaGroupListAllDeny(t *testing.T) { denyRule := quota_model.Rule{ Limit: 0, Subjects: quota_model.LimitSubjects{ @@ -204,18 +191,38 @@ func TestQuotaGroupListAllFailing(t *testing.T) { used := quota_model.Used{} used.Size.Repos.Public = 2048 - ok, limit := groups.Evaluate(used, quota_model.LimitSubjectSizeAll) - assert.False(t, ok) - assert.EqualValues(t, 0, limit) + allow := groups.Evaluate(used, quota_model.LimitSubjectSizeAll) + assert.False(t, allow) } -func TestQuotaGroupListEmpty(t *testing.T) { +// An empty group list should result in the use of the built in Default +// group: size:all defaulting to unlimited +func TestQuotaDefaultGroup(t *testing.T) { groups := quota_model.GroupList{} used := quota_model.Used{} used.Size.Repos.Public = 2048 - ok, limit := groups.Evaluate(used, quota_model.LimitSubjectSizeAll) - assert.True(t, ok) - assert.EqualValues(t, -1, limit) + testSets := []struct { + name string + limit int64 + expectAllow bool + }{ + {"unlimited", -1, true}, + {"limit-allow", 1024 * 1024, true}, + {"limit-deny", 1024, false}, + } + + for _, testSet := range testSets { + t.Run(testSet.name, func(t *testing.T) { + defer test.MockVariableValue(&setting.Quota.Default.Total, testSet.limit)() + + for subject := quota_model.LimitSubjectFirst; subject <= quota_model.LimitSubjectLast; subject++ { + t.Run(subject.String(), func(t *testing.T) { + allow := groups.Evaluate(used, subject) + assert.Equal(t, testSet.expectAllow, allow) + }) + } + }) + } } diff --git a/models/quota/quota_rule_test.go b/models/quota/quota_rule_test.go index 59c05563f0..c4605fd58e 100644 --- a/models/quota/quota_rule_test.go +++ b/models/quota/quota_rule_test.go @@ -83,28 +83,43 @@ func assertEvaluation(t *testing.T, rule quota_model.Rule, used quota_model.Used t.Helper() t.Run(subject.String(), func(t *testing.T) { - ok, has := rule.Evaluate(used, subject) - assert.True(t, has) - assert.Equal(t, expected, ok) + match, allow := rule.Evaluate(used, subject) + assert.True(t, match) + assert.Equal(t, expected, allow) }) } -func TestQuotaRuleNoEvaluation(t *testing.T) { - rule := quota_model.Rule{ - Limit: 1024, - Subjects: quota_model.LimitSubjects{ - quota_model.LimitSubjectSizeAssetsAttachmentsAll, - }, +func TestQuotaRuleNoMatch(t *testing.T) { + testSets := []struct { + name string + limit int64 + }{ + {"unlimited", -1}, + {"limit-0", 0}, + {"limit-1k", 1024}, + {"limit-1M", 1024 * 1024}, } - used := quota_model.Used{} - used.Size.Repos.Public = 4096 - _, has := rule.Evaluate(used, quota_model.LimitSubjectSizeReposAll) + for _, testSet := range testSets { + t.Run(testSet.name, func(t *testing.T) { + rule := quota_model.Rule{ + Limit: testSet.limit, + Subjects: quota_model.LimitSubjects{ + quota_model.LimitSubjectSizeAssetsAttachmentsAll, + }, + } + used := quota_model.Used{} + used.Size.Repos.Public = 4096 - // We have a rule for "size:assets:attachments:all", and query for - // "size:repos:all". We don't cover that subject, so the evaluation returns - // with no rules found. - assert.False(t, has) + match, allow := rule.Evaluate(used, quota_model.LimitSubjectSizeReposAll) + + // We have a rule for "size:assets:attachments:all", and query for + // "size:repos:all". We don't cover that subject, so the rule does not match + // regardless of the limit. + assert.False(t, match) + assert.False(t, allow) + }) + } } func TestQuotaRuleDirectEvaluation(t *testing.T) { @@ -129,13 +144,12 @@ func TestQuotaRuleDirectEvaluation(t *testing.T) { } t.Run("limit:0", func(t *testing.T) { - // With limit:0, nothing used is fine. + // With limit:0, any usage will fail evaluation, including 0 t.Run("used:0", func(t *testing.T) { for subject := quota_model.LimitSubjectFirst; subject <= quota_model.LimitSubjectLast; subject++ { - runTest(t, subject, 0, 0, true) + runTest(t, subject, 0, 0, false) } }) - // With limit:0, any usage will fail evaluation t.Run("used:512", func(t *testing.T) { for subject := quota_model.LimitSubjectFirst; subject <= quota_model.LimitSubjectLast; subject++ { runTest(t, subject, 0, 512, false) @@ -170,14 +184,6 @@ func TestQuotaRuleDirectEvaluation(t *testing.T) { } func TestQuotaRuleCombined(t *testing.T) { - rule := quota_model.Rule{ - Limit: 1024, - Subjects: quota_model.LimitSubjects{ - quota_model.LimitSubjectSizeGitLFS, - quota_model.LimitSubjectSizeAssetsAttachmentsReleases, - quota_model.LimitSubjectSizeAssetsPackagesAll, - }, - } used := quota_model.Used{ Size: quota_model.UsedSize{ Repos: quota_model.UsedSizeRepos{ @@ -198,107 +204,112 @@ func TestQuotaRuleCombined(t *testing.T) { }, } - expectationMap := map[quota_model.LimitSubject]bool{ - quota_model.LimitSubjectSizeGitLFS: false, - quota_model.LimitSubjectSizeAssetsAttachmentsReleases: false, - quota_model.LimitSubjectSizeAssetsPackagesAll: false, + expectMatch := map[quota_model.LimitSubject]bool{ + quota_model.LimitSubjectSizeGitLFS: true, + quota_model.LimitSubjectSizeAssetsAttachmentsReleases: true, + quota_model.LimitSubjectSizeAssetsPackagesAll: true, } - for subject := quota_model.LimitSubjectFirst; subject <= quota_model.LimitSubjectLast; subject++ { - t.Run(subject.String(), func(t *testing.T) { - evalOk, evalHas := rule.Evaluate(used, subject) - expected, expectedHas := expectationMap[subject] + testSets := []struct { + name string + limit int64 + expectAllow bool + }{ + {"unlimited", -1, true}, + {"limit-allow", 1024 * 1024, true}, + {"limit-deny", 1024, false}, + } - assert.Equal(t, expectedHas, evalHas) - if expectedHas { - assert.Equal(t, expected, evalOk) + for _, testSet := range testSets { + t.Run(testSet.name, func(t *testing.T) { + rule := quota_model.Rule{ + Limit: testSet.limit, + Subjects: quota_model.LimitSubjects{ + quota_model.LimitSubjectSizeGitLFS, + quota_model.LimitSubjectSizeAssetsAttachmentsReleases, + quota_model.LimitSubjectSizeAssetsPackagesAll, + }, + } + + for subject := quota_model.LimitSubjectFirst; subject <= quota_model.LimitSubjectLast; subject++ { + t.Run(subject.String(), func(t *testing.T) { + match, allow := rule.Evaluate(used, subject) + + assert.Equal(t, expectMatch[subject], match) + if expectMatch[subject] { + assert.Equal(t, testSet.expectAllow, allow) + } else { + assert.False(t, allow) + } + }) } }) } } func TestQuotaRuleSizeAll(t *testing.T) { - runTests := func(t *testing.T, rule quota_model.Rule, expected bool) { - t.Helper() - - subject := quota_model.LimitSubjectSizeAll - - t.Run("used:0", func(t *testing.T) { - used := quota_model.Used{} - - assertEvaluation(t, rule, used, subject, true) - }) - - t.Run("used:some-each", func(t *testing.T) { - used := makeFullyUsed() - - assertEvaluation(t, rule, used, subject, expected) - }) - - t.Run("used:some", func(t *testing.T) { - used := makePartiallyUsed() - - assertEvaluation(t, rule, used, subject, expected) - }) + type Test struct { + name string + limit int64 + expectAllow bool } - // With all limits set to 0, evaluation always fails if usage > 0 - t.Run("rule:0", func(t *testing.T) { - rule := quota_model.Rule{ - Limit: 0, - Subjects: quota_model.LimitSubjects{ - quota_model.LimitSubjectSizeAll, + usedSets := []struct { + name string + used quota_model.Used + testSets []Test + }{ + { + "empty", + quota_model.Used{}, + []Test{ + {"unlimited", -1, true}, + {"limit-1M", 1024 * 1024, true}, + {"limit-5k", 5 * 1024, true}, + {"limit-0", 0, false}, }, - } - - runTests(t, rule, false) - }) - - // With no limits, evaluation always succeeds - t.Run("rule:unlimited", func(t *testing.T) { - rule := quota_model.Rule{ - Limit: -1, - Subjects: quota_model.LimitSubjects{ - quota_model.LimitSubjectSizeAll, + }, + { + "partial", + makePartiallyUsed(), + []Test{ + {"unlimited", -1, true}, + {"limit-1M", 1024 * 1024, true}, + {"limit-5k", 5 * 1024, true}, + {"limit-0", 0, false}, }, - } - - runTests(t, rule, true) - }) - - // With a specific, very generous limit, evaluation succeeds if the limit isn't exhausted - t.Run("rule:generous", func(t *testing.T) { - rule := quota_model.Rule{ - Limit: 102400, - Subjects: quota_model.LimitSubjects{ - quota_model.LimitSubjectSizeAll, + }, + { + "full", + makeFullyUsed(), + []Test{ + {"unlimited", -1, true}, + {"limit-1M", 1024 * 1024, true}, + {"limit-5k", 5 * 1024, false}, + {"limit-0", 0, false}, }, - } + }, + } - runTests(t, rule, true) + for _, usedSet := range usedSets { + t.Run(usedSet.name, func(t *testing.T) { + testSets := usedSet.testSets + used := usedSet.used - t.Run("limit exhaustion", func(t *testing.T) { - used := quota_model.Used{ - Size: quota_model.UsedSize{ - Repos: quota_model.UsedSizeRepos{ - Public: 204800, - }, - }, + for _, testSet := range testSets { + t.Run(testSet.name, func(t *testing.T) { + rule := quota_model.Rule{ + Limit: testSet.limit, + Subjects: quota_model.LimitSubjects{ + quota_model.LimitSubjectSizeAll, + }, + } + + match, allow := rule.Evaluate(used, quota_model.LimitSubjectSizeAll) + assert.True(t, match) + assert.Equal(t, testSet.expectAllow, allow) + }) } - - assertEvaluation(t, rule, used, quota_model.LimitSubjectSizeAll, false) }) - }) - - // With a specific, small limit, evaluation fails - t.Run("rule:limited", func(t *testing.T) { - rule := quota_model.Rule{ - Limit: 512, - Subjects: quota_model.LimitSubjects{ - quota_model.LimitSubjectSizeAll, - }, - } - - runTests(t, rule, false) - }) + } } diff --git a/models/quota/rule.go b/models/quota/rule.go index 89cb57cace..98959e0a91 100644 --- a/models/quota/rule.go +++ b/models/quota/rule.go @@ -16,6 +16,21 @@ type Rule struct { Subjects LimitSubjects `json:"subjects,omitempty"` } +var subjectToParent = map[LimitSubject]LimitSubject{ + LimitSubjectSizeGitAll: LimitSubjectSizeAll, + LimitSubjectSizeGitLFS: LimitSubjectSizeGitAll, + LimitSubjectSizeReposAll: LimitSubjectSizeGitAll, + LimitSubjectSizeReposPublic: LimitSubjectSizeReposAll, + LimitSubjectSizeReposPrivate: LimitSubjectSizeReposAll, + LimitSubjectSizeAssetsAll: LimitSubjectSizeAll, + LimitSubjectSizeAssetsAttachmentsAll: LimitSubjectSizeAssetsAll, + LimitSubjectSizeAssetsAttachmentsIssues: LimitSubjectSizeAssetsAttachmentsAll, + LimitSubjectSizeAssetsAttachmentsReleases: LimitSubjectSizeAssetsAttachmentsAll, + LimitSubjectSizeAssetsArtifacts: LimitSubjectSizeAssetsAll, + LimitSubjectSizeAssetsPackagesAll: LimitSubjectSizeAssetsAll, + LimitSubjectSizeWiki: LimitSubjectSizeAssetsAll, +} + func (r *Rule) TableName() string { return "quota_rule" } @@ -36,18 +51,25 @@ func (r Rule) Sum(used Used) int64 { return sum } -func (r Rule) Evaluate(used Used, forSubject LimitSubject) (bool, bool) { - // If there's no limit, short circuit out - if r.Limit == -1 { - return true, true - } - - // If the rule does not cover forSubject, bail out early +func (r Rule) Evaluate(used Used, forSubject LimitSubject) (match, allow bool) { if !slices.Contains(r.Subjects, forSubject) { + // this rule does not match the subject being tested + parent := subjectToParent[forSubject] + if parent != LimitSubjectNone { + return r.Evaluate(used, parent) + } return false, false } - return r.Sum(used) <= r.Limit, true + match = true + + if r.Limit == -1 { + // Unlimited, any value is allowed + allow = true + } else { + allow = r.Sum(used) < r.Limit + } + return match, allow } func (r *Rule) Edit(ctx context.Context, limit *int64, subjects *LimitSubjects) (*Rule, error) { diff --git a/models/quota/used.go b/models/quota/used.go index 22815165f6..0d8f62ab78 100644 --- a/models/quota/used.go +++ b/models/quota/used.go @@ -25,7 +25,7 @@ type UsedSize struct { } func (u UsedSize) All() int64 { - return u.Repos.All() + u.Git.All(u.Repos) + u.Assets.All() + return u.Git.All(u.Repos) + u.Assets.All() } type UsedSizeRepos struct { diff --git a/models/quota/used_test.go b/models/quota/used_test.go index 82cc5b9bcc..0fed83342b 100644 --- a/models/quota/used_test.go +++ b/models/quota/used_test.go @@ -1,23 +1,54 @@ // Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: GPL-3.0-or-later -package quota +package quota_test import ( "testing" + quota_model "forgejo.org/models/quota" "forgejo.org/models/unittest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestGetUsedForUser(t *testing.T) { +func TestQuotaUsedGetUsedForUser(t *testing.T) { defer unittest.OverrideFixtures("models/fixtures/TestGetUsedForUser/")() require.NoError(t, unittest.PrepareTestDatabase()) - used, err := GetUsedForUser(t.Context(), 5) + used, err := quota_model.GetUsedForUser(t.Context(), 5) require.NoError(t, err) assert.EqualValues(t, 4096, used.Size.Assets.Artifacts) } + +func TestQuotaUsedTotals(t *testing.T) { + used := quota_model.Used{ + Size: quota_model.UsedSize{ + Repos: quota_model.UsedSizeRepos{ + Public: 2, + Private: 3, + }, + Git: quota_model.UsedSizeGit{ + LFS: 7, + }, + Assets: quota_model.UsedSizeAssets{ + Attachments: quota_model.UsedSizeAssetsAttachments{ + Issues: 11, + Releases: 13, + }, + Artifacts: 17, + Packages: quota_model.UsedSizeAssetsPackages{ + All: 19, + }, + }, + }, + } + + assert.EqualValues(t, 5, used.Size.Repos.All()) // repos public + repos private + assert.EqualValues(t, 12, used.Size.Git.All(used.Size.Repos)) // repos all + git lfs + assert.EqualValues(t, 24, used.Size.Assets.Attachments.All()) // issues + releases + assert.EqualValues(t, 60, used.Size.Assets.All()) // attachments all + artifacts + packages + assert.EqualValues(t, 72, used.Size.All()) // git all + assets all +} diff --git a/models/repo/git.go b/models/repo/git.go index 692176c8f6..11f6452be5 100644 --- a/models/repo/git.go +++ b/models/repo/git.go @@ -29,6 +29,8 @@ const ( MergeStyleRebaseUpdate MergeStyle = "rebase-update-only" ) +var MergeStyles = []MergeStyle{MergeStyleMerge, MergeStyleRebase, MergeStyleRebaseMerge, MergeStyleSquash, MergeStyleFastForwardOnly, MergeStyleManuallyMerged, MergeStyleRebaseUpdate} + type UpdateStyle string const ( diff --git a/models/repo/moderation.go b/models/repo/moderation.go index d7b87dffa0..0d2672227b 100644 --- a/models/repo/moderation.go +++ b/models/repo/moderation.go @@ -5,6 +5,8 @@ package repo import ( "context" + "strconv" + "strings" "forgejo.org/models/moderation" "forgejo.org/modules/json" @@ -25,6 +27,22 @@ type RepositoryData struct { UpdatedUnix timeutil.TimeStamp } +// Implements GetFieldsMap() from ShadowCopyData interface, returning a list of pairs +// to be used when rendering the shadow copy for admins reviewing the corresponding abuse report(s). +func (rd RepositoryData) GetFieldsMap() []moderation.ShadowCopyField { + return []moderation.ShadowCopyField{ + {Key: "OwnerID", Value: strconv.FormatInt(rd.OwnerID, 10)}, + {Key: "OwnerName", Value: rd.OwnerName}, + {Key: "Name", Value: rd.Name}, + {Key: "Description", Value: rd.Description}, + {Key: "Website", Value: rd.Website}, + {Key: "Topics", Value: strings.Join(rd.Topics, ", ")}, + {Key: "Avatar", Value: rd.Avatar}, + {Key: "CreatedUnix", Value: rd.CreatedUnix.AsLocalTime().String()}, + {Key: "UpdatedUnix", Value: rd.UpdatedUnix.AsLocalTime().String()}, + } +} + // newRepositoryData creates a trimmed down repository to be used just to create a JSON structure // (keeping only the fields relevant for moderation purposes) func newRepositoryData(repo *Repository) RepositoryData { diff --git a/models/repo/moderation_test.go b/models/repo/moderation_test.go new file mode 100644 index 0000000000..9852db1b51 --- /dev/null +++ b/models/repo/moderation_test.go @@ -0,0 +1,51 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package repo_test + +import ( + "testing" + + "forgejo.org/models/moderation" + "forgejo.org/models/repo" + "forgejo.org/modules/timeutil" + + "github.com/stretchr/testify/assert" +) + +const ( + tsCreated timeutil.TimeStamp = timeutil.TimeStamp(1753093500) // 2025-07-21 10:25:00 UTC + tsUpdated timeutil.TimeStamp = timeutil.TimeStamp(1753093525) // 2025-07-21 10:25:25 UTC +) + +func testShadowCopyField(t *testing.T, scField moderation.ShadowCopyField, key, value string) { + assert.Equal(t, key, scField.Key) + assert.Equal(t, value, scField.Value) +} + +func TestRepositoryDataGetFieldsMap(t *testing.T) { + rd := repo.RepositoryData{ + OwnerID: 1002, + OwnerName: "alexsmith", + Name: "website", + Description: "My static website.", + Website: "http://promote-your-business.biz", + Topics: []string{"bulk-email", "email-services"}, + Avatar: "avatar-hash-repo-2002", + CreatedUnix: tsCreated, + UpdatedUnix: tsUpdated, + } + scFields := rd.GetFieldsMap() + + if assert.Len(t, scFields, 9) { + testShadowCopyField(t, scFields[0], "OwnerID", "1002") + testShadowCopyField(t, scFields[1], "OwnerName", "alexsmith") + testShadowCopyField(t, scFields[2], "Name", "website") + testShadowCopyField(t, scFields[3], "Description", "My static website.") + testShadowCopyField(t, scFields[4], "Website", "http://promote-your-business.biz") + testShadowCopyField(t, scFields[5], "Topics", "bulk-email, email-services") + testShadowCopyField(t, scFields[6], "Avatar", "avatar-hash-repo-2002") + testShadowCopyField(t, scFields[7], "CreatedUnix", tsCreated.AsLocalTime().String()) + testShadowCopyField(t, scFields[8], "UpdatedUnix", tsUpdated.AsLocalTime().String()) + } +} diff --git a/models/repo/pushmirror.go b/models/repo/pushmirror.go index d6d0d1135a..e57897fb7e 100644 --- a/models/repo/pushmirror.go +++ b/models/repo/pushmirror.go @@ -32,6 +32,7 @@ type PushMirror struct { Repo *Repository `xorm:"-"` RemoteName string RemoteAddress string `xorm:"VARCHAR(2048)"` + BranchFilter string `xorm:"VARCHAR(2048)"` // A keypair formatted in OpenSSH format. PublicKey string `xorm:"VARCHAR(100)"` @@ -122,6 +123,11 @@ func UpdatePushMirrorInterval(ctx context.Context, m *PushMirror) error { return err } +func UpdatePushMirrorBranchFilter(ctx context.Context, m *PushMirror) error { + _, err := db.GetEngine(ctx).ID(m.ID).Cols("branch_filter").Update(m) + return err +} + var DeletePushMirrors = deletePushMirrors func deletePushMirrors(ctx context.Context, opts PushMirrorOptions) error { diff --git a/models/repo/pushmirror_test.go b/models/repo/pushmirror_test.go index fbef835372..a7e063ff71 100644 --- a/models/repo/pushmirror_test.go +++ b/models/repo/pushmirror_test.go @@ -75,3 +75,139 @@ func TestPushMirrorPrivatekey(t *testing.T) { assert.Empty(t, actualPrivateKey) }) } + +func TestPushMirrorBranchFilter(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + t.Run("Create push mirror with branch filter", func(t *testing.T) { + m := &repo_model.PushMirror{ + RepoID: 1, + RemoteName: "test-branch-filter", + BranchFilter: "main,develop", + } + unittest.AssertSuccessfulInsert(t, m) + assert.NotZero(t, m.ID) + assert.Equal(t, "main,develop", m.BranchFilter) + }) + + t.Run("Create push mirror with empty branch filter", func(t *testing.T) { + m := &repo_model.PushMirror{ + RepoID: 1, + RemoteName: "test-empty-filter", + BranchFilter: "", + } + unittest.AssertSuccessfulInsert(t, m) + assert.NotZero(t, m.ID) + assert.Empty(t, m.BranchFilter) + }) + + t.Run("Create push mirror without branch filter", func(t *testing.T) { + m := &repo_model.PushMirror{ + RepoID: 1, + RemoteName: "test-no-filter", + // BranchFilter: "", + } + unittest.AssertSuccessfulInsert(t, m) + assert.NotZero(t, m.ID) + assert.Empty(t, m.BranchFilter) + }) + + t.Run("Update branch filter", func(t *testing.T) { + m := &repo_model.PushMirror{ + RepoID: 1, + RemoteName: "test-update", + BranchFilter: "main", + } + unittest.AssertSuccessfulInsert(t, m) + + m.BranchFilter = "main,develop" + require.NoError(t, repo_model.UpdatePushMirrorBranchFilter(db.DefaultContext, m)) + + updated := unittest.AssertExistsAndLoadBean(t, &repo_model.PushMirror{ID: m.ID}) + assert.Equal(t, "main,develop", updated.BranchFilter) + }) + + t.Run("Retrieve push mirror with branch filter", func(t *testing.T) { + original := &repo_model.PushMirror{ + RepoID: 1, + RemoteName: "test-retrieve", + BranchFilter: "main,develop", + } + unittest.AssertSuccessfulInsert(t, original) + + retrieved := unittest.AssertExistsAndLoadBean(t, &repo_model.PushMirror{ID: original.ID}) + assert.Equal(t, original.BranchFilter, retrieved.BranchFilter) + assert.Equal(t, "main,develop", retrieved.BranchFilter) + }) + + t.Run("GetPushMirrorsByRepoID includes branch filter", func(t *testing.T) { + mirrors := []*repo_model.PushMirror{ + { + RepoID: 2, + RemoteName: "mirror-1", + BranchFilter: "main", + }, + { + RepoID: 2, + RemoteName: "mirror-2", + BranchFilter: "develop,feature-*", + }, + { + RepoID: 2, + RemoteName: "mirror-3", + BranchFilter: "", + }, + } + + for _, mirror := range mirrors { + unittest.AssertSuccessfulInsert(t, mirror) + } + + retrieved, count, err := repo_model.GetPushMirrorsByRepoID(db.DefaultContext, 2, db.ListOptions{}) + require.NoError(t, err) + assert.Equal(t, int64(3), count) + assert.Len(t, retrieved, 3) + + filterMap := make(map[string]string) + for _, mirror := range retrieved { + filterMap[mirror.RemoteName] = mirror.BranchFilter + } + + assert.Equal(t, "main", filterMap["mirror-1"]) + assert.Equal(t, "develop,feature-*", filterMap["mirror-2"]) + assert.Empty(t, filterMap["mirror-3"]) + }) + + t.Run("GetPushMirrorsSyncedOnCommit includes branch filter", func(t *testing.T) { + mirrors := []*repo_model.PushMirror{ + { + RepoID: 3, + RemoteName: "sync-mirror-1", + BranchFilter: "main,develop", + SyncOnCommit: true, + }, + { + RepoID: 3, + RemoteName: "sync-mirror-2", + BranchFilter: "feature-*", + SyncOnCommit: true, + }, + } + + for _, mirror := range mirrors { + unittest.AssertSuccessfulInsert(t, mirror) + } + + retrieved, err := repo_model.GetPushMirrorsSyncedOnCommit(db.DefaultContext, 3) + require.NoError(t, err) + assert.Len(t, retrieved, 2) + + filterMap := make(map[string]string) + for _, mirror := range retrieved { + filterMap[mirror.RemoteName] = mirror.BranchFilter + } + + assert.Equal(t, "main,develop", filterMap["sync-mirror-1"]) + assert.Equal(t, "feature-*", filterMap["sync-mirror-2"]) + }) +} diff --git a/models/repo/redirect.go b/models/repo/redirect.go index 9c44a255d0..e5239a3684 100644 --- a/models/repo/redirect.go +++ b/models/repo/redirect.go @@ -14,8 +14,9 @@ import ( // ErrRedirectNotExist represents a "RedirectNotExist" kind of error. type ErrRedirectNotExist struct { - OwnerID int64 - RepoName string + OwnerID int64 + RepoName string + MissingPermission bool } // IsErrRedirectNotExist check if an error is an ErrRepoRedirectNotExist. @@ -49,8 +50,8 @@ func init() { db.RegisterModel(new(Redirect)) } -// LookupRedirect look up if a repository has a redirect name -func LookupRedirect(ctx context.Context, ownerID int64, repoName string) (int64, error) { +// GetRedirect returns the redirect for a given pair of ownerID and repository name. +func GetRedirect(ctx context.Context, ownerID int64, repoName string) (int64, error) { repoName = strings.ToLower(repoName) redirect := &Redirect{OwnerID: ownerID, LowerName: repoName} if has, err := db.GetEngine(ctx).Get(redirect); err != nil { diff --git a/models/repo/redirect_test.go b/models/repo/redirect_test.go index d84cbbed54..2f2210588f 100644 --- a/models/repo/redirect_test.go +++ b/models/repo/redirect_test.go @@ -10,21 +10,9 @@ import ( repo_model "forgejo.org/models/repo" "forgejo.org/models/unittest" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestLookupRedirect(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - - repoID, err := repo_model.LookupRedirect(db.DefaultContext, 2, "oldrepo1") - require.NoError(t, err) - assert.EqualValues(t, 1, repoID) - - _, err = repo_model.LookupRedirect(db.DefaultContext, unittest.NonexistentID, "doesnotexist") - assert.True(t, repo_model.IsErrRedirectNotExist(err)) -} - func TestNewRedirect(t *testing.T) { // redirect to a completely new name require.NoError(t, unittest.PrepareTestDatabase()) diff --git a/models/repo/release.go b/models/repo/release.go index 10e9bb259f..b39a1de971 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -77,7 +77,7 @@ type Release struct { Target string TargetBehind string `xorm:"-"` // to handle non-existing or empty target Title string - Sha1 string `xorm:"VARCHAR(64)"` + Sha1 string `xorm:"INDEX VARCHAR(64)"` HideArchiveLinks bool `xorm:"NOT NULL DEFAULT false"` NumCommits int64 NumCommitsBehind int64 `xorm:"-"` @@ -618,3 +618,17 @@ func InsertReleases(ctx context.Context, rels ...*Release) error { return committer.Commit() } + +func FindTagsByCommitIDs(ctx context.Context, repoID int64, commitIDs ...string) (map[string][]*Release, error) { + releases := make([]*Release, 0, len(commitIDs)) + if err := db.GetEngine(ctx).Where("repo_id=?", repoID). + In("sha1", commitIDs). + Find(&releases); err != nil { + return nil, err + } + res := make(map[string][]*Release, len(releases)) + for _, r := range releases { + res[r.Sha1] = append(res[r.Sha1], r) + } + return res, nil +} diff --git a/models/repo/release_test.go b/models/repo/release_test.go index 94dbd6d9d5..69f9333589 100644 --- a/models/repo/release_test.go +++ b/models/repo/release_test.go @@ -49,3 +49,16 @@ func TestReleaseDisplayName(t *testing.T) { release.Title = "Title" assert.Equal(t, "Title", release.DisplayName()) } + +func Test_FindTagsByCommitIDs(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + sha1Rels, err := FindTagsByCommitIDs(db.DefaultContext, 1, "65f1bf27bc3bf70f64657658635e66094edbcb4d") + require.NoError(t, err) + assert.Len(t, sha1Rels, 1) + rels := sha1Rels["65f1bf27bc3bf70f64657658635e66094edbcb4d"] + assert.Len(t, rels, 3) + assert.Equal(t, "v1.1", rels[0].TagName) + assert.Equal(t, "delete-tag", rels[1].TagName) + assert.Equal(t, "v1.0", rels[2].TagName) +} diff --git a/models/repo/repo_repository.go b/models/repo/repo_repository.go index 0ba50e6614..9d586b8345 100644 --- a/models/repo/repo_repository.go +++ b/models/repo/repo_repository.go @@ -38,18 +38,18 @@ func StoreFollowingRepos(ctx context.Context, localRepoID int64, followingRepoLi } // Begin transaction - ctx, committer, err := db.TxContext((ctx)) + dbCtx, committer, err := db.TxContext((ctx)) if err != nil { return err } defer committer.Close() - _, err = db.GetEngine(ctx).Where("repo_id=?", localRepoID).Delete(FollowingRepo{}) + _, err = db.GetEngine(dbCtx).Where("repo_id=?", localRepoID).Delete(FollowingRepo{}) if err != nil { return err } for _, followingRepo := range followingRepoList { - _, err = db.GetEngine(ctx).Insert(followingRepo) + _, err = db.GetEngine(dbCtx).Insert(followingRepo) if err != nil { return err } diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index e50f79e945..aa6f2fa0ae 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -336,5 +336,8 @@ func getUnitsByRepoID(ctx context.Context, repoID int64) (units []*RepoUnit, err // UpdateRepoUnit updates the provided repo unit func UpdateRepoUnit(ctx context.Context, unit *RepoUnit) error { _, err := db.GetEngine(ctx).ID(unit.ID).Update(unit) - return err + if err != nil { + return fmt.Errorf("UpdateRepoUnit: %v", err) + } + return nil } diff --git a/models/repo/topic.go b/models/repo/topic.go index 4a3bdc7d8c..9086f17627 100644 --- a/models/repo/topic.go +++ b/models/repo/topic.go @@ -164,7 +164,7 @@ func FindTopics(ctx context.Context, opts *FindTopicOptions) ([]*Topic, int64, e orderBy := "topic.repo_count DESC" if opts.RepoID > 0 { sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id") - orderBy = "topic.name" // when render topics for a repo, it's better to sort them by name, to get consistent result + orderBy = "topic.name" // When rendering topics for a repo, it's better to sort them by name to get consistent results } if opts.PageSize > 0 { sess = db.SetSessionPagination(sess, opts) diff --git a/models/secret/main_test.go b/models/secret/main_test.go new file mode 100644 index 0000000000..85bfec0c4f --- /dev/null +++ b/models/secret/main_test.go @@ -0,0 +1,17 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package secret_test + +import ( + "testing" + + "forgejo.org/models/unittest" + + _ "forgejo.org/models" + _ "forgejo.org/models/activities" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m) +} diff --git a/models/secret/secret.go b/models/secret/secret.go index 7be7f454a1..6f6867db52 100644 --- a/models/secret/secret.go +++ b/models/secret/secret.go @@ -11,9 +11,8 @@ import ( actions_model "forgejo.org/models/actions" "forgejo.org/models/db" actions_module "forgejo.org/modules/actions" + "forgejo.org/modules/keying" "forgejo.org/modules/log" - secret_module "forgejo.org/modules/secret" - "forgejo.org/modules/setting" "forgejo.org/modules/timeutil" "forgejo.org/modules/util" @@ -39,7 +38,7 @@ type Secret struct { OwnerID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL"` RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name) NOT NULL DEFAULT 0"` Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"` - Data string `xorm:"LONGTEXT"` // encrypted data + Data []byte `xorm:"BLOB"` // encrypted data CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"` } @@ -67,17 +66,21 @@ func InsertEncryptedSecret(ctx context.Context, ownerID, repoID int64, name, dat return nil, fmt.Errorf("%w: ownerID and repoID cannot be both zero, global secrets are not supported", util.ErrInvalidArgument) } - encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data) - if err != nil { - return nil, err - } secret := &Secret{ OwnerID: ownerID, RepoID: repoID, Name: strings.ToUpper(name), - Data: encrypted, } - return secret, db.Insert(ctx, secret) + + return secret, db.WithTx(ctx, func(ctx context.Context) error { + if err := db.Insert(ctx, secret); err != nil { + return err + } + + secret.SetSecret(data) + _, err := db.GetEngine(ctx).ID(secret.ID).Cols("data").Update(secret) + return err + }) } func init() { @@ -113,21 +116,9 @@ func (opts FindSecretsOptions) ToConds() builder.Cond { return cond } -// UpdateSecret changes org or user reop secret. -func UpdateSecret(ctx context.Context, secretID int64, data string) error { - encrypted, err := secret_module.EncryptSecret(setting.SecretKey, data) - if err != nil { - return err - } - - s := &Secret{ - Data: encrypted, - } - affected, err := db.GetEngine(ctx).ID(secretID).Cols("data").Update(s) - if affected != 1 { - return ErrSecretNotFound{} - } - return err +func (s *Secret) SetSecret(data string) { + key := keying.DeriveKey(keying.ContextActionSecret) + s.Data = key.Encrypt([]byte(data), keying.ColumnAndID("data", s.ID)) } func GetSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) (map[string]string, error) { @@ -155,13 +146,14 @@ func GetSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) (map[ return nil, err } + key := keying.DeriveKey(keying.ContextActionSecret) for _, secret := range append(ownerSecrets, repoSecrets...) { - v, err := secret_module.DecryptSecret(setting.SecretKey, secret.Data) + v, err := key.Decrypt(secret.Data, keying.ColumnAndID("data", secret.ID)) if err != nil { - log.Error("decrypt secret %v %q: %v", secret.ID, secret.Name, err) + log.Error("unable to decrypt secret[id=%d,name=%q]: %v", secret.ID, secret.Name, err) return nil, err } - secrets[secret.Name] = v + secrets[secret.Name] = string(v) } return secrets, nil diff --git a/models/secret/secret_test.go b/models/secret/secret_test.go new file mode 100644 index 0000000000..15142d207b --- /dev/null +++ b/models/secret/secret_test.go @@ -0,0 +1,103 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package secret + +import ( + "testing" + + "forgejo.org/models/actions" + "forgejo.org/models/repo" + "forgejo.org/models/unittest" + "forgejo.org/modules/keying" + "forgejo.org/modules/util" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestInsertEncryptedSecret(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + t.Run("Global secret", func(t *testing.T) { + secret, err := InsertEncryptedSecret(t.Context(), 0, 0, "GLOBAL_SECRET", "some common secret") + require.ErrorIs(t, err, util.ErrInvalidArgument) + assert.Nil(t, secret) + }) + + key := keying.DeriveKey(keying.ContextActionSecret) + + t.Run("Insert repository secret", func(t *testing.T) { + secret, err := InsertEncryptedSecret(t.Context(), 0, 1, "REPO_SECRET", "some repository secret") + require.NoError(t, err) + assert.NotNil(t, secret) + assert.Equal(t, "REPO_SECRET", secret.Name) + assert.EqualValues(t, 1, secret.RepoID) + assert.NotEmpty(t, secret.Data) + + // Assert the secret is stored in the database. + unittest.AssertExistsAndLoadBean(t, &Secret{RepoID: 1, Name: "REPO_SECRET", Data: secret.Data}) + + t.Run("Keying", func(t *testing.T) { + // Cannot decrypt with different ID. + plainText, err := key.Decrypt(secret.Data, keying.ColumnAndID("data", secret.ID+1)) + require.Error(t, err) + assert.Nil(t, plainText) + + // Cannot decrypt with different column. + plainText, err = key.Decrypt(secret.Data, keying.ColumnAndID("metadata", secret.ID)) + require.Error(t, err) + assert.Nil(t, plainText) + + // Can decrypt with correct column and ID. + plainText, err = key.Decrypt(secret.Data, keying.ColumnAndID("data", secret.ID)) + require.NoError(t, err) + assert.EqualValues(t, "some repository secret", plainText) + }) + }) + + t.Run("Insert owner secret", func(t *testing.T) { + secret, err := InsertEncryptedSecret(t.Context(), 2, 0, "OWNER_SECRET", "some owner secret") + require.NoError(t, err) + assert.NotNil(t, secret) + assert.Equal(t, "OWNER_SECRET", secret.Name) + assert.EqualValues(t, 2, secret.OwnerID) + assert.NotEmpty(t, secret.Data) + + // Assert the secret is stored in the database. + unittest.AssertExistsAndLoadBean(t, &Secret{OwnerID: 2, Name: "OWNER_SECRET", Data: secret.Data}) + + t.Run("Keying", func(t *testing.T) { + // Cannot decrypt with different ID. + plainText, err := key.Decrypt(secret.Data, keying.ColumnAndID("data", secret.ID+1)) + require.Error(t, err) + assert.Nil(t, plainText) + + // Cannot decrypt with different column. + plainText, err = key.Decrypt(secret.Data, keying.ColumnAndID("metadata", secret.ID)) + require.Error(t, err) + assert.Nil(t, plainText) + + // Can decrypt with correct column and ID. + plainText, err = key.Decrypt(secret.Data, keying.ColumnAndID("data", secret.ID)) + require.NoError(t, err) + assert.EqualValues(t, "some owner secret", plainText) + }) + }) + + t.Run("Get secrets", func(t *testing.T) { + secrets, err := GetSecretsOfTask(t.Context(), &actions.ActionTask{ + Job: &actions.ActionRunJob{ + Run: &actions.ActionRun{ + RepoID: 1, + Repo: &repo.Repository{ + OwnerID: 2, + }, + }, + }, + }) + require.NoError(t, err) + assert.Equal(t, "some owner secret", secrets["OWNER_SECRET"]) + assert.Equal(t, "some repository secret", secrets["REPO_SECRET"]) + }) +} diff --git a/models/unit/unit.go b/models/unit/unit.go index a14f3ff364..6b4f2765ee 100644 --- a/models/unit/unit.go +++ b/models/unit/unit.go @@ -271,7 +271,6 @@ type Unit struct { Name string NameKey string URI string - DescKey string Idx int MaxAccessMode perm.AccessMode // The max access mode of the unit. i.e. Read means this unit can only be read. } @@ -299,7 +298,6 @@ var ( "code", "repo.code", "/", - "repo.code.desc", 0, perm.AccessModeOwner, } @@ -309,7 +307,6 @@ var ( "issues", "repo.issues", "/issues", - "repo.issues.desc", 1, perm.AccessModeOwner, } @@ -319,7 +316,6 @@ var ( "ext_issues", "repo.ext_issues", "/issues", - "repo.ext_issues.desc", 1, perm.AccessModeRead, } @@ -329,7 +325,6 @@ var ( "pulls", "repo.pulls", "/pulls", - "repo.pulls.desc", 2, perm.AccessModeOwner, } @@ -339,7 +334,6 @@ var ( "releases", "repo.releases", "/releases", - "repo.releases.desc", 3, perm.AccessModeOwner, } @@ -349,7 +343,6 @@ var ( "wiki", "repo.wiki", "/wiki", - "repo.wiki.desc", 4, perm.AccessModeOwner, } @@ -359,7 +352,6 @@ var ( "ext_wiki", "repo.ext_wiki", "/wiki", - "repo.ext_wiki.desc", 4, perm.AccessModeRead, } @@ -369,7 +361,6 @@ var ( "projects", "repo.projects", "/projects", - "repo.projects.desc", 5, perm.AccessModeOwner, } @@ -379,7 +370,6 @@ var ( "packages", "repo.packages", "/packages", - "packages.desc", 6, perm.AccessModeRead, } @@ -389,7 +379,6 @@ var ( "actions", "repo.actions", "/actions", - "actions.unit.desc", 7, perm.AccessModeOwner, } diff --git a/models/unittest/fixture_loader.go b/models/unittest/fixture_loader.go index 67ef1b28df..0b4ab52c61 100644 --- a/models/unittest/fixture_loader.go +++ b/models/unittest/fixture_loader.go @@ -12,7 +12,9 @@ import ( "path/filepath" "strings" - "gopkg.in/yaml.v3" + "forgejo.org/modules/container" + + "go.yaml.in/yaml/v3" ) type insertSQL struct { @@ -32,13 +34,15 @@ type loader struct { fixtureFiles []*fixtureFile } -func newFixtureLoader(db *sql.DB, dialect string, fixturePaths []string) (*loader, error) { +func newFixtureLoader(db *sql.DB, dialect string, fixturePaths []string, allTableNames container.Set[string]) (*loader, error) { l := &loader{ db: db, dialect: dialect, fixtureFiles: []*fixtureFile{}, } + tablesWithoutFixture := allTableNames + // Load fixtures for _, fixturePath := range fixturePaths { stat, err := os.Stat(fixturePath) @@ -60,6 +64,7 @@ func newFixtureLoader(db *sql.DB, dialect string, fixturePaths []string) (*loade return nil, err } l.fixtureFiles = append(l.fixtureFiles, fixtureFile) + tablesWithoutFixture.Remove(fixtureFile.name) } } } else { @@ -71,6 +76,14 @@ func newFixtureLoader(db *sql.DB, dialect string, fixturePaths []string) (*loade } } + // Even though these tables have no fixtures, they can still be used and ensure + // they are cleaned. + for table := range tablesWithoutFixture.Seq() { + l.fixtureFiles = append(l.fixtureFiles, &fixtureFile{ + name: table, + }) + } + return l, nil } @@ -178,13 +191,13 @@ func (l *loader) Load() error { }() // Clean the table and re-insert the fixtures. - tableDeleted := map[string]struct{}{} + tableDeleted := make(container.Set[string]) for _, fixture := range l.fixtureFiles { - if _, ok := tableDeleted[fixture.name]; !ok { + 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[fixture.name] = struct{}{} + tableDeleted.Add(fixture.name) } for _, insertSQL := range fixture.insertSQLs { diff --git a/models/unittest/fixtures.go b/models/unittest/fixtures.go index 6dc5c8412d..829cc16466 100644 --- a/models/unittest/fixtures.go +++ b/models/unittest/fixtures.go @@ -7,10 +7,12 @@ package unittest import ( "fmt" "path/filepath" + "sync" "time" "forgejo.org/models/db" "forgejo.org/modules/auth/password/hash" + "forgejo.org/modules/container" "forgejo.org/modules/setting" "xorm.io/xorm" @@ -44,6 +46,8 @@ func OverrideFixtures(dir string) func() { } } +var allTableNames = sync.OnceValue(db.GetTableNames) + // InitFixtures initialize test fixtures for a test database func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) { e, err := GetXORMEngine(engine...) @@ -75,7 +79,12 @@ func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) { panic("Unsupported RDBMS for test") } - fixturesLoader, err = newFixtureLoader(e.DB().DB, dialect, fixturePaths) + var allTables container.Set[string] + if !opts.SkipCleanRegistedModels { + allTables = allTableNames().Clone() + } + + fixturesLoader, err = newFixtureLoader(e.DB().DB, dialect, fixturePaths, allTables) if err != nil { return err } diff --git a/models/unittest/mock_http.go b/models/unittest/mock_http.go index 6064e07e9b..c2c12e55ee 100644 --- a/models/unittest/mock_http.go +++ b/models/unittest/mock_http.go @@ -41,6 +41,10 @@ func NewMockWebServer(t *testing.T, liveServerBaseURL, testDataDir string, liveM log.Info("Mock HTTP Server: got request for path %s", r.URL.Path) // TODO check request method (support POST?) fixturePath := fmt.Sprintf("%s/%s_%s", testDataDir, r.Method, url.PathEscape(path)) + if strings.Contains(path, "test_repo.git") { + // We got a git clone request against our mock server + fixturePath = fmt.Sprintf("%s/%s", testDataDir, strings.TrimLeft(r.URL.Path, "/")) + } if liveMode { liveURL := fmt.Sprintf("%s%s", liveServerBaseURL, path) diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index d34c9e9a0a..29ec82c55f 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -217,6 +217,10 @@ 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 } // CreateTestEngine creates a memory database and loads the fixture data from fixturesDir diff --git a/models/user/activitypub.go b/models/user/activitypub.go index 816fd8a098..aabf2336fc 100644 --- a/models/user/activitypub.go +++ b/models/user/activitypub.go @@ -19,7 +19,7 @@ func (u *User) APActorID() string { return fmt.Sprintf("%sapi/v1/activitypub/user-id/%s", setting.AppURL, url.PathEscape(fmt.Sprintf("%d", u.ID))) } -// APActorKeyID returns the ID of the user's public key -func (u *User) APActorKeyID() string { +// KeyID returns the ID of the user's public key +func (u *User) KeyID() string { return u.APActorID() + "#main-key" } diff --git a/models/user/fixtures/login_source.yml b/models/user/fixtures/login_source.yml new file mode 100644 index 0000000000..3950f85964 --- /dev/null +++ b/models/user/fixtures/login_source.yml @@ -0,0 +1,8 @@ +- + id: 1001 + type: 6 # OAuth2 + name: OAuth2 authentication source + is_active: 1 + cfg: '{"Provider":"invalid","ClientID":"invalid","ClientSecret":"invalid","AllowUsernameChange":true}' + created_unix: 1753740851 + updated_unix: 1753740851 diff --git a/models/user/fixtures/user.yml b/models/user/fixtures/user.yml index b1892f331b..137064a368 100644 --- a/models/user/fixtures/user.yml +++ b/models/user/fixtures/user.yml @@ -11,6 +11,7 @@ must_change_password: false login_source: 1001 login_name: 123 + login_type: 6 type: 5 salt: ZogKvWdyEx max_repo_creation: -1 diff --git a/models/user/follow.go b/models/user/follow.go index e32c226385..8663b2a943 100644 --- a/models/user/follow.go +++ b/models/user/follow.go @@ -39,21 +39,21 @@ func FollowUser(ctx context.Context, userID, followID int64) (err error) { return ErrBlockedByUser } - ctx, committer, err := db.TxContext(ctx) + dbCtx, committer, err := db.TxContext(ctx) if err != nil { return err } defer committer.Close() - if err = db.Insert(ctx, &Follow{UserID: userID, FollowID: followID}); err != nil { + if err = db.Insert(dbCtx, &Follow{UserID: userID, FollowID: followID}); err != nil { return err } - if _, err = db.Exec(ctx, "UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", followID); err != nil { + if _, err = db.Exec(dbCtx, "UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", followID); err != nil { return err } - if _, err = db.Exec(ctx, "UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", userID); err != nil { + if _, err = db.Exec(dbCtx, "UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", userID); err != nil { return err } return committer.Commit() @@ -65,21 +65,21 @@ func UnfollowUser(ctx context.Context, userID, followID int64) (err error) { return nil } - ctx, committer, err := db.TxContext(ctx) + dbCtx, committer, err := db.TxContext(ctx) if err != nil { return err } defer committer.Close() - if _, err = db.DeleteByBean(ctx, &Follow{UserID: userID, FollowID: followID}); err != nil { + if _, err = db.DeleteByBean(dbCtx, &Follow{UserID: userID, FollowID: followID}); err != nil { return err } - if _, err = db.Exec(ctx, "UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?", followID); err != nil { + if _, err = db.Exec(dbCtx, "UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?", followID); err != nil { return err } - if _, err = db.Exec(ctx, "UPDATE `user` SET num_following = num_following - 1 WHERE id = ?", userID); err != nil { + if _, err = db.Exec(dbCtx, "UPDATE `user` SET num_following = num_following - 1 WHERE id = ?", userID); err != nil { return err } return committer.Commit() diff --git a/models/user/moderation.go b/models/user/moderation.go index afda497f02..17901f84ec 100644 --- a/models/user/moderation.go +++ b/models/user/moderation.go @@ -37,6 +37,26 @@ type UserData struct { //revive:disable-line:exported AvatarEmail string } +// Implements GetFieldsMap() from ShadowCopyData interface, returning a list of pairs +// to be used when rendering the shadow copy for admins reviewing the corresponding abuse report(s). +func (ud UserData) GetFieldsMap() []moderation.ShadowCopyField { + return []moderation.ShadowCopyField{ + {Key: "Name", Value: ud.Name}, + {Key: "FullName", Value: ud.FullName}, + {Key: "Email", Value: ud.Email}, + {Key: "LoginName", Value: ud.LoginName}, + {Key: "Location", Value: ud.Location}, + {Key: "Website", Value: ud.Website}, + {Key: "Pronouns", Value: ud.Pronouns}, + {Key: "Description", Value: ud.Description}, + {Key: "CreatedUnix", Value: ud.CreatedUnix.AsLocalTime().String()}, + {Key: "UpdatedUnix", Value: ud.UpdatedUnix.AsLocalTime().String()}, + {Key: "LastLogin", Value: ud.LastLogin.AsLocalTime().String()}, + {Key: "Avatar", Value: ud.Avatar}, + {Key: "AvatarEmail", Value: ud.AvatarEmail}, + } +} + // newUserData creates a trimmed down user to be used just to create a JSON structure // (keeping only the fields relevant for moderation purposes) func newUserData(user *User) UserData { @@ -73,16 +93,20 @@ var userDataColumnNames = sync.OnceValue(func() []string { // and if found a shadow copy of relevant user fields will be stored into DB and linked to the above report(s). // This function should be called before a user is deleted or updated. // +// In case the User object was already altered before calling this method, just provide the userID and +// nil for unalteredUser; when it is decided that a shadow copy should be created and unalteredUser is nil, +// the user will be retrieved from DB based on the provided userID. +// // For deletions alteredCols argument must be omitted. // // In case of updates it will first checks whether any of the columns being updated (alteredCols argument) // is relevant for moderation purposes (i.e. included in the UserData struct). -func IfNeededCreateShadowCopyForUser(ctx context.Context, user *User, alteredCols ...string) error { +func IfNeededCreateShadowCopyForUser(ctx context.Context, userID int64, unalteredUser *User, alteredCols ...string) error { // TODO: this can be triggered quite often (e.g. by routers/web/repo/middlewares.go SetDiffViewStyle()) shouldCheckIfNeeded := len(alteredCols) == 0 // no columns being updated, therefore a deletion if !shouldCheckIfNeeded { - // for updates we need to go further only if certain column are being changed + // for updates we need to go further only if certain columns are being changed for _, colName := range userDataColumnNames() { if shouldCheckIfNeeded = slices.Contains(alteredCols, colName); shouldCheckIfNeeded { break @@ -94,18 +118,23 @@ func IfNeededCreateShadowCopyForUser(ctx context.Context, user *User, alteredCol return nil } - shadowCopyNeeded, err := moderation.IsShadowCopyNeeded(ctx, moderation.ReportedContentTypeUser, user.ID) + shadowCopyNeeded, err := moderation.IsShadowCopyNeeded(ctx, moderation.ReportedContentTypeUser, userID) if err != nil { return err } if shadowCopyNeeded { - userData := newUserData(user) + if unalteredUser == nil { + if unalteredUser, err = GetUserByID(ctx, userID); err != nil { + return err + } + } + userData := newUserData(unalteredUser) content, err := json.Marshal(userData) if err != nil { return err } - return moderation.CreateShadowCopyForUser(ctx, user.ID, string(content)) + return moderation.CreateShadowCopyForUser(ctx, userID, string(content)) } return nil diff --git a/models/user/moderation_test.go b/models/user/moderation_test.go new file mode 100644 index 0000000000..f951e41e11 --- /dev/null +++ b/models/user/moderation_test.go @@ -0,0 +1,60 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package user_test + +import ( + "testing" + + "forgejo.org/models/moderation" + "forgejo.org/models/user" + "forgejo.org/modules/timeutil" + + "github.com/stretchr/testify/assert" +) + +const ( + tsCreated timeutil.TimeStamp = timeutil.TimeStamp(1753093200) // 2025-07-21 10:20:00 UTC + tsUpdated timeutil.TimeStamp = timeutil.TimeStamp(1753093320) // 2025-07-21 10:22:00 UTC + tsLastLogin timeutil.TimeStamp = timeutil.TimeStamp(1753093800) // 2025-07-21 10:30:00 UTC +) + +func testShadowCopyField(t *testing.T, scField moderation.ShadowCopyField, key, value string) { + assert.Equal(t, key, scField.Key) + assert.Equal(t, value, scField.Value) +} + +func TestUserDataGetFieldsMap(t *testing.T) { + ud := user.UserData{ + Name: "alexsmith", + FullName: "Alex Smith", + Email: "alexsmith@example.org", + LoginName: "", + Location: "@master@seo.net", + Website: "http://promote-your-business.biz", + Pronouns: "SEO", + Description: "I can help you promote your business online using SEO.", + CreatedUnix: tsCreated, + UpdatedUnix: tsUpdated, + LastLogin: tsLastLogin, + Avatar: "avatar-hash-user-1002", + AvatarEmail: "alexsmith@example.org", + } + scFields := ud.GetFieldsMap() + + if assert.Len(t, scFields, 13) { + testShadowCopyField(t, scFields[0], "Name", "alexsmith") + testShadowCopyField(t, scFields[1], "FullName", "Alex Smith") + testShadowCopyField(t, scFields[2], "Email", "alexsmith@example.org") + testShadowCopyField(t, scFields[3], "LoginName", "") + testShadowCopyField(t, scFields[4], "Location", "@master@seo.net") + testShadowCopyField(t, scFields[5], "Website", "http://promote-your-business.biz") + testShadowCopyField(t, scFields[6], "Pronouns", "SEO") + testShadowCopyField(t, scFields[7], "Description", "I can help you promote your business online using SEO.") + testShadowCopyField(t, scFields[8], "CreatedUnix", tsCreated.AsLocalTime().String()) + testShadowCopyField(t, scFields[9], "UpdatedUnix", tsUpdated.AsLocalTime().String()) + testShadowCopyField(t, scFields[10], "LastLogin", tsLastLogin.AsLocalTime().String()) + testShadowCopyField(t, scFields[11], "Avatar", "avatar-hash-user-1002") + testShadowCopyField(t, scFields[12], "AvatarEmail", "alexsmith@example.org") + } +} diff --git a/models/user/openid.go b/models/user/openid.go index 96b00255a3..b4d4f175b2 100644 --- a/models/user/openid.go +++ b/models/user/openid.go @@ -40,8 +40,8 @@ func GetUserOpenIDs(ctx context.Context, uid int64) ([]*UserOpenID, error) { return openids, nil } -// isOpenIDUsed returns true if the openid has been used. -func isOpenIDUsed(ctx context.Context, uri string) (bool, error) { +// IsOpenIDUsed returns true if the openid has been used. +func IsOpenIDUsed(ctx context.Context, uri string) (bool, error) { if len(uri) == 0 { return true, nil } @@ -71,7 +71,7 @@ func (err ErrOpenIDAlreadyUsed) Unwrap() error { // AddUserOpenID adds an pre-verified/normalized OpenID URI to given user. // NOTE: make sure openid.URI is normalized already func AddUserOpenID(ctx context.Context, openid *UserOpenID) error { - used, err := isOpenIDUsed(ctx, openid.URI) + used, err := IsOpenIDUsed(ctx, openid.URI) if err != nil { return err } else if used { diff --git a/models/user/redirect.go b/models/user/redirect.go index 75876f17d2..bcb421d4a1 100644 --- a/models/user/redirect.go +++ b/models/user/redirect.go @@ -21,7 +21,8 @@ import ( // ErrUserRedirectNotExist represents a "UserRedirectNotExist" kind of error. type ErrUserRedirectNotExist struct { - Name string + Name string + MissingPermission bool } // IsErrUserRedirectNotExist check if an error is an ErrUserRedirectNotExist. @@ -81,15 +82,6 @@ func GetUserRedirect(ctx context.Context, userName string) (*Redirect, error) { return redirect, nil } -// LookupUserRedirect look up userID if a user has a redirect name -func LookupUserRedirect(ctx context.Context, userName string) (int64, error) { - redirect, err := GetUserRedirect(ctx, userName) - if err != nil { - return 0, err - } - return redirect.RedirectUserID, nil -} - // NewUserRedirect create a new user redirect func NewUserRedirect(ctx context.Context, ID int64, oldUserName, newUserName string) error { oldUserName = strings.ToLower(oldUserName) diff --git a/models/user/redirect_test.go b/models/user/redirect_test.go deleted file mode 100644 index c598fb045f..0000000000 --- a/models/user/redirect_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package user_test - -import ( - "testing" - - "forgejo.org/models/db" - "forgejo.org/models/unittest" - user_model "forgejo.org/models/user" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestLookupUserRedirect(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - - userID, err := user_model.LookupUserRedirect(db.DefaultContext, "olduser1") - require.NoError(t, err) - assert.EqualValues(t, 1, userID) - - _, err = user_model.LookupUserRedirect(db.DefaultContext, "doesnotexist") - assert.True(t, user_model.IsErrUserRedirectNotExist(err)) -} diff --git a/models/user/user.go b/models/user/user.go index b124572bb6..8cdecc06e4 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -234,6 +234,33 @@ func GetAllAdmins(ctx context.Context) ([]*User, error) { return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).And("is_admin = ?", true).Find(&users) } +// MustHaveTwoFactor returns true if the user is a individual and requires 2fa +func (u *User) MustHaveTwoFactor() bool { + if !u.IsIndividual() || setting.GlobalTwoFactorRequirement.IsNone() { + return false + } + + return setting.GlobalTwoFactorRequirement.IsAll() || (u.IsAdmin && setting.GlobalTwoFactorRequirement.IsAdmin()) +} + +// IsAccessAllowed determines whether the user is permitted to log in based on +// their activation status, login prohibition, 2FA requirement and 2FA enrollment status. +func (u *User) IsAccessAllowed(ctx context.Context) bool { + if !u.IsActive || u.ProhibitLogin { + return false + } + if !u.MustHaveTwoFactor() { + return true + } + + hasTwoFactor, err := auth.HasTwoFactorByUID(ctx, u.ID) + if err != nil { + log.Error("Error getting 2fa: %s", err) + return false + } + return hasTwoFactor +} + // IsLocal returns true if user login type is LoginPlain. func (u *User) IsLocal() bool { return u.LoginType <= auth.Plain @@ -296,6 +323,9 @@ func (u *User) CanImportLocal() bool { // DashboardLink returns the user dashboard page link. func (u *User) DashboardLink() string { + if u.IsGhost() { + return "" + } if u.IsOrganization() { return u.OrganisationLink() + "/dashboard" } @@ -304,16 +334,25 @@ func (u *User) DashboardLink() string { // HomeLink returns the user or organization home page link. func (u *User) HomeLink() string { + if u.IsGhost() { + return "" + } return setting.AppSubURL + "/" + url.PathEscape(u.Name) } // HTMLURL returns the user or organization's full link. func (u *User) HTMLURL() string { + if u.IsGhost() { + return "" + } return setting.AppURL + url.PathEscape(u.Name) } // OrganisationLink returns the organization sub page link. func (u *User) OrganisationLink() string { + if u.IsGhost() || !u.IsOrganization() { + return "" + } return setting.AppSubURL + "/org/" + url.PathEscape(u.Name) } @@ -927,7 +966,9 @@ func UpdateUserCols(ctx context.Context, u *User, cols ...string) error { // If the user was reported as abusive and any of the columns being updated is relevant // for moderation purposes a shadow copy should be created before first update. - if err := IfNeededCreateShadowCopyForUser(ctx, u, cols...); err != nil { + // Since u is already altered at this point we are sending nil instead as an argument + // so that the unaltered version will be retrieved from DB. + if err := IfNeededCreateShadowCopyForUser(ctx, u.ID, nil, cols...); err != nil { return err } @@ -1161,8 +1202,8 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) { email = strings.ToLower(email) // Otherwise, check in alternative list for activated email addresses - emailAddress := &EmailAddress{LowerEmail: email, IsActivated: true} - has, err := db.GetEngine(ctx).Get(emailAddress) + emailAddress := &EmailAddress{} + has, err := db.GetEngine(ctx).Where("lower_email = ? AND is_activated = ?", email, true).Get(emailAddress) if err != nil { return nil, err } diff --git a/models/user/user_repository.go b/models/user/user_repository.go index 3f24efb1fb..df864746e8 100644 --- a/models/user/user_repository.go +++ b/models/user/user_repository.go @@ -28,7 +28,7 @@ func CreateFederatedUser(ctx context.Context, user *User, federatedUser *Federat } // Begin transaction - ctx, committer, err := db.TxContext((ctx)) + txCtx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -39,7 +39,7 @@ func CreateFederatedUser(ctx context.Context, user *User, federatedUser *Federat } }() - if err := CreateUser(ctx, user, &overwrite); err != nil { + if err := CreateUser(txCtx, user, &overwrite); err != nil { return err } @@ -48,7 +48,7 @@ func CreateFederatedUser(ctx context.Context, user *User, federatedUser *Federat return err } - _, err = db.GetEngine(ctx).Insert(federatedUser) + _, err = db.GetEngine(txCtx).Insert(federatedUser) if err != nil { return err } @@ -57,14 +57,6 @@ func CreateFederatedUser(ctx context.Context, user *User, federatedUser *Federat return committer.Commit() } -func (federatedUser *FederatedUser) UpdateFederatedUser(ctx context.Context) error { - if _, err := validation.IsValid(federatedUser); err != nil { - return err - } - _, err := db.GetEngine(ctx).ID(federatedUser.ID).Cols("inbox_path").Update(federatedUser) - return err -} - func FindFederatedUser(ctx context.Context, externalID string, federationHostID int64) (*User, *FederatedUser, error) { federatedUser := new(FederatedUser) user := new(User) @@ -78,7 +70,7 @@ func FindFederatedUser(ctx context.Context, externalID string, federationHostID if err != nil { return nil, nil, err } else if !has { - return nil, nil, fmt.Errorf("User %v for federated user is missing", federatedUser.UserID) + return nil, nil, fmt.Errorf("FederatedUser table contains entry for user ID %v, but no user with this ID exists", federatedUser.UserID) } if res, err := validation.IsValid(*user); !res { @@ -95,7 +87,7 @@ func GetFederatedUser(ctx context.Context, externalID string, federationHostID i if err != nil { return nil, nil, err } else if federatedUser == nil { - return nil, nil, fmt.Errorf("FederatedUser for externalId = %v and federationHostId = %v does not exist", externalID, federationHostID) + return nil, nil, fmt.Errorf("FederatedUser not found (given externalId: %v, federationHostId: %v)", externalID, federationHostID) } return user, federatedUser, nil } @@ -107,13 +99,13 @@ func GetFederatedUserByUserID(ctx context.Context, userID int64) (*User, *Federa if err != nil { return nil, nil, err } else if !has { - return nil, nil, fmt.Errorf("Federated user %v does not exist", federatedUser.UserID) + return nil, nil, fmt.Errorf("FederatedUser table does not contain entry for user ID: %v", federatedUser.UserID) } has, err = db.GetEngine(ctx).ID(federatedUser.UserID).Get(user) if err != nil { return nil, nil, err } else if !has { - return nil, nil, fmt.Errorf("User %v for federated user is missing", federatedUser.UserID) + return nil, nil, fmt.Errorf("FederatedUser table contains entry for user ID %v, but no user with this ID exists", federatedUser.UserID) } if res, err := validation.IsValid(*user); !res { @@ -138,7 +130,7 @@ func FindFederatedUserByKeyID(ctx context.Context, keyID string) (*User, *Federa if err != nil { return nil, nil, err } else if !has { - return nil, nil, fmt.Errorf("User %v for federated user is missing", federatedUser.UserID) + return nil, nil, fmt.Errorf("FederatedUser table contains entry for user ID %v, but no user with this ID exists", federatedUser.UserID) } if res, err := validation.IsValid(*user); !res { @@ -219,7 +211,6 @@ func RemoveFollower(ctx context.Context, followedUser *User, followingUser *Fede return err } -// TODO: We should unify Activity-pub-following and classical following (see models/user/follow.go) func IsFollowingAp(ctx context.Context, followedUser *User, followingUser *FederatedUser) (bool, error) { if res, err := validation.IsValid(followedUser); !res { return false, err diff --git a/models/user/user_test.go b/models/user/user_test.go index fd9d05653f..e4a94cbc57 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -46,6 +46,28 @@ func TestIsValidUserID(t *testing.T) { assert.True(t, user_model.IsValidUserID(200)) } +func TestUserLinks(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + assert.Equal(t, "/", user1.DashboardLink()) + assert.Equal(t, "/user1", user1.HomeLink()) + assert.Equal(t, "https://try.gitea.io/user1", user1.HTMLURL()) + assert.Empty(t, user1.OrganisationLink()) + + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + assert.Equal(t, "/org/org3/dashboard", org3.DashboardLink()) + assert.Equal(t, "/org3", org3.HomeLink()) + assert.Equal(t, "https://try.gitea.io/org3", org3.HTMLURL()) + assert.Equal(t, "/org/org3", org3.OrganisationLink()) + + ghost := user_model.NewGhostUser() + assert.Empty(t, ghost.DashboardLink()) + assert.Empty(t, ghost.HomeLink()) + assert.Empty(t, ghost.HTMLURL()) + assert.Empty(t, ghost.OrganisationLink()) +} + func TestGetUserFromMap(t *testing.T) { id := int64(200) idMap := map[int64]*user_model.User{ @@ -150,7 +172,7 @@ func TestAPActorID_APActorID(t *testing.T) { func TestKeyID(t *testing.T) { user := user_model.User{ID: 1} - url := user.APActorKeyID() + url := user.KeyID() expected := "https://try.gitea.io/api/v1/activitypub/user-id/1#main-key" assert.Equal(t, expected, url) } @@ -615,6 +637,145 @@ func TestGetAllAdmins(t *testing.T) { assert.Equal(t, int64(1), admins[0].ID) } +func TestMustHaveTwoFactor(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + normalUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) + restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}) + ghostUser := user_model.NewGhostUser() + + t.Run("NoneTwoFactorRequirement", func(t *testing.T) { + // this should be the default, so don't have to set the variable + assert.False(t, adminUser.MustHaveTwoFactor()) + assert.False(t, normalUser.MustHaveTwoFactor()) + assert.False(t, restrictedUser.MustHaveTwoFactor()) + assert.False(t, org.MustHaveTwoFactor()) + assert.False(t, ghostUser.MustHaveTwoFactor()) + }) + + t.Run("AllTwoFactorRequirement", func(t *testing.T) { + defer test.MockVariableValue(&setting.GlobalTwoFactorRequirement, setting.AllTwoFactorRequirement)() + + assert.True(t, adminUser.MustHaveTwoFactor()) + assert.True(t, normalUser.MustHaveTwoFactor()) + assert.True(t, restrictedUser.MustHaveTwoFactor()) + assert.False(t, org.MustHaveTwoFactor()) + assert.True(t, ghostUser.MustHaveTwoFactor()) + }) + + t.Run("AdminTwoFactorRequirement", func(t *testing.T) { + defer test.MockVariableValue(&setting.GlobalTwoFactorRequirement, setting.AdminTwoFactorRequirement)() + + assert.True(t, adminUser.MustHaveTwoFactor()) + assert.False(t, normalUser.MustHaveTwoFactor()) + assert.False(t, restrictedUser.MustHaveTwoFactor()) + assert.False(t, org.MustHaveTwoFactor()) + assert.False(t, ghostUser.MustHaveTwoFactor()) + }) +} + +func TestIsAccessAllowed(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + runTest := func(t *testing.T, user *user_model.User, useTOTP, accessAllowed bool) { + t.Helper() + if useTOTP { + unittest.AssertSuccessfulInsert(t, &auth.TwoFactor{UID: user.ID}) + defer unittest.AssertSuccessfulDelete(t, &auth.TwoFactor{UID: user.ID}) + } + + assert.Equal(t, accessAllowed, user.IsAccessAllowed(t.Context())) + } + + adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + normalUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + inactiveUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 9}) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) + restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}) + prohibitLoginUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 37}) + ghostUser := user_model.NewGhostUser() + + // users with enabled WebAuthn + normalWebAuthnUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 32}) + + t.Run("NoneTwoFactorRequirement", func(t *testing.T) { + // this should be the default, so don't have to set the variable + + t.Run("no 2fa", func(t *testing.T) { + runTest(t, adminUser, false, true) + runTest(t, normalUser, false, true) + runTest(t, inactiveUser, false, false) + runTest(t, org, false, true) + runTest(t, restrictedUser, false, true) + runTest(t, prohibitLoginUser, false, false) + runTest(t, ghostUser, false, false) + }) + + t.Run("enabled 2fa", func(t *testing.T) { + runTest(t, normalWebAuthnUser, false, true) + + runTest(t, adminUser, true, true) + runTest(t, normalUser, true, true) + runTest(t, inactiveUser, true, false) + runTest(t, org, true, true) + runTest(t, restrictedUser, true, true) + runTest(t, prohibitLoginUser, true, false) + }) + }) + + t.Run("AllTwoFactorRequirement", func(t *testing.T) { + defer test.MockVariableValue(&setting.GlobalTwoFactorRequirement, setting.AllTwoFactorRequirement)() + + t.Run("no 2fa", func(t *testing.T) { + runTest(t, adminUser, false, false) + runTest(t, normalUser, false, false) + runTest(t, inactiveUser, false, false) + runTest(t, org, false, true) + runTest(t, restrictedUser, false, false) + runTest(t, prohibitLoginUser, false, false) + runTest(t, ghostUser, false, false) + }) + + t.Run("enabled 2fa", func(t *testing.T) { + runTest(t, normalWebAuthnUser, false, true) + + runTest(t, adminUser, true, true) + runTest(t, normalUser, true, true) + runTest(t, inactiveUser, true, false) + runTest(t, org, true, true) + runTest(t, restrictedUser, true, true) + runTest(t, prohibitLoginUser, true, false) + }) + }) + + t.Run("AdminTwoFactorRequirement", func(t *testing.T) { + defer test.MockVariableValue(&setting.GlobalTwoFactorRequirement, setting.AdminTwoFactorRequirement)() + + t.Run("no 2fa", func(t *testing.T) { + runTest(t, adminUser, false, false) + runTest(t, normalUser, false, true) + runTest(t, inactiveUser, false, false) + runTest(t, org, false, true) + runTest(t, restrictedUser, false, true) + runTest(t, prohibitLoginUser, false, false) + runTest(t, ghostUser, false, false) + }) + + t.Run("enabled 2fa", func(t *testing.T) { + runTest(t, normalWebAuthnUser, false, true) + + runTest(t, adminUser, true, true) + runTest(t, normalUser, true, true) + runTest(t, inactiveUser, true, false) + runTest(t, org, true, true) + runTest(t, restrictedUser, true, true) + runTest(t, prohibitLoginUser, true, false) + }) + }) +} + func Test_ValidateUser(t *testing.T) { defer test.MockVariableValue(&setting.Service.AllowedUserVisibilityModesSlice, []bool{true, false, true})() @@ -835,3 +996,25 @@ func TestPronounsPrivacy(t *testing.T) { assert.Equal(t, "any", user.GetPronouns(true)) }) } + +func TestGetUserByEmail(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + t.Run("Normal", func(t *testing.T) { + u, err := user_model.GetUserByEmail(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.GetUserByEmail(t.Context(), "user11@example.com") + require.ErrorIs(t, err, user_model.ErrUserNotExist{Name: "user11@example.com"}) + assert.Nil(t, u) + }) + + t.Run("Not primary", func(t *testing.T) { + u, err := user_model.GetUserByEmail(t.Context(), "user1-3@example.com") + require.NoError(t, err) + assert.EqualValues(t, 1, u.ID) + }) +} diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 7ae4557ed6..c3960d140a 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -13,11 +13,11 @@ import ( api "forgejo.org/modules/structs" webhook_module "forgejo.org/modules/webhook" + "code.forgejo.org/forgejo/runner/v11/act/jobparser" + "code.forgejo.org/forgejo/runner/v11/act/model" + "code.forgejo.org/forgejo/runner/v11/act/workflowpattern" "github.com/gobwas/glob" - "github.com/nektos/act/pkg/jobparser" - "github.com/nektos/act/pkg/model" - "github.com/nektos/act/pkg/workflowpattern" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) type DetectedWorkflow struct { @@ -86,7 +86,7 @@ func GetContentFromEntry(entry *git.TreeEntry) ([]byte, error) { } func GetEventsFromContent(content []byte) ([]*jobparser.Event, error) { - workflow, err := model.ReadWorkflow(bytes.NewReader(content)) + workflow, err := model.ReadWorkflow(bytes.NewReader(content), false) if err != nil { return nil, err } diff --git a/modules/activitypub/client.go b/modules/activitypub/client.go index d015fb7bec..11a2fd94c3 100644 --- a/modules/activitypub/client.go +++ b/modules/activitypub/client.go @@ -66,6 +66,11 @@ type ClientFactory struct { // NewClient function func NewClientFactory() (c *ClientFactory, err error) { + return NewClientFactoryWithTimeout(5 * time.Second) +} + +// NewClient function +func NewClientFactoryWithTimeout(timeout time.Duration) (c *ClientFactory, err error) { if err = containsRequiredHTTPHeaders(http.MethodGet, setting.Federation.GetHeaders); err != nil { return nil, err } else if err = containsRequiredHTTPHeaders(http.MethodPost, setting.Federation.PostHeaders); err != nil { @@ -77,7 +82,7 @@ func NewClientFactory() (c *ClientFactory, err error) { Transport: &http.Transport{ Proxy: proxy.Proxy(), }, - Timeout: 5 * time.Second, + Timeout: timeout, }, algs: setting.HttpsigAlgs, digestAlg: httpsig.DigestAlgorithm(setting.Federation.DigestAlgorithm), @@ -89,6 +94,7 @@ func NewClientFactory() (c *ClientFactory, err error) { type APClientFactory interface { WithKeys(ctx context.Context, user *user_model.User, pubID string) (APClient, error) + WithKeysDirect(ctx context.Context, privateKey, pubID string) (APClient, error) } // Client struct @@ -103,12 +109,8 @@ type Client struct { } // NewRequest function -func (cf *ClientFactory) WithKeys(ctx context.Context, user *user_model.User, pubID string) (APClient, error) { - priv, err := GetPrivateKey(ctx, user) - if err != nil { - return nil, err - } - privPem, _ := pem.Decode([]byte(priv)) +func (cf *ClientFactory) WithKeysDirect(ctx context.Context, privateKey, pubID string) (APClient, error) { + privPem, _ := pem.Decode([]byte(privateKey)) privParsed, err := x509.ParsePKCS1PrivateKey(privPem.Bytes) if err != nil { return nil, err @@ -126,6 +128,14 @@ func (cf *ClientFactory) WithKeys(ctx context.Context, user *user_model.User, pu return &c, nil } +func (cf *ClientFactory) WithKeys(ctx context.Context, user *user_model.User, pubID string) (APClient, error) { + priv, err := GetPrivateKey(ctx, user) + if err != nil { + return nil, err + } + return cf.WithKeysDirect(ctx, priv, pubID) +} + // NewRequest function func (c *Client) newRequest(method string, b []byte, to string) (req *http.Request, err error) { buf := bytes.NewBuffer(b) @@ -149,12 +159,14 @@ func (c *Client) Post(b []byte, to string) (resp *http.Response, err error) { return nil, err } - signer, _, err := httpsig.NewSigner(c.algs, c.digestAlg, c.postHeaders, httpsig.Signature, httpsigExpirationTime) - if err != nil { - return nil, err - } - if err := signer.SignRequest(c.priv, c.pubID, req, b); err != nil { - return nil, err + if c.pubID != "" { + signer, _, err := httpsig.NewSigner(c.algs, c.digestAlg, c.postHeaders, httpsig.Signature, httpsigExpirationTime) + if err != nil { + return nil, err + } + if err := signer.SignRequest(c.priv, c.pubID, req, b); err != nil { + return nil, err + } } resp, err = c.client.Do(req) @@ -167,12 +179,15 @@ func (c *Client) Get(to string) (resp *http.Response, err error) { if req, err = c.newRequest(http.MethodGet, nil, to); err != nil { return nil, err } - signer, _, err := httpsig.NewSigner(c.algs, c.digestAlg, c.getHeaders, httpsig.Signature, httpsigExpirationTime) - if err != nil { - return nil, err - } - if err := signer.SignRequest(c.priv, c.pubID, req, nil); err != nil { - return nil, err + + if c.pubID != "" { + signer, _, err := httpsig.NewSigner(c.algs, c.digestAlg, c.getHeaders, httpsig.Signature, httpsigExpirationTime) + if err != nil { + return nil, err + } + if err := signer.SignRequest(c.priv, c.pubID, req, nil); err != nil { + return nil, err + } } resp, err = c.client.Do(req) diff --git a/modules/assetfs/layered.go b/modules/assetfs/layered.go index 48c6728f43..2041f28bb1 100644 --- a/modules/assetfs/layered.go +++ b/modules/assetfs/layered.go @@ -56,14 +56,7 @@ func Local(name, base string, sub ...string) *Layer { panic(fmt.Sprintf("Unable to get absolute path for %q: %v", base, err)) } root := util.FilePathJoinAbs(base, sub...) - fsRoot, err := os.OpenRoot(root) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - return nil - } - panic(fmt.Sprintf("Unable to open layer %q", err)) - } - return &Layer{name: name, fs: fsRoot.FS(), localPath: root} + return &Layer{name: name, fs: os.DirFS(root), localPath: root} } // Bindata returns a new Layer with the given name, it serves files from the given bindata asset. @@ -80,7 +73,7 @@ type LayeredFS struct { // Layered returns a new LayeredFS with the given layers. The first layer is the top layer. func Layered(layers ...*Layer) *LayeredFS { - return &LayeredFS{layers: slices.DeleteFunc(layers, func(layer *Layer) bool { return layer == nil })} + return &LayeredFS{layers: layers} } // Open opens the named file. The caller is responsible for closing the file. diff --git a/modules/assetfs/layered_test.go b/modules/assetfs/layered_test.go index 87d1f92b00..76eeb61d83 100644 --- a/modules/assetfs/layered_test.go +++ b/modules/assetfs/layered_test.go @@ -1,4 +1,5 @@ // Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package assetfs @@ -108,3 +109,30 @@ func TestLayered(t *testing.T) { assert.Equal(t, "l1", assets.GetFileLayerName("f1")) assert.Equal(t, "l2", assets.GetFileLayerName("f2")) } + +// Allow layers to read symlink outside the layer root. +func TestLayeredSymlink(t *testing.T) { + dir := t.TempDir() + dirl1 := filepath.Join(dir, "l1") + require.NoError(t, os.MkdirAll(dirl1, 0o755)) + + // Open layer in dir/l1 + layer := Local("l1", dirl1) + + // Create a file in dir/outside + fileContents := []byte("I am outside the layer") + require.NoError(t, os.WriteFile(filepath.Join(dir, "outside"), fileContents, 0o600)) + // Symlink dir/l1/outside to dir/outside + require.NoError(t, os.Symlink(filepath.Join(dir, "outside"), filepath.Join(dirl1, "outside"))) + + // Open dir/l1/outside. + f, err := layer.Open("outside") + require.NoError(t, err) + defer f.Close() + + // Confirm it contains the output of dir/outside + contents, err := io.ReadAll(f) + require.NoError(t, err) + + assert.Equal(t, fileContents, contents) +} diff --git a/modules/base/tool.go b/modules/base/tool.go index fd6a7c2b77..e3a3ff4a23 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -114,7 +114,7 @@ func EntryIcon(entry *git.TreeEntry) string { return "file-symlink-file" case entry.IsDir(): return "file-directory-fill" - case entry.IsSubModule(): + case entry.IsSubmodule(): return "file-submodule" } diff --git a/modules/container/set.go b/modules/container/set.go index 70f837bc66..d3719dc552 100644 --- a/modules/container/set.go +++ b/modules/container/set.go @@ -74,3 +74,8 @@ func (s Set[T]) Values() []T { func (s Set[T]) Seq() iter.Seq[T] { return maps.Keys(s) } + +// Clone returns a identical shallow copy of this set. +func (s Set[T]) Clone() Set[T] { + return maps.Clone(s) +} diff --git a/modules/container/set_test.go b/modules/container/set_test.go index af5e9126ab..44e4847f6b 100644 --- a/modules/container/set_test.go +++ b/modules/container/set_test.go @@ -47,4 +47,11 @@ func TestSet(t *testing.T) { assert.False(t, s.IsSubset([]string{"key1"})) assert.True(t, s.IsSubset([]string{})) + + t.Run("Clone", func(t *testing.T) { + clonedSet := s.Clone() + clonedSet.Remove("key6") + assert.False(t, clonedSet.Contains("key6")) + assert.True(t, s.Contains("key6")) + }) } diff --git a/modules/forgefed/activity_follow_test.go b/modules/forgefed/activity_follow_test.go index bb0c1de2f7..8ba31d5f6f 100644 --- a/modules/forgefed/activity_follow_test.go +++ b/modules/forgefed/activity_follow_test.go @@ -9,6 +9,7 @@ import ( "forgejo.org/modules/validation" ap "github.com/go-ap/activitypub" + "github.com/stretchr/testify/assert" ) func Test_NewForgeFollowValidation(t *testing.T) { @@ -17,15 +18,13 @@ func Test_NewForgeFollowValidation(t *testing.T) { sut.Actor = ap.IRI("example.org/alice") sut.Object = ap.IRI("example.org/bob") - if err, _ := validation.IsValid(sut); !err { - t.Errorf("sut is invalid: %v\n", err) - } + valid, err := validation.IsValid(sut) + assert.True(t, valid, "sut is invalid: %v\n", err) sut = ForgeFollow{} sut.Actor = ap.IRI("example.org/alice") sut.Object = ap.IRI("example.org/bob") - if err, _ := validation.IsValid(sut); err { - t.Errorf("sut is valid: %v\n", err) - } + valid, err = validation.IsValid(sut) + assert.False(t, valid, "sut is valid: %v\n", err) } diff --git a/modules/forgefed/activity_like_test.go b/modules/forgefed/activity_like_test.go index 6b252d5960..eef5563d8b 100644 --- a/modules/forgefed/activity_like_test.go +++ b/modules/forgefed/activity_like_test.go @@ -13,6 +13,8 @@ import ( "forgejo.org/modules/validation" ap "github.com/go-ap/activitypub" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_NewForgeLike(t *testing.T) { @@ -22,21 +24,14 @@ func Test_NewForgeLike(t *testing.T) { objectIRI := "https://codeberg.org/api/v1/activitypub/repository-id/1" startTime, _ := time.Parse("2006-Jan-02", "2024-Mar-07") sut, err := NewForgeLike(actorIRI, objectIRI, startTime) - if err != nil { - t.Errorf("unexpected error: %v\n", err) - } - if valid, _ := validation.IsValid(sut); !valid { - t.Errorf("sut expected to be valid: %v\n", sut.Validate()) - } + require.NoError(t, err, "unexpected error: %v\n", err) + + valid, _ := validation.IsValid(sut) + assert.True(t, valid, "sut expected to be valid: %v\n", sut.Validate()) got, err := sut.MarshalJSON() - if err != nil { - t.Errorf("MarshalJSON() error = \"%v\"", err) - return - } - if !reflect.DeepEqual(got, want) { - t.Errorf("MarshalJSON() got = %q, want %q", got, want) - } + require.NoError(t, err, "MarshalJSON() error = %q", err) + assert.True(t, reflect.DeepEqual(got, want), "MarshalJSON()\n got: %q,\n want: %q", got, want) } func Test_LikeMarshalJSON(t *testing.T) { @@ -66,13 +61,8 @@ func Test_LikeMarshalJSON(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { got, err := tt.item.MarshalJSON() - if (err != nil || tt.wantErr != nil) && tt.wantErr.Error() != err.Error() { - t.Errorf("MarshalJSON() error = \"%v\", wantErr \"%v\"", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("MarshalJSON() got = %q, want %q", got, tt.want) - } + assert.False(t, (err != nil || tt.wantErr != nil) && tt.wantErr.Error() != err.Error(), "MarshalJSON()\n got: %v,\n want: %v", err, tt.wantErr) + assert.True(t, reflect.DeepEqual(got, tt.want), "MarshalJSON()\n got: %q\n want: %q", got, tt.want) }) } } @@ -89,8 +79,8 @@ func Test_LikeUnmarshalJSON(t *testing.T) { item: []byte(`{"type":"Like","actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1","object":"https://codeberg.org/api/activitypub/repository-id/1"}`), want: &ForgeLike{ Activity: ap.Activity{ - Actor: ap.IRI("https://repo.prod.meissa.de/api/activitypub/user-id/1"), Type: "Like", + Actor: ap.IRI("https://repo.prod.meissa.de/api/activitypub/user-id/1"), Object: ap.IRI("https://codeberg.org/api/activitypub/repository-id/1"), }, }, @@ -107,12 +97,10 @@ func Test_LikeUnmarshalJSON(t *testing.T) { t.Run(name, func(t *testing.T) { got := new(ForgeLike) err := got.UnmarshalJSON(test.item) - if (err != nil || test.wantErr != nil) && !strings.Contains(err.Error(), test.wantErr.Error()) { - t.Errorf("UnmarshalJSON() error = \"%v\", wantErr \"%v\"", err, test.wantErr) - return - } + assert.False(t, (err != nil || test.wantErr != nil) && !strings.Contains(err.Error(), test.wantErr.Error()), "UnmarshalJSON()\n error: %v\n wantErr: %v", err, test.wantErr) + if !reflect.DeepEqual(got, test.want) { - t.Errorf("UnmarshalJSON() got = %q, want %q, err %q", got, test.want, err.Error()) + assert.Errorf(t, err, "UnmarshalJSON() got = %q, want %q, err %q", got, test.want, err.Error()) } }) } @@ -120,46 +108,47 @@ func Test_LikeUnmarshalJSON(t *testing.T) { func Test_ForgeLikeValidation(t *testing.T) { // Successful - sut := new(ForgeLike) sut.UnmarshalJSON([]byte(`{"type":"Like", "actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1", "object":"https://codeberg.org/api/activitypub/repository-id/1", "startTime": "2014-12-31T23:00:00-08:00"}`)) - if res, _ := validation.IsValid(sut); !res { - t.Errorf("sut expected to be valid: %v\n", sut.Validate()) - } + valid, _ := validation.IsValid(sut) + assert.True(t, valid, "sut expected to be valid: %v\n", sut.Validate()) // Errors - sut.UnmarshalJSON([]byte(`{"actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1", "object":"https://codeberg.org/api/activitypub/repository-id/1", "startTime": "2014-12-31T23:00:00-08:00"}`)) - if err := validateAndCheckError(sut, "type should not be empty"); err != nil { - t.Error(err) - } + validate := sut.Validate() + assert.Len(t, validate, 2) + assert.Equal(t, + "Field type contains the value , which is not in allowed subset [Like]", + validate[1]) sut.UnmarshalJSON([]byte(`{"type":"bad-type", "actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1", "object":"https://codeberg.org/api/activitypub/repository-id/1", "startTime": "2014-12-31T23:00:00-08:00"}`)) - if err := validateAndCheckError(sut, "Field type contains the value bad-type, which is not in allowed subset [Like]"); err != nil { - t.Error(err) - } + validate = sut.Validate() + assert.Len(t, validate, 1) + assert.Equal(t, + "Field type contains the value bad-type, which is not in allowed subset [Like]", + validate[0]) sut.UnmarshalJSON([]byte(`{"type":"Like", "actor":"https://repo.prod.meissa.de/api/activitypub/user-id/1", "object":"https://codeberg.org/api/activitypub/repository-id/1", "startTime": "not a date"}`)) - if err := validateAndCheckError(sut, "StartTime was invalid."); err != nil { - t.Error(err) - } + validate = sut.Validate() + assert.Len(t, validate, 1) + assert.Equal(t, + "StartTime was invalid.", + validate[0]) } func TestActivityValidation_Attack(t *testing.T) { sut := new(ForgeLike) sut.UnmarshalJSON([]byte(`{rubbish}`)) - if len(sut.Validate()) != 5 { - t.Errorf("5 validation errors expected but was: %v\n", len(sut.Validate())) - } + assert.Len(t, sut.Validate(), 5) } diff --git a/modules/forgefed/activity_undo_like_test.go b/modules/forgefed/activity_undo_like_test.go index 5867a84e7b..76358b1669 100644 --- a/modules/forgefed/activity_undo_like_test.go +++ b/modules/forgefed/activity_undo_like_test.go @@ -173,7 +173,7 @@ func TestActivityValidationUndo(t *testing.T) { "startTime":"2024-03-27T00:00:00Z", "actor":"https://repo.prod.meissa.de/api/v1/activitypub/user-id/1", "object":"https://codeberg.org/api/v1/activitypub/repository-id/1"}}`)) - if err := validateAndCheckError(sut, "type should not be empty"); err != nil { + if err := validateAndCheckError(sut, "Value type should not be empty"); err != nil { t.Error(*err) } diff --git a/modules/forgefed/activity_user_activity_test.go b/modules/forgefed/activity_user_activity_test.go index 9cb9f133b9..107ae51204 100644 --- a/modules/forgefed/activity_user_activity_test.go +++ b/modules/forgefed/activity_user_activity_test.go @@ -9,6 +9,7 @@ import ( "forgejo.org/modules/validation" ap "github.com/go-ap/activitypub" + "github.com/stretchr/testify/assert" ) func Test_ForgeUserActivityValidation(t *testing.T) { @@ -34,7 +35,6 @@ func Test_ForgeUserActivityValidation(t *testing.T) { sut.Note = note - if res, _ := validation.IsValid(sut); !res { - t.Errorf("sut expected to be valid: %v\n", sut.Validate()) - } + valid, _ := validation.IsValid(sut) + assert.True(t, valid, "sut expected to be valid: %v\n", sut.Validate()) } diff --git a/modules/forgefed/actor_person_test.go b/modules/forgefed/actor_person_test.go index f466ddb964..e4f1734a9d 100644 --- a/modules/forgefed/actor_person_test.go +++ b/modules/forgefed/actor_person_test.go @@ -115,7 +115,7 @@ func TestPersonIdValidation(t *testing.T) { result, err := validation.IsValid(sut) assert.False(t, result) - require.EqualError(t, err, "Validation Error: forgefed.PersonID: path should not be empty\npath: \"\" has to be a person specific api path") + require.EqualError(t, err, "Validation Error: forgefed.PersonID: Value path should not be empty\npath: \"\" has to be a person specific api path") sut = PersonID{} sut.ID = "1" @@ -166,38 +166,28 @@ func TestWebfingerId(t *testing.T) { } func TestShouldThrowErrorOnInvalidInput(t *testing.T) { - var err any - _, err = NewPersonID("", "forgejo") - if err == nil { - t.Errorf("empty input should be invalid.") + tests := []struct { + input string + username string + expectErr bool + }{ + {"", "forgejo", true}, + {"http://localhost:3000/api/v1/something", "forgejo", true}, + {"./api/v1/something", "forgejo", true}, + {"http://1.2.3.4/api/v1/something", "forgejo", true}, + {"http:///[fe80::1ff:fe23:4567:890a%25eth0]/api/v1/something", "forgejo", true}, + {"https://codeberg.org/api/v1/activitypub/../activitypub/user-id/12345", "forgejo", true}, + {"https://myuser@an.other.host/api/v1/activitypub/user-id/1", "forgejo", true}, + {"https://an.other.host/api/v1/activitypub/user-id/1", "forgejo", false}, } - _, err = NewPersonID("http://localhost:3000/api/v1/something", "forgejo") - if err == nil { - t.Errorf("localhost uris are not external") - } - _, err = NewPersonID("./api/v1/something", "forgejo") - if err == nil { - t.Errorf("relative uris are not allowed") - } - _, err = NewPersonID("http://1.2.3.4/api/v1/something", "forgejo") - if err == nil { - t.Errorf("uri may not be ip-4 based") - } - _, err = NewPersonID("http:///[fe80::1ff:fe23:4567:890a%25eth0]/api/v1/something", "forgejo") - if err == nil { - t.Errorf("uri may not be ip-6 based") - } - _, err = NewPersonID("https://codeberg.org/api/v1/activitypub/../activitypub/user-id/12345", "forgejo") - if err == nil { - t.Errorf("uri may not contain relative path elements") - } - _, err = NewPersonID("https://myuser@an.other.host/api/v1/activitypub/user-id/1", "forgejo") - if err == nil { - t.Errorf("uri may not contain unparsed elements") - } - _, err = NewPersonID("https://an.other.host/api/v1/activitypub/user-id/1", "forgejo") - if err != nil { - t.Errorf("this uri should be valid but was: %v", err) + + for _, tt := range tests { + _, err := NewPersonID(tt.input, tt.username) + if tt.expectErr { + assert.Error(t, err, "Expected an error for input: %s", tt.input) + } else { + assert.NoError(t, err, "Expected no error for input: %s, but got: %v", tt.input, err) + } } } @@ -221,14 +211,11 @@ func Test_PersonUnmarshalJSON(t *testing.T) { } sut := new(ForgePerson) err := sut.UnmarshalJSON([]byte(`{"type":"Person","preferredUsername":"MaxMuster"}`)) - if err != nil { - t.Errorf("UnmarshalJSON() unexpected error: %v", err) - } + require.NoError(t, err, "UnmarshalJSON() unexpected error: %q", err) + x, _ := expected.MarshalJSON() y, _ := sut.MarshalJSON() - if !reflect.DeepEqual(x, y) { - t.Errorf("UnmarshalJSON() expected: %q got: %q", x, y) - } + assert.True(t, reflect.DeepEqual(x, y), "UnmarshalJSON()\n got: %q,\n want: %q", x, y) expectedStr := strings.ReplaceAll(strings.ReplaceAll(`{ "id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10", @@ -244,9 +231,7 @@ func Test_PersonUnmarshalJSON(t *testing.T) { "\n", ""), "\t", "") err = sut.UnmarshalJSON([]byte(expectedStr)) - if err != nil { - t.Errorf("UnmarshalJSON() unexpected error: %v", err) - } + require.NoError(t, err, "UnmarshalJSON() unexpected error: %q", err) result, _ := sut.MarshalJSON() assert.JSONEq(t, expectedStr, string(result), "Expected string is not equal") } @@ -254,9 +239,8 @@ func Test_PersonUnmarshalJSON(t *testing.T) { func TestForgePersonValidation(t *testing.T) { sut := new(ForgePerson) sut.UnmarshalJSON([]byte(`{"type":"Person","preferredUsername":"MaxMuster"}`)) - if res, _ := validation.IsValid(sut); !res { - t.Errorf("sut expected to be valid: %v\n", sut.Validate()) - } + valid, _ := validation.IsValid(sut) + assert.True(t, valid, "sut expected to be valid: %v\n", sut.Validate()) } func TestAsloginName(t *testing.T) { diff --git a/modules/forgefed/actor_test.go b/modules/forgefed/actor_test.go index 48d773c5b9..a32114616c 100644 --- a/modules/forgefed/actor_test.go +++ b/modules/forgefed/actor_test.go @@ -58,7 +58,7 @@ func TestActorIdValidation(t *testing.T) { sut.UnvalidatedInput = "https://an.other.host/api/v1/activitypub/user-id/" result := sut.Validate() assert.Len(t, result, 1) - assert.Equal(t, "ID should not be empty", result[0]) + assert.Equal(t, "Value ID should not be empty", result[0]) sut = ActorID{} sut.ID = "1" diff --git a/modules/forgefed/object_user_activity_note_test.go b/modules/forgefed/object_user_activity_note_test.go index 20c3666bb1..02aebd58d3 100644 --- a/modules/forgefed/object_user_activity_note_test.go +++ b/modules/forgefed/object_user_activity_note_test.go @@ -9,6 +9,7 @@ import ( "forgejo.org/modules/validation" ap "github.com/go-ap/activitypub" + "github.com/stretchr/testify/assert" ) func Test_UserActivityNoteValidation(t *testing.T) { @@ -22,7 +23,6 @@ func Test_UserActivityNoteValidation(t *testing.T) { } sut.URL = ap.IRI("example.org/user-id/57") - if res, _ := validation.IsValid(sut); !res { - t.Errorf("sut expected to be valid: %v\n", sut.Validate()) - } + valid, _ := validation.IsValid(sut) + assert.True(t, valid, "sut expected to be valid: %v\n", sut.Validate()) } diff --git a/modules/git/blame.go b/modules/git/blame.go index 4ff347e31b..868edab2b8 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -132,7 +132,7 @@ func (r *BlameReader) Close() error { // CreateBlameReader creates reader for given repository, commit and file func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) { var ignoreRevsFile *string - if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore { + if !bypassBlameIgnore { ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit) } diff --git a/modules/git/blob.go b/modules/git/blob.go index 8c5c275146..4eef5f0e2a 100644 --- a/modules/git/blob.go +++ b/modules/git/blob.go @@ -8,6 +8,7 @@ import ( "bufio" "bytes" "encoding/base64" + "fmt" "io" "forgejo.org/modules/log" @@ -172,60 +173,43 @@ func (b *Blob) GetBlobContent(limit int64) (string, error) { return string(buf), err } -// GetBlobLineCount gets line count of the blob -func (b *Blob) GetBlobLineCount() (int, error) { - reader, err := b.DataAsync() - if err != nil { - return 0, err - } - defer reader.Close() - buf := make([]byte, 32*1024) - count := 1 - lineSep := []byte{'\n'} - - c, err := reader.Read(buf) - if c == 0 && err == io.EOF { - return 0, nil - } - for { - count += bytes.Count(buf[:c], lineSep) - switch { - case err == io.EOF: - return count, nil - case err != nil: - return count, err - } - c, err = reader.Read(buf) - } +type BlobTooLargeError struct { + Size, Limit int64 } -// GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string -func (b *Blob) GetBlobContentBase64() (string, error) { - dataRc, err := b.DataAsync() - if err != nil { - return "", err - } - defer dataRc.Close() +func (b BlobTooLargeError) Error() string { + return fmt.Sprintf("blob: content larger than limit (%d > %d)", b.Size, b.Limit) +} - pr, pw := io.Pipe() - encoder := base64.NewEncoder(base64.StdEncoding, pw) - - go func() { - _, err := io.Copy(encoder, dataRc) - _ = encoder.Close() - - if err != nil { - _ = pw.CloseWithError(err) - } else { - _ = pw.Close() +// GetContentBase64 Reads the content of the blob and returns it as base64 encoded string. +// Returns [BlobTooLargeError] if the (unencoded) content is larger than the limit. +func (b *Blob) GetContentBase64(limit int64) (string, error) { + if b.Size() > limit { + return "", BlobTooLargeError{ + Size: b.Size(), + Limit: limit, } - }() + } - out, err := io.ReadAll(pr) + rc, size, err := b.NewTruncatedReader(limit) if err != nil { return "", err } - return string(out), nil + defer rc.Close() + + encoding := base64.StdEncoding + buf := bytes.NewBuffer(make([]byte, 0, encoding.EncodedLen(int(size)))) + + encoder := base64.NewEncoder(encoding, buf) + + if _, err := io.Copy(encoder, rc); err != nil { + return "", err + } + if err := encoder.Close(); err != nil { + return "", err + } + + return buf.String(), nil } // GuessContentType guesses the content type of the blob. @@ -236,7 +220,7 @@ func (b *Blob) GuessContentType() (typesniffer.SniffedType, error) { } defer r.Close() - return typesniffer.DetectContentTypeFromReader(r) + return typesniffer.DetectContentTypeFromReader(r, b.Name()) } // GetBlob finds the blob object in the repository. diff --git a/modules/git/blob_test.go b/modules/git/blob_test.go index 54115013d3..a4b8033941 100644 --- a/modules/git/blob_test.go +++ b/modules/git/blob_test.go @@ -63,6 +63,24 @@ func TestBlob(t *testing.T) { require.Equal(t, "file2\n", r) }) + t.Run("GetContentBase64", func(t *testing.T) { + r, err := testBlob.GetContentBase64(100) + require.NoError(t, err) + require.Equal(t, "ZmlsZTIK", r) + + r, err = testBlob.GetContentBase64(-1) + require.ErrorAs(t, err, &BlobTooLargeError{}) + require.Empty(t, r) + + r, err = testBlob.GetContentBase64(4) + require.ErrorAs(t, err, &BlobTooLargeError{}) + require.Empty(t, r) + + r, err = testBlob.GetContentBase64(6) + require.NoError(t, err) + require.Equal(t, "ZmlsZTIK", r) + }) + t.Run("NewTruncatedReader", func(t *testing.T) { // read fewer than available rc, size, err := testBlob.NewTruncatedReader(100) diff --git a/modules/git/commit.go b/modules/git/commit.go index 96831e3ae4..4fb13ecd4f 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -16,8 +16,6 @@ import ( "forgejo.org/modules/log" "forgejo.org/modules/util" - - "github.com/go-git/go-git/v5/config" ) // Commit represents a git commit. @@ -29,8 +27,8 @@ type Commit struct { CommitMessage string Signature *ObjectSignature - Parents []ObjectID // ID strings - submoduleCache *ObjectCache + Parents []ObjectID // ID strings + submodules map[string]Submodule // submodule indexed by path } // Message returns the commit message. Same as retrieving CommitMessage directly. @@ -352,71 +350,9 @@ func (c *Commit) GetFileContent(filename string, limit int) (string, error) { return string(bytes), nil } -// GetSubModules get all the sub modules of current revision git tree -func (c *Commit) GetSubModules() (*ObjectCache, error) { - if c.submoduleCache != nil { - return c.submoduleCache, nil - } - - entry, err := c.GetTreeEntryByPath(".gitmodules") - if err != nil { - if _, ok := err.(ErrNotExist); ok { - return nil, nil - } - return nil, err - } - - content, err := entry.Blob().GetBlobContent(10 * 1024) - if err != nil { - return nil, err - } - - c.submoduleCache, err = parseSubmoduleContent([]byte(content)) - if err != nil { - return nil, err - } - return c.submoduleCache, nil -} - -func parseSubmoduleContent(bs []byte) (*ObjectCache, error) { - cfg := config.NewModules() - if err := cfg.Unmarshal(bs); err != nil { - return nil, err - } - submoduleCache := newObjectCache() - if len(cfg.Submodules) == 0 { - return nil, errors.New("no submodules found") - } - for _, subModule := range cfg.Submodules { - submoduleCache.Set(subModule.Path, subModule.URL) - } - - return submoduleCache, nil -} - -// GetSubModule returns the URL to the submodule according entryname -func (c *Commit) GetSubModule(entryname string) (string, error) { - modules, err := c.GetSubModules() - if err != nil { - return "", err - } - - if modules != nil { - module, has := modules.Get(entryname) - if has { - return module.(string), nil - } - } - return "", nil -} - // GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only') func (c *Commit) GetBranchName() (string, error) { - cmd := NewCommand(c.repo.Ctx, "name-rev") - if CheckGitVersionAtLeast("2.13.0") == nil { - cmd.AddArguments("--exclude", "refs/tags/*") - } - cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String()) + cmd := NewCommand(c.repo.Ctx, "name-rev", "--exclude", "refs/tags/*", "--name-only", "--no-undefined").AddDynamicArguments(c.ID.String()) data, _, err := cmd.RunStdString(&RunOpts{Dir: c.repo.Path}) if err != nil { // handle special case where git can not describe commit diff --git a/modules/git/commit_info.go b/modules/git/commit_info.go index 8d9142d362..6511a1689a 100644 --- a/modules/git/commit_info.go +++ b/modules/git/commit_info.go @@ -15,9 +15,9 @@ import ( // CommitInfo describes the first commit with the provided entry type CommitInfo struct { - Entry *TreeEntry - Commit *Commit - SubModuleFile *SubModuleFile + Entry *TreeEntry + Commit *Commit + Submodule Submodule } // GetCommitsInfo gets information of all commits that are corresponding to these entries @@ -71,19 +71,18 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath } // If the entry if a submodule add a submodule file for this - if entry.IsSubModule() { + if entry.IsSubmodule() { var fullPath string if len(treePath) > 0 { fullPath = treePath + "/" + entry.Name() } else { fullPath = entry.Name() } - subModuleURL, err := commit.GetSubModule(fullPath) + submodule, err := commit.GetSubmodule(fullPath, entry) if err != nil { return nil, nil, err } - subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String()) - commitsInfo[i].SubModuleFile = subModuleFile + commitsInfo[i].Submodule = submodule } } diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go index 484827149c..ee57a735e6 100644 --- a/modules/git/commit_test.go +++ b/modules/git/commit_test.go @@ -436,33 +436,3 @@ func TestGetAllBranches(t *testing.T) { assert.Equal(t, []string{"branch1", "branch2", "master"}, branches) } - -func Test_parseSubmoduleContent(t *testing.T) { - submoduleFiles := []struct { - fileContent string - expectedPath string - expectedURL string - }{ - { - fileContent: `[submodule "jakarta-servlet"] -url = ../../ALP-pool/jakarta-servlet -path = jakarta-servlet`, - expectedPath: "jakarta-servlet", - expectedURL: "../../ALP-pool/jakarta-servlet", - }, - { - fileContent: `[submodule "jakarta-servlet"] -path = jakarta-servlet -url = ../../ALP-pool/jakarta-servlet`, - expectedPath: "jakarta-servlet", - expectedURL: "../../ALP-pool/jakarta-servlet", - }, - } - for _, kase := range submoduleFiles { - submodule, err := parseSubmoduleContent([]byte(kase.fileContent)) - require.NoError(t, err) - v, ok := submodule.Get(kase.expectedPath) - assert.True(t, ok) - assert.Equal(t, kase.expectedURL, v) - } -} diff --git a/modules/git/diff_compare.go b/modules/git/diff_compare.go new file mode 100644 index 0000000000..0eba8cb541 --- /dev/null +++ b/modules/git/diff_compare.go @@ -0,0 +1,56 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later +package git + +import ( + "bytes" + "context" + "fmt" + "os" + + "forgejo.org/modules/log" + "forgejo.org/modules/util" +) + +// CheckIfDiffDiffers returns if the diff of the newCommitID and +// oldCommitID with the merge base of the base branch has changed. +// +// Informally it checks if the following two diffs are exactly the same in their +// contents, thus ignoring different commit IDs, headers and messages: +// 1. git diff --merge-base baseReference newCommitID +// 2. git diff --merge-base baseReference oldCommitID +func (repo *Repository) CheckIfDiffDiffers(base, oldCommitID, newCommitID string, env []string) (hasChanged bool, err error) { + cmd := NewCommand(repo.Ctx, "diff", "--name-only", "-z").AddDynamicArguments(newCommitID, oldCommitID, base) + stdoutReader, stdoutWriter, err := os.Pipe() + if err != nil { + return false, fmt.Errorf("unable to open pipe for to run diff: %w", err) + } + + stderr := new(bytes.Buffer) + if err := cmd.Run(&RunOpts{ + Dir: repo.Path, + Stdout: stdoutWriter, + Stderr: stderr, + PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { + _ = stdoutWriter.Close() + defer func() { + _ = stdoutReader.Close() + }() + return util.IsEmptyReader(stdoutReader) + }, + }); err != nil { + if err == util.ErrNotEmpty { + return true, nil + } + err = ConcatenateError(err, stderr.String()) + + log.Error("Unable to run git diff on %s %s %s in %q: Error: %v", + newCommitID, oldCommitID, base, + repo.Path, + err) + + return false, fmt.Errorf("Unable to run git diff --name-only -z %s %s %s: %w", newCommitID, oldCommitID, base, err) + } + + return false, nil +} diff --git a/modules/git/diff_compare_test.go b/modules/git/diff_compare_test.go new file mode 100644 index 0000000000..433497b5c4 --- /dev/null +++ b/modules/git/diff_compare_test.go @@ -0,0 +1,421 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later +package git + +import ( + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCheckIfDiffDiffers(t *testing.T) { + tmpDir := t.TempDir() + + err := InitRepository(t.Context(), tmpDir, false, Sha1ObjectFormat.Name()) + require.NoError(t, err) + + gitRepo, err := openRepositoryWithDefaultContext(tmpDir) + require.NoError(t, err) + defer gitRepo.Close() + + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "main").Run(&RunOpts{Dir: tmpDir})) + + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("aaa"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "initial commit").Run(&RunOpts{Dir: tmpDir})) + + t.Run("Simple fast-forward", func(t *testing.T) { + // Check that A--B--C, where A is the base branch. + + t.Run("Different diff", func(t *testing.T) { + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "a-1", "main").Run(&RunOpts{Dir: tmpDir})) + + // B commit + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "a-2").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("ccc"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("main", "a-1", "a-2", nil) + require.NoError(t, err) + assert.True(t, changed) + }) + + t.Run("Same diff", func(t *testing.T) { + // Because C is a empty commit, the diff does not differ relative to the + // base branch. + + // B commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "a-3", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "a-4").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "--allow-empty", "-m", "No changes to the README").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("main", "a-3", "a-4", nil) + require.NoError(t, err) + assert.False(t, changed) + }) + }) + + t.Run("Merge-base is base reference", func(t *testing.T) { + // B + // / + // A + // \ + // C + t.Run("Different diff", func(t *testing.T) { + // B commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "b-1", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changed to the README").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "b-2", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("ccc"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changed to the README").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("main", "b-1", "b-2", nil) + require.NoError(t, err) + assert.True(t, changed) + }) + + t.Run("Same diff", func(t *testing.T) { + // B commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "b-3", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changed to the README").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "b-4", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changed to the README").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("main", "b-3", "b-4", nil) + require.NoError(t, err) + assert.False(t, changed) + }) + }) + + t.Run("Merge-base is different", func(t *testing.T) { + // B + // / + // A--D + // \ + // C + // Where D is the base reference. + + // D commit, where A is `main`. + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "main-D", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "FUNFACT"), []byte("Smithy was the runner up to be Forgejo's name"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "FUNFACT").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Forgejo history").Run(&RunOpts{Dir: tmpDir})) + + t.Run("Different diff", func(t *testing.T) { + // B commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "c-1", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "c-2", "main-D").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "FUNFACT"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "FUNFACT").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the funfact").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("main-D", "c-1", "c-2", nil) + require.NoError(t, err) + assert.True(t, changed) + }) + + t.Run("Same diff", func(t *testing.T) { + // B commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "c-3", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "c-4", "main-D").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("main-D", "c-3", "c-4", nil) + require.NoError(t, err) + assert.False(t, changed) + }) + }) + + t.Run("Merge commit", func(t *testing.T) { + // B + // / + // A - D + // \ + // C + // + // From B, it merges D where E is the merge commit : + // B---E + // / / + // A---D + // \ + // C + + t.Run("Different diff", func(t *testing.T) { + // B commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "d-1", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + // E commit + require.NoError(t, NewCommand(t.Context(), "merge", "--no-ff", "main-D").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "d-2", "main-D").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "FUNFACT"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "FUNFACT").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the funfact").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("main-D", "d-1", "d-2", nil) + require.NoError(t, err) + assert.True(t, changed) + }) + + t.Run("Same diff", func(t *testing.T) { + // B commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "d-3", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + // Merges D. + require.NoError(t, NewCommand(t.Context(), "merge", "--no-ff", "main-D").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "d-4", "main-D").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("main-D", "d-3", "d-4", nil) + require.NoError(t, err) + assert.False(t, changed) + }) + }) + + t.Run("Non-typical rebase", func(t *testing.T) { + // B + // / + // A--D + // \ + // C + // Where D is the base reference. + // B was rebased onto D, which produced C. + // B and D made the same change to same file. + + // D commit. + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "main-D-2", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "FUNFACT"), []byte("Smithy was the runner up to be Forgejo's name"), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("ccc"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "FUNFACT", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Forgejo history").Run(&RunOpts{Dir: tmpDir})) + + // B commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "e-1", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "CONTACT"), []byte("@example.com"), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("ccc"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README", "CONTACT").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the contact and README").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "e-2").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "rebase", "main-D-2").Run(&RunOpts{Dir: tmpDir})) + + // The diff changed, because it no longers shows the change made to `README`. + changed, err := gitRepo.CheckIfDiffDiffers("main-D-2", "e-1", "e-2", nil) + require.NoError(t, err) + assert.False(t, changed) // This should be true. + }) + + t.Run("Directory", func(t *testing.T) { + // B + // / + // A + // \ + // C + t.Run("Same directory", func(t *testing.T) { + t.Run("Different diff", func(t *testing.T) { + // B commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "f-1", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "docs"), 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "docs", "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "docs/README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changed to the README").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "f-2", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "docs"), 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "docs", "README"), []byte("ccc"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "docs/README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changed to the README").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("main", "f-1", "f-2", nil) + require.NoError(t, err) + assert.True(t, changed) + }) + + t.Run("Same diff", func(t *testing.T) { + // B commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "f-3", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "docs"), 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "docs", "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "docs/README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changed to the README").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "f-4", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "docs"), 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "docs", "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "docs/README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("main", "f-3", "f-4", nil) + require.NoError(t, err) + assert.False(t, changed) + }) + }) + + t.Run("Different directory", func(t *testing.T) { + // B commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "f-5", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "docs"), 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "docs", "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "docs/README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "f-6", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "documentation"), 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "documentation", "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "documentation/README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("main", "f-5", "f-6", nil) + require.NoError(t, err) + assert.True(t, changed) + }) + + t.Run("Directory and file", func(t *testing.T) { + // B commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "f-7", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "docs"), 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "docs", "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "docs/README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "f-8", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "README"), []byte("bbb"), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "README").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Changes to the README").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("main", "f-7", "f-8", nil) + require.NoError(t, err) + assert.True(t, changed) + }) + }) + + t.Run("Rebase", func(t *testing.T) { + // B + // / + // A--D + // \ + // C + // Where D is the base reference. + // B was rebased onto D, which produced C. + // B and D made different (non conflicting) changes to same file. + + // A commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "main-3", "main").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "REBASE"), bytes.Repeat([]byte{'b', 'b', 'b', '\n'}, 100), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "REBASE").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Rebasing is fun").Run(&RunOpts{Dir: tmpDir})) + + // B commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "g-1", "main-3").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "REBASE"), append(bytes.Repeat([]byte{'b', 'b', 'b', '\n'}, 100), 'a', 'a', 'a'), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "REBASE").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Rebasing is fun").Run(&RunOpts{Dir: tmpDir})) + + // D commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "g-2", "main-3").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "REBASE"), append([]byte{'a', 'a', 'a'}, bytes.Repeat([]byte{'b', 'b', 'b', '\n'}, 99)...), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "REBASE").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Rebasing is fun").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "g-3", "g-1").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "rebase", "g-2").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("g-2", "g-1", "g-3", nil) + require.NoError(t, err) + assert.True(t, changed) + }) + + t.Run("Rebasing change not shown", func(t *testing.T) { + // B + // / + // A--D + // \ + // C + // Where D is the base reference. + // B was rebased onto D, which produced C. + // B and D made different (non conflicting) changes to same file. + + // A commit + require.NoError(t, NewCommand(t.Context(), "switch", "--orphan", "main-4").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "A"), 0o700)) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "A", "a"), bytes.Repeat([]byte{'A', 'A', 'A', '\n'}, 100), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "A/a").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Just wondering").Run(&RunOpts{Dir: tmpDir})) + + // B commit + // Changes last line. + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "h-1").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "A", "a"), append(bytes.Repeat([]byte{'A', 'A', 'A', '\n'}, 99), 'B', 'B', 'B', '\n'), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "A/a").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Just wondering").Run(&RunOpts{Dir: tmpDir})) + + // D commit + // Changes first line. + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "h-2", "main-4").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "A", "a"), append([]byte{'B', 'B', 'B', '\n'}, bytes.Repeat([]byte{'A', 'A', 'A', '\n'}, 99)...), 0o600)) + require.NoError(t, NewCommand(t.Context(), "add", "A/a").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "commit", "-m", "Just wondering").Run(&RunOpts{Dir: tmpDir})) + + // C commit + require.NoError(t, NewCommand(t.Context(), "switch", "-c", "h-3").Run(&RunOpts{Dir: tmpDir})) + require.NoError(t, NewCommand(t.Context(), "rebase", "h-2").Run(&RunOpts{Dir: tmpDir})) + + changed, err := gitRepo.CheckIfDiffDiffers("h-2", "h-1", "h-3", nil) + require.NoError(t, err) + + assert.False(t, changed) + }) +} diff --git a/modules/git/git.go b/modules/git/git.go index 1dfd0b5134..851b090b53 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -23,7 +23,7 @@ import ( ) // RequiredVersion is the minimum Git version required -const RequiredVersion = "2.0.0" +const RequiredVersion = "2.34.1" var ( // GitExecutable is the command name of git @@ -33,7 +33,6 @@ var ( // DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx DefaultContext context.Context - SupportProcReceive bool // >= 2.29 SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an โ€˜experimental curiosityโ€™ InvertedGitFlushEnv bool // 2.43.1 SupportCheckAttrOnBare bool // >= 2.40 @@ -113,7 +112,7 @@ func VersionInfo() string { format := "%s" args := []any{GitVersion.Original()} // Since git wire protocol has been released from git v2.18 - if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil { + if setting.Git.EnableAutoGitWireProtocol { format += ", Wire Protocol %s Enabled" args = append(args, "Version 2") // for focus color } @@ -172,16 +171,13 @@ func InitFull(ctx context.Context) (err error) { _ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg")) } - // Since git wire protocol has been released from git v2.18 - if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil { + if setting.Git.EnableAutoGitWireProtocol { globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2") } // Explicitly disable credential helper, otherwise Git credentials might leak - if CheckGitVersionAtLeast("2.9") == nil { - globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") - } - SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil + globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") + SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil SupportCheckAttrOnBare = CheckGitVersionAtLeast("2.40") == nil if SupportHashSha256 { @@ -195,9 +191,6 @@ func InitFull(ctx context.Context) (err error) { SupportGrepMaxCount = CheckGitVersionAtLeast("2.38") == nil if setting.LFS.StartServer { - if CheckGitVersionAtLeast("2.1.2") != nil { - return errors.New("LFS server support requires Git >= 2.1.2") - } globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=") } @@ -234,38 +227,28 @@ func syncGitConfig() (err error) { } } - // Set git some configurations - these must be set to these values for gitea to work correctly + // Set git some configurations - these must be set to these values for forgejo to work correctly if err := configSet("core.quotePath", "false"); err != nil { return err } - if CheckGitVersionAtLeast("2.10") == nil { - if err := configSet("receive.advertisePushOptions", "true"); err != nil { - return err - } + if err := configSet("receive.advertisePushOptions", "true"); err != nil { + return err } - if CheckGitVersionAtLeast("2.18") == nil { - if err := configSet("core.commitGraph", "true"); err != nil { - return err - } - if err := configSet("gc.writeCommitGraph", "true"); err != nil { - return err - } - if err := configSet("fetch.writeCommitGraph", "true"); err != nil { - return err - } + if err := configSet("core.commitGraph", "true"); err != nil { + return err + } + if err := configSet("gc.writeCommitGraph", "true"); err != nil { + return err + } + if err := configSet("fetch.writeCommitGraph", "true"); err != nil { + return err } - if SupportProcReceive { - // set support for AGit flow - if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil { - return err - } - } else { - if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil { - return err - } + // set support for AGit flow + if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil { + return err } // Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user @@ -284,11 +267,6 @@ func syncGitConfig() (err error) { switch setting.Repository.Signing.Format { case "ssh": - // First do a git version check. - if CheckGitVersionAtLeast("2.34.0") != nil { - return errors.New("ssh signing requires Git >= 2.34.0") - } - // Get the ssh-keygen binary that Git will use. // This can be overridden in app.ini in [git.config] section, so we must // query this information. @@ -325,8 +303,7 @@ func syncGitConfig() (err error) { } } - // By default partial clones are disabled, enable them from git v2.22 - if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil { + if !setting.Git.DisablePartialClone { if err = configSet("uploadpack.allowfilter", "true"); err != nil { return err } diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 01200dba68..38d4db169c 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -14,7 +14,6 @@ import ( "forgejo.org/modules/test" "forgejo.org/modules/util" - "github.com/hashicorp/go-version" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -105,10 +104,6 @@ func TestSyncConfigGPGFormat(t *testing.T) { }) t.Run("SSH format", func(t *testing.T) { - if CheckGitVersionAtLeast("2.34.0") != nil { - t.SkipNow() - } - r, err := os.OpenRoot(t.TempDir()) require.NoError(t, err) f, err := r.OpenFile("ssh-keygen", os.O_CREATE|os.O_TRUNC, 0o700) @@ -121,13 +116,6 @@ func TestSyncConfigGPGFormat(t *testing.T) { assert.True(t, gitConfigContains("[gpg]")) assert.True(t, gitConfigContains("format = ssh")) - t.Run("Old version", func(t *testing.T) { - oldVersion, err := version.NewVersion("2.33.0") - require.NoError(t, err) - defer test.MockVariableValue(&GitVersion, oldVersion)() - require.ErrorContains(t, syncGitConfig(), "ssh signing requires Git >= 2.34.0") - }) - t.Run("No ssh-keygen binary", func(t *testing.T) { require.NoError(t, r.Remove("ssh-keygen")) require.ErrorContains(t, syncGitConfig(), "git signing requires a ssh-keygen binary") diff --git a/modules/git/grep.go b/modules/git/grep.go index 3703b13660..b5471b8f6c 100644 --- a/modules/git/grep.go +++ b/modules/git/grep.go @@ -36,6 +36,7 @@ const ( RegExpGrepMode ) +// llu:TrKeysSuffix search. var GrepSearchOptions = [3]string{"exact", "union", "regexp"} type GrepOptions struct { diff --git a/modules/git/parse.go b/modules/git/parse.go index 6bc32057a7..c7b84d7198 100644 --- a/modules/git/parse.go +++ b/modules/git/parse.go @@ -10,8 +10,6 @@ import ( "io" "strconv" "strings" - - "forgejo.org/modules/log" ) // ParseTreeEntries parses the output of a `git ls-tree -l` command. @@ -55,19 +53,9 @@ func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) { entry.sized = true } - switch string(entryMode) { - case "100644": - entry.entryMode = EntryModeBlob - case "100755": - entry.entryMode = EntryModeExec - case "120000": - entry.entryMode = EntryModeSymlink - case "160000": - entry.entryMode = EntryModeCommit - case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons - entry.entryMode = EntryModeTree - default: - return nil, fmt.Errorf("unknown type: %v", string(entryMode)) + entry.entryMode, err = parseMode(string(entryMode)) + if err != nil { + return nil, err } entry.ID, err = NewIDFromString(string(entryObjectID)) @@ -108,23 +96,10 @@ loop: sz -= int64(count) entry := new(TreeEntry) entry.ptree = ptree - - switch string(mode) { - case "100644": - entry.entryMode = EntryModeBlob - case "100755": - entry.entryMode = EntryModeExec - case "120000": - entry.entryMode = EntryModeSymlink - case "160000": - entry.entryMode = EntryModeCommit - case "40000", "40755": // git uses 40000 for tree object, but some users may get 40755 for unknown reasons - entry.entryMode = EntryModeTree - default: - log.Debug("Unknown mode: %v", string(mode)) - return nil, fmt.Errorf("unknown mode: %v", string(mode)) + entry.entryMode, err = parseMode(string(mode)) + if err != nil { + return nil, err } - entry.ID = objectFormat.MustID(sha) entry.name = string(fname) entries = append(entries, entry) @@ -135,3 +110,31 @@ loop: return entries, nil } + +// Parse the file mode, we cannot hardcode the modes that we expect for +// a variety of reasons (that is not known to us) the permissions bits +// of files can vary, usually the result because of tooling that uses Git in +// a funny way. So we have to parse the mode as a integer and do bit tricks. +func parseMode(modeStr string) (EntryMode, error) { + mode, err := strconv.ParseUint(modeStr, 8, 64) + if err != nil { + return 0, fmt.Errorf("cannot parse mode: %v", err) + } + + switch mode & 0o170000 { + case 0o040000: + return EntryModeTree, nil + case 0o120000: + return EntryModeSymlink, nil + case 0o160000: + return EntryModeCommit, nil + case 0o100000: + // Check for the permission bit on the owner. + if mode&0o100 == 0o100 { + return EntryModeExec, nil + } + return EntryModeBlob, nil + } + + return 0, fmt.Errorf("unknown mode: %o", mode) +} diff --git a/modules/git/parse_test.go b/modules/git/parse_test.go index 03f359f6c1..502adab4da 100644 --- a/modules/git/parse_test.go +++ b/modules/git/parse_test.go @@ -101,3 +101,38 @@ func TestParseTreeEntriesInvalid(t *testing.T) { require.Error(t, err) assert.Empty(t, entries) } + +func TestParseMode(t *testing.T) { + ok := func(t *testing.T, mode string, entry EntryMode) { + t.Helper() + actualEntry, err := parseMode(mode) + require.NoError(t, err) + assert.Equal(t, entry, actualEntry) + } + + fail := func(t *testing.T, mode string) { + t.Helper() + entry, err := parseMode(mode) + require.Error(t, err) + assert.Zero(t, entry) + } + + ok(t, "100644", EntryModeBlob) + ok(t, "100755", EntryModeExec) + ok(t, "100754", EntryModeExec) + ok(t, "100700", EntryModeExec) + ok(t, "100744", EntryModeExec) + ok(t, "120000", EntryModeSymlink) + ok(t, "120644", EntryModeSymlink) + ok(t, "160000", EntryModeCommit) + ok(t, "160644", EntryModeCommit) + ok(t, "040000", EntryModeTree) + ok(t, "040755", EntryModeTree) + ok(t, "040775", EntryModeTree) + ok(t, "040754", EntryModeTree) + + fail(t, "not-a-number") + fail(t, "000000") + fail(t, "400000") + fail(t, "111111") +} diff --git a/modules/git/pipeline/revlist.go b/modules/git/pipeline/revlist.go index f39b7113bb..1ee8921854 100644 --- a/modules/git/pipeline/revlist.go +++ b/modules/git/pipeline/revlist.go @@ -16,26 +16,6 @@ import ( "forgejo.org/modules/log" ) -// RevListAllObjects runs rev-list --objects --all and writes to a pipewriter -func RevListAllObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.WaitGroup, basePath string, errChan chan<- error) { - defer wg.Done() - defer revListWriter.Close() - - stderr := new(bytes.Buffer) - var errbuf strings.Builder - cmd := git.NewCommand(ctx, "rev-list", "--objects", "--all") - if err := cmd.Run(&git.RunOpts{ - Dir: basePath, - Stdout: revListWriter, - Stderr: stderr, - }); err != nil { - log.Error("git rev-list --objects --all [%s]: %v - %s", basePath, err, errbuf.String()) - err = fmt.Errorf("git rev-list --objects --all [%s]: %w - %s", basePath, err, errbuf.String()) - _ = revListWriter.CloseWithError(err) - errChan <- err - } -} - // RevListObjects run rev-list --objects from headSHA to baseSHA func RevListObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.WaitGroup, tmpBasePath, headSHA, baseSHA string, errChan chan<- error) { defer wg.Done() diff --git a/modules/git/pushoptions/pushoptions.go b/modules/git/pushoptions/pushoptions.go index 9709a8be79..e96ba0a339 100644 --- a/modules/git/pushoptions/pushoptions.go +++ b/modules/git/pushoptions/pushoptions.go @@ -4,6 +4,7 @@ package pushoptions import ( + "encoding/base64" "fmt" "os" "strconv" @@ -109,5 +110,22 @@ func (o gitPushOptions) GetBool(key Key, def bool) bool { func (o gitPushOptions) GetString(key Key) (string, bool) { val, ok := o[string(key)] - return val, ok + if !ok { + return "", false + } + + // If the value is prefixed with `{base64}` then everything after that is very + // likely to be encoded via base64. + base64Value, found := strings.CutPrefix(val, "{base64}") + if !found { + return val, true + } + + value, err := base64.StdEncoding.DecodeString(base64Value) + if err != nil { + // Not valid base64? Return the original value. + return val, true + } + + return string(value), true } diff --git a/modules/git/pushoptions/pushoptions_test.go b/modules/git/pushoptions/pushoptions_test.go index 1cb36d9d1e..d7c50649d0 100644 --- a/modules/git/pushoptions/pushoptions_test.go +++ b/modules/git/pushoptions/pushoptions_test.go @@ -4,6 +4,7 @@ package pushoptions import ( + "encoding/base64" "fmt" "testing" @@ -92,6 +93,23 @@ func TestParse(t *testing.T) { assert.False(t, options.Parse("unknown=value")) assert.True(t, options.Empty()) }) + + t.Run("Base64 values", func(t *testing.T) { + options := New() + + description := `I contain +a +line` + assert.True(t, options.Parse(fmt.Sprintf("%s={base64}%s", AgitDescription, base64.StdEncoding.EncodeToString([]byte(description))))) + val, ok := options.GetString(AgitDescription) + assert.True(t, ok) + assert.Equal(t, description, val) + + assert.True(t, options.Parse(fmt.Sprintf("%s={base64}fooled you", AgitTitle))) + val, ok = options.GetString(AgitTitle) + assert.True(t, ok) + assert.Equal(t, "{base64}fooled you", val) + }) } func TestReadEnv(t *testing.T) { diff --git a/modules/git/remote.go b/modules/git/remote.go index fb66d76ff0..83a02fe2be 100644 --- a/modules/git/remote.go +++ b/modules/git/remote.go @@ -12,14 +12,7 @@ import ( // GetRemoteAddress returns remote url of git repository in the repoPath with special remote name func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) { - var cmd *Command - if CheckGitVersionAtLeast("2.7") == nil { - cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName) - } else { - cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url") - } - - result, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) + result, _, err := NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName).RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return "", err } diff --git a/modules/git/repo.go b/modules/git/repo.go index 23e9337615..4f2b05bca5 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -18,6 +18,7 @@ import ( "strings" "time" + "forgejo.org/modules/log" "forgejo.org/modules/proxy" "forgejo.org/modules/setting" "forgejo.org/modules/util" @@ -160,24 +161,89 @@ func CloneWithArgs(ctx context.Context, args TrustedCmdArgs, from, to string, op if len(opts.Branch) > 0 { cmd.AddArguments("-b").AddDynamicArguments(opts.Branch) } - cmd.AddDashesAndList(from, to) - if strings.Contains(from, "://") && strings.Contains(from, "@") { - cmd.SetDescription(fmt.Sprintf("clone branch %s from %s to %s (shared: %t, mirror: %t, depth: %d)", opts.Branch, util.SanitizeCredentialURLs(from), to, opts.Shared, opts.Mirror, opts.Depth)) - } else { - cmd.SetDescription(fmt.Sprintf("clone branch %s from %s to %s (shared: %t, mirror: %t, depth: %d)", opts.Branch, from, to, opts.Shared, opts.Mirror, opts.Depth)) + envs := os.Environ() + parsedFromURL, err := url.Parse(from) + if err == nil { + envs = proxy.EnvWithProxy(parsedFromURL) } + fromURL := from + sanitizedFrom := from + + // If the clone URL has credentials, sanitize it and store the credentials in + // a temporary file that git will access. + if strings.Contains(from, "://") && strings.Contains(from, "@") { + sanitizedFrom = util.SanitizeCredentialURLs(from) + if parsedFromURL != nil { + if pwd, has := parsedFromURL.User.Password(); has { + parsedFromURL.User = url.User(parsedFromURL.User.Username()) + fromURL = parsedFromURL.String() + + credentialsFile, err := os.CreateTemp(os.TempDir(), "forgejo-clone-credentials") + if err != nil { + return err + } + credentialsPath := credentialsFile.Name() + + defer func() { + _ = credentialsFile.Close() + if err := util.Remove(credentialsPath); err != nil { + log.Warn("Unable to remove temporary file %q: %v", credentialsPath, err) + } + }() + + // Make it read-write. + if err := credentialsFile.Chmod(0o600); err != nil { + return err + } + + // Write the password. + if _, err := fmt.Fprint(credentialsFile, pwd); err != nil { + return err + } + + askpassFile, err := os.CreateTemp(os.TempDir(), "forgejo-askpass") + if err != nil { + return err + } + askpassPath := askpassFile.Name() + + defer func() { + _ = askpassFile.Close() + if err := util.Remove(askpassPath); err != nil { + log.Warn("Unable to remove temporary file %q: %v", askpassPath, err) + } + }() + + // Make it executable. + if err := askpassFile.Chmod(0o700); err != nil { + return err + } + + // Write the password script. + if _, err := fmt.Fprintf(askpassFile, "exec cat %s", credentialsPath); err != nil { + return err + } + + // Close it, so that Git can use it and no busy errors arise. + _ = askpassFile.Close() + _ = credentialsFile.Close() + + // Use environments to specify that git should ask for credentials, this + // takes precedences over anything else https://git-scm.com/docs/gitcredentials#_requesting_credentials. + envs = append(envs, "GIT_ASKPASS="+askpassPath) + } + } + } + + cmd.SetDescription(fmt.Sprintf("clone branch %s from %s to %s (shared: %t, mirror: %t, depth: %d)", opts.Branch, sanitizedFrom, to, opts.Shared, opts.Mirror, opts.Depth)) + cmd.AddDashesAndList(fromURL, to) + if opts.Timeout <= 0 { opts.Timeout = -1 } - envs := os.Environ() - u, err := url.Parse(from) - if err == nil { - envs = proxy.EnvWithProxy(u) - } - stderr := new(bytes.Buffer) if err = cmd.Run(&RunOpts{ Timeout: opts.Timeout, diff --git a/modules/git/repo_attribute_test.go b/modules/git/repo_attribute_test.go index c69382e245..3d2c845fa0 100644 --- a/modules/git/repo_attribute_test.go +++ b/modules/git/repo_attribute_test.go @@ -5,7 +5,6 @@ package git import ( "context" - "fmt" "io" "io/fs" "os" @@ -197,7 +196,7 @@ func TestGitAttributeCheckerError(t *testing.T) { path := t.TempDir() // we can't use unittest.CopyDir because of an import cycle (git.Init in unittest) - require.NoError(t, CopyFS(path, os.DirFS(filepath.Join(testReposDir, "language_stats_repo")))) + require.NoError(t, os.CopyFS(path, os.DirFS(filepath.Join(testReposDir, "language_stats_repo")))) gitRepo, err := openRepositoryWithDefaultContext(path) require.NoError(t, err) @@ -324,32 +323,3 @@ func TestGitAttributeCheckerError(t *testing.T) { require.ErrorIs(t, err, fs.ErrClosed) }) } - -// CopyFS is adapted from https://github.com/golang/go/issues/62484 -// which should be available with go1.23 -func CopyFS(dir string, fsys fs.FS) error { - return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, _ error) error { - targ := filepath.Join(dir, filepath.FromSlash(path)) - if d.IsDir() { - return os.MkdirAll(targ, 0o777) - } - r, err := fsys.Open(path) - if err != nil { - return err - } - defer r.Close() - info, err := r.Stat() - if err != nil { - return err - } - w, err := os.OpenFile(targ, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o666|info.Mode()&0o777) - if err != nil { - return err - } - if _, err := io.Copy(w, r); err != nil { - w.Close() - return fmt.Errorf("copying %s: %v", path, err) - } - return w.Close() - }) -} diff --git a/modules/git/repo_blame.go b/modules/git/repo_blame.go index 139cdd7be9..d812354af5 100644 --- a/modules/git/repo_blame.go +++ b/modules/git/repo_blame.go @@ -4,20 +4,46 @@ package git import ( + "errors" "fmt" + "regexp" +) + +var ( + ErrBlameFileDoesNotExist = errors.New("the blamed file does not exist") + ErrBlameFileNotEnoughLines = errors.New("the blamed file has not enough lines") + + notEnoughLinesRe = regexp.MustCompile(`^fatal: file .+ has only \d+ lines?\n$`) ) // LineBlame returns the latest commit at the given line -func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) { - res, _, err := NewCommand(repo.Ctx, "blame"). +func (repo *Repository) LineBlame(revision, file string, line uint64) (*Commit, error) { + res, _, gitErr := NewCommand(repo.Ctx, "blame"). AddOptionFormat("-L %d,%d", line, line). AddOptionValues("-p", revision). - AddDashesAndList(file).RunStdString(&RunOpts{Dir: path}) + AddDashesAndList(file).RunStdString(&RunOpts{Dir: repo.Path}) + if gitErr != nil { + stdErr := gitErr.Stderr() + + if stdErr == fmt.Sprintf("fatal: no such path %s in %s\n", file, revision) { + return nil, ErrBlameFileDoesNotExist + } + if notEnoughLinesRe.MatchString(stdErr) { + return nil, ErrBlameFileNotEnoughLines + } + + return nil, gitErr + } + + objectFormat, err := repo.GetObjectFormat() if err != nil { return nil, err } - if len(res) < 40 { - return nil, fmt.Errorf("invalid result of blame: %s", res) + + objectIDLen := objectFormat.FullLength() + if len(res) < objectIDLen { + return nil, fmt.Errorf("output of blame is invalid, cannot contain commit ID: %s", res) } - return repo.GetCommit(res[:40]) + + return repo.GetCommit(res[:objectIDLen]) } diff --git a/modules/git/repo_blame_test.go b/modules/git/repo_blame_test.go new file mode 100644 index 0000000000..126b95386d --- /dev/null +++ b/modules/git/repo_blame_test.go @@ -0,0 +1,52 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package git + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLineBlame(t *testing.T) { + t.Run("SHA1", func(t *testing.T) { + repo, err := OpenRepository(t.Context(), filepath.Join(testReposDir, "repo1_bare")) + require.NoError(t, err) + defer repo.Close() + + commit, err := repo.LineBlame("HEAD", "foo/link_short", 1) + require.NoError(t, err) + assert.Equal(t, "37991dec2c8e592043f47155ce4808d4580f9123", commit.ID.String()) + + commit, err = repo.LineBlame("HEAD", "foo/link_short", 512) + require.ErrorIs(t, err, ErrBlameFileNotEnoughLines) + assert.Nil(t, commit) + + commit, err = repo.LineBlame("HEAD", "non-existent/path", 512) + require.ErrorIs(t, err, ErrBlameFileDoesNotExist) + assert.Nil(t, commit) + }) + + t.Run("SHA256", func(t *testing.T) { + skipIfSHA256NotSupported(t) + + repo, err := OpenRepository(t.Context(), filepath.Join(testReposDir, "repo1_bare_sha256")) + require.NoError(t, err) + defer repo.Close() + + commit, err := repo.LineBlame("HEAD", "foo/link_short", 1) + require.NoError(t, err) + assert.Equal(t, "6aae864a3d1d0d6a5be0cc64028c1e7021e2632b031fd8eb82afc5a283d1c3d1", commit.ID.String()) + + commit, err = repo.LineBlame("HEAD", "foo/link_short", 512) + require.ErrorIs(t, err, ErrBlameFileNotEnoughLines) + assert.Nil(t, commit) + + commit, err = repo.LineBlame("HEAD", "non-existent/path", 512) + require.ErrorIs(t, err, ErrBlameFileDoesNotExist) + assert.Nil(t, commit) + }) +} diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index 3a9aa3e4e6..1e38bf2946 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -19,15 +19,9 @@ import ( // BranchPrefix base dir of the branch information file store on git const BranchPrefix = "refs/heads/" -// IsReferenceExist returns true if given reference exists in the repository. -func IsReferenceExist(ctx context.Context, repoPath, name string) bool { - _, _, err := NewCommand(ctx, "show-ref", "--verify").AddDashesAndList(name).RunStdString(&RunOpts{Dir: repoPath}) - return err == nil -} - // IsBranchExist returns true if given branch exists in the repository. func IsBranchExist(ctx context.Context, repoPath, name string) bool { - return IsReferenceExist(ctx, repoPath, BranchPrefix+name) + return NewCommand(ctx, "show-ref", "--verify", "--quiet").AddDashesAndList(BranchPrefix+name).Run(&RunOpts{Dir: repoPath}) == nil } // Branch represents a Git branch. diff --git a/modules/git/repo_branch_test.go b/modules/git/repo_branch_test.go index 1e0fea7cd4..e61ea6f5d7 100644 --- a/modules/git/repo_branch_test.go +++ b/modules/git/repo_branch_test.go @@ -1,4 +1,5 @@ // Copyright 2018 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package git @@ -195,3 +196,17 @@ func TestRepository_IsReferenceExist(t *testing.T) { }) } } + +func TestIsBranchExist(t *testing.T) { + repo1Path := filepath.Join(testReposDir, "repo1_bare") + + assert.True(t, IsBranchExist(t.Context(), repo1Path, "branch1")) + assert.True(t, IsBranchExist(t.Context(), repo1Path, "branch2")) + assert.True(t, IsBranchExist(t.Context(), repo1Path, "master")) + + assert.False(t, IsBranchExist(t.Context(), repo1Path, "HEAD")) + assert.False(t, IsBranchExist(t.Context(), repo1Path, "153f451b9ee7fa1da317ab17a127e9fd9d384310")) + assert.False(t, IsBranchExist(t.Context(), repo1Path, "153f451b9ee7fa1da317ab17a127e9fd9d384310")) + assert.False(t, IsBranchExist(t.Context(), repo1Path, "signed-tag")) + assert.False(t, IsBranchExist(t.Context(), repo1Path, "test")) +} diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 4c8516f828..a650e597d2 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -443,53 +443,43 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit, } func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) { - if CheckGitVersionAtLeast("2.7.0") == nil { - command := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").AddOptionValues("--contains", commit.ID.String(), BranchPrefix) + command := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").AddOptionValues("--contains", commit.ID.String(), BranchPrefix) - if limit != -1 { - command = command.AddOptionFormat("--count=%d", limit) - } - - stdout, _, err := command.RunStdString(&RunOpts{Dir: repo.Path}) - if err != nil { - return nil, err - } - - branches := strings.Fields(stdout) - return branches, nil + if limit != -1 { + command = command.AddOptionFormat("--count=%d", limit) } - stdout, _, err := NewCommand(repo.Ctx, "branch").AddOptionValues("--contains", commit.ID.String()).RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err := command.RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } - refs := strings.Split(stdout, "\n") - - var max int - if len(refs) > limit { - max = limit - } else { - max = len(refs) - 1 - } - - branches := make([]string, max) - for i, ref := range refs[:max] { - parts := strings.Fields(ref) - - branches[i] = parts[len(parts)-1] - } + branches := strings.Fields(stdout) return branches, nil } -// GetCommitsFromIDs get commits from commit IDs -func (repo *Repository) GetCommitsFromIDs(commitIDs []string) []*Commit { +// GetCommitsFromIDs get commits from commit IDs. If ignoreExistence is +// specified, then commits that no longer exists are still returned but +// without any information except the ID. +func (repo *Repository) GetCommitsFromIDs(commitIDs []string, ignoreExistence bool) []*Commit { commits := make([]*Commit, 0, len(commitIDs)) for _, commitID := range commitIDs { commit, err := repo.GetCommit(commitID) if err == nil && commit != nil { commits = append(commits, commit) + } else if ignoreExistence && IsErrNotExist(err) { + // It's entirely possible the commit no longer exists, we only care + // about the status and verification. Verification is no longer possible, + // but getting the status is still possible with just the ID. We do have + // to assumme the commitID is not shortened, we cannot recover the full + // commitID. + id, err := NewIDFromString(commitID) + if err == nil { + commits = append(commits, &Commit{ + ID: id, + }) + } } } diff --git a/modules/git/repo_commit_test.go b/modules/git/repo_commit_test.go index 53760b208e..5932f31e3c 100644 --- a/modules/git/repo_commit_test.go +++ b/modules/git/repo_commit_test.go @@ -199,3 +199,40 @@ func TestCommitsByFileAndRange(t *testing.T) { assert.Len(t, commits, testCase.ExpectedCommitCount, "file: '%s', page: %d", testCase.File, testCase.Page) } } + +func TestGetCommitsFromIDs(t *testing.T) { + bareRepo1, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare")) + require.NoError(t, err) + + commitIDs := []string{"2839944139e0de9737a044f78b0e4b40d989a9e3", "2839944139e0de9737a044f78b0e4b40d989a9e4"} + + t.Run("Normal", func(t *testing.T) { + commits := bareRepo1.GetCommitsFromIDs(commitIDs, false) + if assert.Len(t, commits, 1) { + assert.Equal(t, "2839944139e0de9737a044f78b0e4b40d989a9e3", commits[0].ID.String()) + assert.Equal(t, "Example User", commits[0].Author.Name) + } + }) + + t.Run("Ignore existence", func(t *testing.T) { + commits := bareRepo1.GetCommitsFromIDs(commitIDs, true) + if assert.Len(t, commits, 2) { + assert.Equal(t, "2839944139e0de9737a044f78b0e4b40d989a9e3", commits[0].ID.String()) + assert.Equal(t, "Example User", commits[0].Author.Name) + + assert.Equal(t, "2839944139e0de9737a044f78b0e4b40d989a9e4", commits[1].ID.String()) + assert.Nil(t, commits[1].Author) + } + }) + + t.Run("Not full commit ID", func(t *testing.T) { + commits := bareRepo1.GetCommitsFromIDs(append(commitIDs, "abba"), true) + if assert.Len(t, commits, 2) { + assert.Equal(t, "2839944139e0de9737a044f78b0e4b40d989a9e3", commits[0].ID.String()) + assert.Equal(t, "Example User", commits[0].Author.Name) + + assert.Equal(t, "2839944139e0de9737a044f78b0e4b40d989a9e4", commits[1].ID.String()) + assert.Nil(t, commits[1].Author) + } + }) +} diff --git a/modules/git/repo_commitgraph.go b/modules/git/repo_commitgraph.go index 492438be37..c3647bd894 100644 --- a/modules/git/repo_commitgraph.go +++ b/modules/git/repo_commitgraph.go @@ -11,10 +11,8 @@ import ( // WriteCommitGraph write commit graph to speed up repo access // this requires git v2.18 to be installed func WriteCommitGraph(ctx context.Context, repoPath string) error { - if CheckGitVersionAtLeast("2.18") == nil { - if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil { - return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err) - } + if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil { + return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err) } return nil } diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go index 373b5befb5..94f1911c4a 100644 --- a/modules/git/repo_compare.go +++ b/modules/git/repo_compare.go @@ -183,6 +183,17 @@ func (repo *Repository) GetDiffShortStat(base, head string) (numFiles, totalAddi return numFiles, totalAdditions, totalDeletions, err } +// GetCommitStat 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}) + if err != nil { + return 0, 0, 0, err + } + + 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: diff --git a/modules/git/repo_compare_test.go b/modules/git/repo_compare_test.go index 86bd6855a7..b1ebdf6177 100644 --- a/modules/git/repo_compare_test.go +++ b/modules/git/repo_compare_test.go @@ -1,4 +1,5 @@ // Copyright 2018 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package git @@ -162,3 +163,83 @@ func TestGetCommitFilesChanged(t *testing.T) { assert.ElementsMatch(t, tc.files, changedFiles) } } + +func TestGetCommitShortStat(t *testing.T) { + t.Run("repo1_bare", func(t *testing.T) { + repo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare")) + if err != nil { + require.NoError(t, err) + return + } + defer repo.Close() + + numFiles, totalAddition, totalDeletions, err := repo.GetCommitShortStat("ce064814f4a0d337b333e646ece456cd39fab612") + require.NoError(t, err) + assert.Equal(t, 0, numFiles) + assert.Equal(t, 0, totalAddition) + assert.Equal(t, 0, totalDeletions) + + numFiles, totalAddition, totalDeletions, err = repo.GetCommitShortStat("feaf4ba6bc635fec442f46ddd4512416ec43c2c2") + require.NoError(t, err) + assert.Equal(t, 0, numFiles) + assert.Equal(t, 0, totalAddition) + assert.Equal(t, 0, totalDeletions) + + numFiles, totalAddition, totalDeletions, err = repo.GetCommitShortStat("37991dec2c8e592043f47155ce4808d4580f9123") + require.NoError(t, err) + assert.Equal(t, 1, numFiles) + assert.Equal(t, 1, totalAddition) + assert.Equal(t, 0, totalDeletions) + + numFiles, totalAddition, totalDeletions, err = repo.GetCommitShortStat("6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1") + require.NoError(t, err) + assert.Equal(t, 2, numFiles) + assert.Equal(t, 2, totalAddition) + assert.Equal(t, 0, totalDeletions) + + numFiles, totalAddition, totalDeletions, err = repo.GetCommitShortStat("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0") + require.NoError(t, err) + assert.Equal(t, 2, numFiles) + assert.Equal(t, 2, totalAddition) + assert.Equal(t, 0, totalDeletions) + + numFiles, totalAddition, totalDeletions, err = repo.GetCommitShortStat("8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2") + require.NoError(t, err) + assert.Equal(t, 1, numFiles) + assert.Equal(t, 1, totalAddition) + assert.Equal(t, 0, totalDeletions) + + numFiles, totalAddition, totalDeletions, err = repo.GetCommitShortStat("95bb4d39648ee7e325106df01a621c530863a653") + require.NoError(t, err) + assert.Equal(t, 1, numFiles) + assert.Equal(t, 1, totalAddition) + assert.Equal(t, 0, totalDeletions) + }) + + t.Run("repo6_blame_sha256", func(t *testing.T) { + repo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo6_blame_sha256")) + if err != nil { + require.NoError(t, err) + return + } + defer repo.Close() + + numFiles, totalAddition, totalDeletions, err := repo.GetCommitShortStat("e2f5660e15159082902960af0ed74fc144921d2b0c80e069361853b3ece29ba3") + require.NoError(t, err) + assert.Equal(t, 1, numFiles) + assert.Equal(t, 1, totalAddition) + assert.Equal(t, 0, totalDeletions) + + numFiles, totalAddition, totalDeletions, err = repo.GetCommitShortStat("9347b0198cd1f25017579b79d0938fa89dba34ad2514f0dd92f6bc975ed1a2fe") + require.NoError(t, err) + assert.Equal(t, 1, numFiles) + assert.Equal(t, 1, totalAddition) + assert.Equal(t, 1, totalDeletions) + + numFiles, totalAddition, totalDeletions, err = repo.GetCommitShortStat("ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc") + require.NoError(t, err) + assert.Equal(t, 1, numFiles) + assert.Equal(t, 6, totalAddition) + assert.Equal(t, 0, totalDeletions) + }) +} diff --git a/modules/git/repo_test.go b/modules/git/repo_test.go index c3971b2a5f..b07fc3732d 100644 --- a/modules/git/repo_test.go +++ b/modules/git/repo_test.go @@ -4,7 +4,15 @@ package git import ( + "bytes" + "encoding/base64" + "io/fs" + "net/http" + "net/http/httptest" + "net/url" + "os" "path/filepath" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -54,3 +62,80 @@ func TestRepoGetDivergingCommits(t *testing.T) { Behind: 2, }, do) } + +func TestCloneCredentials(t *testing.T) { + calledWithoutPassword := false + askpassFile := "" + credentialsFile := "" + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path != "/info/refs" { + return + } + + // Get basic authorization. + auth, ok := strings.CutPrefix(req.Header.Get("Authorization"), "Basic ") + if !ok { + w.Header().Set("WWW-Authenticate", `Basic realm="Forgejo"`) + http.Error(w, "require credentials", http.StatusUnauthorized) + return + } + + rawAuth, err := base64.StdEncoding.DecodeString(auth) + require.NoError(t, err) + + user, password, ok := bytes.Cut(rawAuth, []byte{':'}) + assert.True(t, ok) + + // First time around Git tries without password (that's specified in the clone URL). + // It demonstrates it doesn't immediately uses askpass. + if len(password) == 0 { + assert.EqualValues(t, "oauth2", user) + calledWithoutPassword = true + + w.Header().Set("WWW-Authenticate", `Basic realm="Forgejo"`) + http.Error(w, "require credentials", http.StatusUnauthorized) + return + } + + assert.EqualValues(t, "oauth2", user) + assert.EqualValues(t, "some_token", password) + + tmpDir := os.TempDir() + + // Verify that the askpass implementation was used. + files, err := fs.Glob(os.DirFS(tmpDir), "forgejo-askpass*") + require.NoError(t, err) + for _, fileName := range files { + fileContent, err := os.ReadFile(filepath.Join(tmpDir, fileName)) + require.NoError(t, err) + + credentialsPath, ok := bytes.CutPrefix(fileContent, []byte(`exec cat `)) + assert.True(t, ok) + + fileContent, err = os.ReadFile(string(credentialsPath)) + require.NoError(t, err) + assert.EqualValues(t, "some_token", fileContent) + + askpassFile = filepath.Join(tmpDir, fileName) + credentialsFile = string(credentialsPath) + } + })) + + serverURL, err := url.Parse(server.URL) + require.NoError(t, err) + + serverURL.User = url.UserPassword("oauth2", "some_token") + + require.NoError(t, Clone(t.Context(), serverURL.String(), t.TempDir(), CloneRepoOptions{})) + + assert.True(t, calledWithoutPassword) + assert.NotEmpty(t, askpassFile) + assert.NotEmpty(t, credentialsFile) + + // Check that the helper files are gone. + _, err = os.Stat(askpassFile) + require.ErrorIs(t, err, fs.ErrNotExist) + _, err = os.Stat(credentialsFile) + require.ErrorIs(t, err, fs.ErrNotExist) +} diff --git a/modules/git/submodule.go b/modules/git/submodule.go index b99c81582b..4ea97d66eb 100644 --- a/modules/git/submodule.go +++ b/modules/git/submodule.go @@ -6,38 +6,124 @@ package git import ( "fmt" + "io" "net" "net/url" "path" "regexp" "strings" + + "forgejo.org/modules/setting" + "forgejo.org/modules/util" + + "gopkg.in/ini.v1" //nolint:depguard // used to read .gitmodules ) -var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`) - -// SubModule submodule is a reference on git repository -type SubModule struct { - Name string - URL string -} - -// SubModuleFile represents a file with submodule type. -type SubModuleFile struct { - *Commit - - refURL string - refID string -} - -// NewSubModuleFile create a new submodule file -func NewSubModuleFile(c *Commit, refURL, refID string) *SubModuleFile { - return &SubModuleFile{ - Commit: c, - refURL: refURL, - refID: refID, +// GetSubmodule returns the Submodule of a given path +func (c *Commit) GetSubmodule(path string, entry *TreeEntry) (Submodule, error) { + err := c.readSubmodules() + if err != nil { + // the .gitmodules file exists but could not be read or parsed + return Submodule{}, err } + + sm, ok := c.submodules[path] + if !ok { + // no info found in .gitmodules: fallback to what we can provide + return Submodule{ + Path: path, + Commit: entry.ID, + }, nil + } + + sm.Commit = entry.ID + return sm, nil } +// readSubmodules populates the submodules field by reading the .gitmodules file +func (c *Commit) readSubmodules() error { + if c.submodules != nil { + return nil + } + + entry, err := c.GetTreeEntryByPath(".gitmodules") + if err != nil { + if IsErrNotExist(err) { + c.submodules = make(map[string]Submodule) + return nil + } + return err + } + + rc, _, err := entry.Blob().NewTruncatedReader(10 * 1024) + if err != nil { + return err + } + defer rc.Close() + + c.submodules, err = parseSubmoduleContent(rc) + return err +} + +func parseSubmoduleContent(r io.Reader) (map[string]Submodule, error) { + // https://git-scm.com/docs/gitmodules#_description + // The .gitmodules file, located in the top-level directory of a Git working tree + // is a text file with a syntax matching the requirements of git-config[1]. + // https://git-scm.com/docs/git-config#_configuration_file + + cfg := ini.Empty(ini.LoadOptions{ + InsensitiveKeys: true, // "The variable names are case-insensitive", but "Subsection names are case sensitive" + }) + err := cfg.Append(r) + if err != nil { + return nil, err + } + + sections := cfg.Sections() + submodule := make(map[string]Submodule, len(sections)) + + for _, s := range sections { + sm := parseSubmoduleSection(s) + if sm.Path == "" || sm.URL == "" { + continue + } + submodule[sm.Path] = sm + } + return submodule, nil +} + +func parseSubmoduleSection(s *ini.Section) Submodule { + section, name, _ := strings.Cut(s.Name(), " ") + if !util.ASCIIEqualFold("submodule", section) { // See https://codeberg.org/forgejo/forgejo/pulls/8438#issuecomment-5805251 + return Submodule{} + } + _ = name + + sm := Submodule{} + if key, _ := s.GetKey("path"); key != nil { + sm.Path = key.Value() + } + if key, _ := s.GetKey("url"); key != nil { + sm.URL = key.Value() + } + return sm +} + +// Submodule represents a parsed git submodule reference. +type Submodule struct { + Path string // path property + URL string // upstream URL + Commit ObjectID // upstream Commit-ID +} + +// ResolveUpstreamURL resolves the upstream URL relative to the repo URL. +func (sm Submodule) ResolveUpstreamURL(repoURL string) string { + repoFullName := strings.TrimPrefix(repoURL, setting.AppURL) // currently hacky, but can be dropped when refactoring getRefURL + return getRefURL(sm.URL, setting.AppURL, repoFullName, setting.SSH.Domain) +} + +var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`) + func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string { if refURL == "" { return "" @@ -53,7 +139,7 @@ func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string { urlPrefix = strings.TrimSuffix(urlPrefix, "/") - // FIXME: Need to consider branch - which will require changes in modules/git/commit.go:GetSubModules + // FIXME: Need to consider branch - which will require changes in parseSubmoduleSection // Relative url prefix check (according to git submodule documentation) if strings.HasPrefix(refURI, "./") || strings.HasPrefix(refURI, "../") { return urlPrefix + path.Clean(path.Join("/", repoFullName, refURI)) @@ -107,13 +193,3 @@ func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string { return "" } - -// RefURL guesses and returns reference URL. -func (sf *SubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string { - return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain) -} - -// RefID returns reference ID. -func (sf *SubModuleFile) RefID() string { - return sf.refID -} diff --git a/modules/git/submodule_test.go b/modules/git/submodule_test.go index a396e4ea0d..2d27f47456 100644 --- a/modules/git/submodule_test.go +++ b/modules/git/submodule_test.go @@ -4,9 +4,11 @@ package git import ( + "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestGetRefURL(t *testing.T) { @@ -40,3 +42,74 @@ func TestGetRefURL(t *testing.T) { assert.Equal(t, kase.expect, getRefURL(kase.refURL, kase.prefixURL, kase.parentPath, kase.SSHDomain)) } } + +func Test_parseSubmoduleContent(t *testing.T) { + submoduleFiles := []struct { + fileContent string + expectedPath string + expected Submodule + }{ + { + fileContent: `[submodule "jakarta-servlet"] +url = ../../ALP-pool/jakarta-servlet +path = jakarta-servlet`, + expectedPath: "jakarta-servlet", + expected: Submodule{ + Path: "jakarta-servlet", + URL: "../../ALP-pool/jakarta-servlet", + }, + }, + { + fileContent: `[submodule "jakarta-servlet"] +path = jakarta-servlet +url = ../../ALP-pool/jakarta-servlet`, + expectedPath: "jakarta-servlet", + expected: Submodule{ + Path: "jakarta-servlet", + URL: "../../ALP-pool/jakarta-servlet", + }, + }, + { + fileContent: `[submodule "about/documents"] + path = about/documents + url = git@github.com:example/documents.git + branch = gh-pages +[submodule "custom-name"] + path = manifesto + url = https://github.com/example/manifesto.git +[submodule] + path = relative/url + url = ../such-relative.git +`, + expectedPath: "relative/url", + expected: Submodule{ + Path: "relative/url", + URL: "../such-relative.git", + }, + }, + { + fileContent: `# .gitmodules +# Subsection names are case sensitive +[submodule "Seanpm2001/Degoogle-your-life"] + path = Its-time-to-cut-WideVine-DRM/DeGoogle-Your-Life/submodule.gitmodules + url = https://github.com/seanpm2001/Degoogle-your-life/ + +[submodule "seanpm2001/degoogle-your-life"] + url = https://github.com/seanpm2001/degoogle-your-life/ +# This second section should not be merged with the first, because of casing +`, + expectedPath: "Its-time-to-cut-WideVine-DRM/DeGoogle-Your-Life/submodule.gitmodules", + expected: Submodule{ + Path: "Its-time-to-cut-WideVine-DRM/DeGoogle-Your-Life/submodule.gitmodules", + URL: "https://github.com/seanpm2001/Degoogle-your-life/", + }, + }, + } + for _, kase := range submoduleFiles { + submodule, err := parseSubmoduleContent(strings.NewReader(kase.fileContent)) + require.NoError(t, err) + v, ok := submodule[kase.expectedPath] + assert.True(t, ok) + assert.Equal(t, kase.expected, v) + } +} diff --git a/modules/git/tests/repos/templates_repo/COMMIT_EDITMSG b/modules/git/tests/repos/templates_repo/COMMIT_EDITMSG new file mode 100644 index 0000000000..a77fa514de --- /dev/null +++ b/modules/git/tests/repos/templates_repo/COMMIT_EDITMSG @@ -0,0 +1 @@ +Initial diff --git a/modules/git/tests/repos/templates_repo/HEAD b/modules/git/tests/repos/templates_repo/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/modules/git/tests/repos/templates_repo/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/modules/git/tests/repos/templates_repo/config b/modules/git/tests/repos/templates_repo/config new file mode 100644 index 0000000000..a4ef456cbc --- /dev/null +++ b/modules/git/tests/repos/templates_repo/config @@ -0,0 +1,5 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + logallrefupdates = true diff --git a/modules/git/tests/repos/templates_repo/description b/modules/git/tests/repos/templates_repo/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/modules/git/tests/repos/templates_repo/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/modules/git/tests/repos/templates_repo/index b/modules/git/tests/repos/templates_repo/index new file mode 100644 index 0000000000..2b3f95a155 Binary files /dev/null and b/modules/git/tests/repos/templates_repo/index differ diff --git a/modules/git/tests/repos/templates_repo/info/exclude b/modules/git/tests/repos/templates_repo/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/modules/git/tests/repos/templates_repo/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/git/tests/repos/templates_repo/info/refs b/modules/git/tests/repos/templates_repo/info/refs new file mode 100644 index 0000000000..8ee4ab3ff8 --- /dev/null +++ b/modules/git/tests/repos/templates_repo/info/refs @@ -0,0 +1 @@ +45697427ce0595075c5c8efa42567f050208510d refs/heads/master diff --git a/modules/git/tests/repos/templates_repo/objects/info/commit-graph b/modules/git/tests/repos/templates_repo/objects/info/commit-graph new file mode 100644 index 0000000000..8904092586 Binary files /dev/null and b/modules/git/tests/repos/templates_repo/objects/info/commit-graph differ diff --git a/modules/git/tests/repos/templates_repo/objects/info/packs b/modules/git/tests/repos/templates_repo/objects/info/packs new file mode 100644 index 0000000000..5833126b59 --- /dev/null +++ b/modules/git/tests/repos/templates_repo/objects/info/packs @@ -0,0 +1,2 @@ +P pack-abb44544ae19d590e95822e963f78d069d27ba9e.pack + diff --git a/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.bitmap b/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.bitmap new file mode 100644 index 0000000000..6bd87bf13c Binary files /dev/null and b/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.bitmap differ diff --git a/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.idx b/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.idx new file mode 100644 index 0000000000..b530533e1b Binary files /dev/null and b/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.idx differ diff --git a/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.pack b/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.pack new file mode 100644 index 0000000000..6bbcbb6546 Binary files /dev/null and b/modules/git/tests/repos/templates_repo/objects/pack/pack-abb44544ae19d590e95822e963f78d069d27ba9e.pack differ diff --git a/modules/git/tests/repos/templates_repo/packed-refs b/modules/git/tests/repos/templates_repo/packed-refs new file mode 100644 index 0000000000..eb6e7a8add --- /dev/null +++ b/modules/git/tests/repos/templates_repo/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +45697427ce0595075c5c8efa42567f050208510d refs/heads/master diff --git a/modules/git/tests/repos/templates_repo/refs/heads/.gitkeep b/modules/git/tests/repos/templates_repo/refs/heads/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/git/tests/repos/templates_repo/refs/tags/.gitkeep b/modules/git/tests/repos/templates_repo/refs/tags/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/git/tree_blob.go b/modules/git/tree_blob.go index df339f64b1..5c1aa7753d 100644 --- a/modules/git/tree_blob.go +++ b/modules/git/tree_blob.go @@ -17,7 +17,6 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) { ptree: t, ID: t.ID, name: "", - fullName: "", entryMode: EntryModeTree, }, nil } @@ -55,7 +54,7 @@ func (t *Tree) GetBlobByPath(relpath string) (*Blob, error) { return nil, err } - if !entry.IsDir() && !entry.IsSubModule() { + if !entry.IsDir() && !entry.IsSubmodule() { return entry.Blob(), nil } diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index d51b7992fe..8b6c4c467c 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -21,16 +21,12 @@ type TreeEntry struct { entryMode EntryMode name string - size int64 - sized bool - fullName string + size int64 + sized bool } // Name returns the name of the entry func (te *TreeEntry) Name() string { - if te.fullName != "" { - return te.fullName - } return te.name } @@ -68,8 +64,8 @@ func (te *TreeEntry) Size() int64 { return te.size } -// IsSubModule if the entry is a sub module -func (te *TreeEntry) IsSubModule() bool { +// IsSubmodule if the entry is a submodule +func (te *TreeEntry) IsSubmodule() bool { return te.entryMode == EntryModeCommit } @@ -116,32 +112,37 @@ func (te *TreeEntry) Type() string { } } +// LinkTarget returns the target of the symlink as string. +func (te *TreeEntry) LinkTarget() (string, error) { + if !te.IsLink() { + return "", ErrBadLink{te.Name(), "not a symlink"} + } + + const symlinkLimit = 4096 // according to git config core.longpaths https://stackoverflow.com/a/22575737 + blob := te.Blob() + if blob.Size() > symlinkLimit { + return "", ErrBadLink{te.Name(), "symlink too large"} + } + + rc, size, err := blob.NewTruncatedReader(symlinkLimit) + if err != nil { + return "", err + } + defer rc.Close() + + buf := make([]byte, int(size)) + _, err = io.ReadFull(rc, buf) + return string(buf), err +} + // FollowLink returns the entry pointed to by a symlink func (te *TreeEntry) FollowLink() (*TreeEntry, string, error) { - if !te.IsLink() { - return nil, "", ErrBadLink{te.Name(), "not a symlink"} - } - // read the link - r, err := te.Blob().DataAsync() + lnk, err := te.LinkTarget() if err != nil { return nil, "", err } - closed := false - defer func() { - if !closed { - _ = r.Close() - } - }() - buf := make([]byte, te.Size()) - _, err = io.ReadFull(r, buf) - if err != nil { - return nil, "", err - } - _ = r.Close() - closed = true - lnk := string(buf) t := te.ptree // traverse up directories @@ -209,7 +210,7 @@ func (te *TreeEntry) Tree() *Tree { // GetSubJumpablePathName return the full path of subdirectory jumpable ( contains only one directory ) func (te *TreeEntry) GetSubJumpablePathName() string { - if te.IsSubModule() || !te.IsDir() { + if te.IsSubmodule() || !te.IsDir() { return "" } tree, err := te.ptree.SubTree(te.Name()) @@ -236,7 +237,7 @@ type customSortableEntries struct { var sorter = []func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool{ func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool { - return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule() + return (t1.IsDir() || t1.IsSubmodule()) && !t2.IsDir() && !t2.IsSubmodule() }, func(t1, t2 *TreeEntry, cmp func(s1, s2 string) bool) bool { return cmp(t1.Name(), t2.Name()) diff --git a/modules/graceful/server_http.go b/modules/graceful/server_http.go index 7c855ac64e..0b2984a953 100644 --- a/modules/graceful/server_http.go +++ b/modules/graceful/server_http.go @@ -16,6 +16,11 @@ func newHTTPServer(network, address, name string, handler http.Handler) (*Server Handler: handler, BaseContext: func(net.Listener) context.Context { return GetManager().HammerContext() }, } + // Enable H2C for HTTP server + httpServer.Protocols = new(http.Protocols) + httpServer.Protocols.SetHTTP1(true) + httpServer.Protocols.SetHTTP2(true) + httpServer.Protocols.SetUnencryptedHTTP2(true) server.OnShutdown = func() { httpServer.SetKeepAlivesEnabled(false) } diff --git a/modules/httplib/serve.go b/modules/httplib/serve.go index c5f0658d4e..d385ac21c9 100644 --- a/modules/httplib/serve.go +++ b/modules/httplib/serve.go @@ -99,7 +99,7 @@ func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, filePath stri Filename: path.Base(filePath), } - sniffedType := typesniffer.DetectContentType(mineBuf) + sniffedType := typesniffer.DetectContentType(mineBuf, opts.Filename) // the "render" parameter came from year 2016: 638dd24c, it doesn't have clear meaning, so I think it could be removed later isPlain := sniffedType.IsText() || r.FormValue("render") != "" diff --git a/modules/indexer/code/bleve/bleve.go b/modules/indexer/code/bleve/bleve.go index c53b7a2e6d..4c8b5f2a86 100644 --- a/modules/indexer/code/bleve/bleve.go +++ b/modules/indexer/code/bleve/bleve.go @@ -177,7 +177,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro fileContents, err := io.ReadAll(io.LimitReader(batchReader, size)) if err != nil { return err - } else if !typesniffer.DetectContentType(fileContents).IsText() { + } else if !typesniffer.DetectContentType(fileContents, update.Filename).IsText() { // FIXME: UTF-16 files will probably fail here // Even if the file is not recognized as a "text file", we could still put its name into the indexers to make the filename become searchable, while leave the content to empty. fileContents = nil diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go index 3903d77fe0..9b11f56fb7 100644 --- a/modules/indexer/code/elasticsearch/elasticsearch.go +++ b/modules/indexer/code/elasticsearch/elasticsearch.go @@ -144,7 +144,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro fileContents, err := io.ReadAll(io.LimitReader(batchReader, size)) if err != nil { return nil, err - } else if !typesniffer.DetectContentType(fileContents).IsText() { + } else if !typesniffer.DetectContentType(fileContents, update.Filename).IsText() { // FIXME: UTF-16 files will probably fail here return nil, nil } diff --git a/modules/indexer/code/search.go b/modules/indexer/code/search.go index 499b9117c4..66c9497dab 100644 --- a/modules/indexer/code/search.go +++ b/modules/indexer/code/search.go @@ -35,6 +35,7 @@ type SearchResultLanguages = internal.SearchResultLanguages type SearchOptions = internal.SearchOptions +// llu:TrKeysSuffix search. var CodeSearchOptions = [2]string{"exact", "union"} type SearchMode = internal.CodeSearchMode diff --git a/modules/indexer/code/search_test.go b/modules/indexer/code/search_test.go index e542b38c24..2413ddec0b 100644 --- a/modules/indexer/code/search_test.go +++ b/modules/indexer/code/search_test.go @@ -86,9 +86,9 @@ func TestHighlightSearchResultCode(t *testing.T) { Range: [][3]int{{1, 14, 23}}, Code: "func main() {\n\tfmt.Println(\"mark this\")\n}", Result: []template.HTML{ - "func main() {", - "\tfmt.Println("mark this")", - "}", + "func main() {", + "\tfmt.Println("mark this")", + "}", }, }, { @@ -97,9 +97,9 @@ func TestHighlightSearchResultCode(t *testing.T) { Range: [][3]int{{1, 14, 28}}, Code: "func main() {\n\tfmt.Println(\"mark this ๐Ÿ˜Š\")\n}", Result: []template.HTML{ - "func main() {", - "\tfmt.Println("mark this ๐Ÿ˜Š")", - "}", + "func main() {", + "\tfmt.Println("mark this ๐Ÿ˜Š")", + "}", }, }, } diff --git a/modules/indexer/issues/bleve/bleve.go b/modules/indexer/issues/bleve/bleve.go index 80af7bac45..cb98f722c5 100644 --- a/modules/indexer/issues/bleve/bleve.go +++ b/modules/indexer/issues/bleve/bleve.go @@ -171,7 +171,7 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if issueID, err := token.ParseIssueReference(); err == nil { idQuery := inner_bleve.NumericEqualityQuery(issueID, "index") - idQuery.SetBoost(5.0) + idQuery.SetBoost(20.0) innerQ.AddQuery(idQuery) } @@ -198,6 +198,15 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( queries = append(queries, bleve.NewDisjunctionQuery(repoQueries...)) } + if options.PriorityRepoID.Has() { + eq := inner_bleve.NumericEqualityQuery(options.PriorityRepoID.Value(), "repo_id") + eq.SetBoost(10.0) + meh := bleve.NewMatchAllQuery() + meh.SetBoost(0) + should := bleve.NewDisjunctionQuery(eq, meh) + queries = append(queries, should) + } + if options.IsPull.Has() { queries = append(queries, inner_bleve.BoolFieldQuery(options.IsPull.Value(), "is_pull")) } diff --git a/modules/indexer/issues/db/db.go b/modules/indexer/issues/db/db.go index 397daa3265..5f42bce9a1 100644 --- a/modules/indexer/issues/db/db.go +++ b/modules/indexer/issues/db/db.go @@ -53,6 +53,7 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( cond := builder.NewCond() + var priorityIssueIndex int64 if options.Keyword != "" { repoCond := builder.In("repo_id", options.RepoIDs) if len(options.RepoIDs) == 1 { @@ -82,6 +83,7 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( builder.Eq{"`index`": issueID}, cond, ) + priorityIssueIndex = issueID } } @@ -89,6 +91,7 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if err != nil { return nil, err } + opt.PriorityIssueIndex = priorityIssueIndex // If pagesize == 0, return total count only. It's a special case for search count. if options.Paginator != nil && options.Paginator.PageSize == 0 { diff --git a/modules/indexer/issues/db/options.go b/modules/indexer/issues/db/options.go index 4411cc1c37..55a471fc8e 100644 --- a/modules/indexer/issues/db/options.go +++ b/modules/indexer/issues/db/options.go @@ -78,6 +78,11 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m User: nil, } + if options.PriorityRepoID.Has() { + opts.SortType = "priorityrepo" + opts.PriorityRepoID = options.PriorityRepoID.Value() + } + if len(options.MilestoneIDs) == 1 && options.MilestoneIDs[0] == 0 { opts.MilestoneIDs = []int64{db.NoConditionID} } else { diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go index 0c4e5fae02..311e92730e 100644 --- a/modules/indexer/issues/elasticsearch/elasticsearch.go +++ b/modules/indexer/issues/elasticsearch/elasticsearch.go @@ -166,7 +166,7 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( } var eitherQ elastic.Query = innerQ if issueID, err := token.ParseIssueReference(); err == nil { - indexQ := elastic.NewTermQuery("index", issueID).Boost(15.0) + indexQ := elastic.NewTermQuery("index", issueID).Boost(20) eitherQ = elastic.NewDisMaxQuery().Query(indexQ).Query(innerQ).TieBreaker(0.5) } switch token.Kind { @@ -189,6 +189,10 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( } query.Must(q) } + if options.PriorityRepoID.Has() { + q := elastic.NewTermQuery("repo_id", options.PriorityRepoID.Value()).Boost(10) + query.Should(q) + } if options.IsPull.Has() { query.Must(elastic.NewTermQuery("is_pull", options.IsPull.Value())) diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go index d3b3494672..21daea3d45 100644 --- a/modules/indexer/issues/indexer_test.go +++ b/modules/indexer/issues/indexer_test.go @@ -84,7 +84,7 @@ func searchIssueWithKeyword(t *testing.T) { issueIDs, _, err := SearchIssues(t.Context(), &test.opts) require.NoError(t, err) - assert.Equal(t, test.expectedIDs, issueIDs) + assert.Equal(t, test.expectedIDs, issueIDs, test.opts.Keyword) } } diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go index 6c55405179..cdd113212d 100644 --- a/modules/indexer/issues/internal/model.go +++ b/modules/indexer/issues/internal/model.go @@ -75,8 +75,9 @@ type SearchResult struct { type SearchOptions struct { Keyword string // keyword to search - RepoIDs []int64 // repository IDs which the issues belong to - AllPublic bool // if include all public repositories + RepoIDs []int64 // repository IDs which the issues belong to + AllPublic bool // if include all public repositories + PriorityRepoID optional.Option[int64] // issues from this repository will be prioritized when SortByScore IsPull optional.Option[bool] // if the issues is a pull request IsClosed optional.Option[bool] // if the issues is closed diff --git a/modules/indexer/issues/internal/tests/tests.go b/modules/indexer/issues/internal/tests/tests.go index e7723ea69d..46014994a0 100644 --- a/modules/indexer/issues/internal/tests/tests.go +++ b/modules/indexer/issues/internal/tests/tests.go @@ -772,6 +772,25 @@ var cases = []*testIndexerCase{ } }, }, + { + Name: "PriorityRepoID", + SearchOptions: &internal.SearchOptions{ + IsPull: optional.Some(false), + IsClosed: optional.Some(false), + PriorityRepoID: optional.Some(int64(3)), + Paginator: &db.ListOptionsAll, + SortBy: internal.SortByScore, + }, + Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { + for i, v := range result.Hits { + if i < 7 { + assert.Equal(t, int64(3), data[v.ID].RepoID) + } else { + assert.NotEqual(t, int64(3), data[v.ID].RepoID) + } + } + }, + }, } type testIndexerCase struct { diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go index 17a8ba2452..9c14e4cbd3 100644 --- a/modules/indexer/issues/meilisearch/meilisearch.go +++ b/modules/indexer/issues/meilisearch/meilisearch.go @@ -100,7 +100,7 @@ func (b *Indexer) Index(_ context.Context, issues ...*internal.IndexerData) erro return nil } for _, issue := range issues { - _, err := b.inner.Client.Index(b.inner.VersionedIndexName()).AddDocuments(issue) + _, err := b.inner.Client.Index(b.inner.VersionedIndexName()).AddDocuments(issue, nil) if err != nil { return err } @@ -303,21 +303,9 @@ func doubleQuoteKeyword(k string) string { } func convertHits(searchRes *meilisearch.SearchResponse) ([]internal.Match, error) { - hits := make([]internal.Match, 0, len(searchRes.Hits)) - for _, hit := range searchRes.Hits { - hit, ok := hit.(map[string]any) - if !ok { - return nil, ErrMalformedResponse - } - - issueID, ok := hit["id"].(float64) - if !ok { - return nil, ErrMalformedResponse - } - - hits = append(hits, internal.Match{ - ID: int64(issueID), - }) + hits := make([]internal.Match, 0) + if err := searchRes.Hits.DecodeInto(&hits); err != nil { + return nil, ErrMalformedResponse } return hits, nil } diff --git a/modules/indexer/issues/meilisearch/meilisearch_test.go b/modules/indexer/issues/meilisearch/meilisearch_test.go index 7637e8d6b4..810de77046 100644 --- a/modules/indexer/issues/meilisearch/meilisearch_test.go +++ b/modules/indexer/issues/meilisearch/meilisearch_test.go @@ -46,30 +46,34 @@ func TestMeilisearchIndexer(t *testing.T) { } func TestConvertHits(t *testing.T) { - _, err := convertHits(&meilisearch.SearchResponse{ - Hits: []any{"aa", "bb", "cc", "dd"}, - }) - require.ErrorIs(t, err, ErrMalformedResponse) + for _, invalidID := range []string{"\"aa\"", "{\"aa\":\"123\"}", "[\"aa\"]"} { + _, err := convertHits(&meilisearch.SearchResponse{ + Hits: meilisearch.Hits{ + meilisearch.Hit{"id": []byte(invalidID)}, + }, + }) + require.ErrorIs(t, err, ErrMalformedResponse) + } validResponse := &meilisearch.SearchResponse{ - Hits: []any{ - map[string]any{ - "id": float64(11), - "title": "a title", - "content": "issue body with no match", - "comments": []any{"hey what's up?", "I'm currently bowling", "nice"}, + Hits: meilisearch.Hits{ + meilisearch.Hit{ + "id": []byte("11"), + "title": []byte("\"a title\""), + "content": []byte("\"issue body with no match\""), + "comments": []byte("[\"hey what's up?\", \"I'm currently bowling\", \"nice\"]"), }, - map[string]any{ - "id": float64(22), - "title": "Bowling as title", - "content": "", - "comments": []any{}, + meilisearch.Hit{ + "id": []byte("22"), + "title": []byte("\"Bowling as title\""), + "content": []byte("\"\""), + "comments": []byte("[]"), }, - map[string]any{ - "id": float64(33), - "title": "Bowl-ing as fuzzy match", - "content": "", - "comments": []any{}, + meilisearch.Hit{ + "id": []byte("33"), + "title": []byte("\"Bowl-ing as fuzzy match\""), + "content": []byte("\"\""), + "comments": []byte("[]"), }, }, } diff --git a/modules/issue/template/unmarshal.go b/modules/issue/template/unmarshal.go index 8df71a3299..0d80dae009 100644 --- a/modules/issue/template/unmarshal.go +++ b/modules/issue/template/unmarshal.go @@ -15,7 +15,7 @@ import ( api "forgejo.org/modules/structs" "forgejo.org/modules/util" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) // CouldBe indicates a file with the filename could be a template, diff --git a/modules/keying/keying.go b/modules/keying/keying.go index c859e30e9f..f39e16aeed 100644 --- a/modules/keying/keying.go +++ b/modules/keying/keying.go @@ -58,6 +58,8 @@ var ( ContextPushMirror Context = "pushmirror" // Used for the `two_factor` table. ContextTOTP Context = "totp" + // Used for the `secret` table. + ContextActionSecret Context = "action_secret" ) // Derive *the* key for a given context, this is a deterministic function. diff --git a/modules/label/parser.go b/modules/label/parser.go index 558ae68def..12fc176967 100644 --- a/modules/label/parser.go +++ b/modules/label/parser.go @@ -10,7 +10,7 @@ import ( "forgejo.org/modules/options" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) type labelFile struct { diff --git a/modules/lfs/pointer_scanner.go b/modules/lfs/pointer_scanner.go index 632ecd19ae..80da8e5222 100644 --- a/modules/lfs/pointer_scanner.go +++ b/modules/lfs/pointer_scanner.go @@ -39,16 +39,7 @@ func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan c go pipeline.BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg) // 1. Run batch-check on all objects in the repository - if git.CheckGitVersionAtLeast("2.6.0") != nil { - revListReader, revListWriter := io.Pipe() - shasToCheckReader, shasToCheckWriter := io.Pipe() - wg.Add(2) - go pipeline.CatFileBatchCheck(ctx, shasToCheckReader, catFileCheckWriter, &wg, basePath) - go pipeline.BlobsFromRevListObjects(revListReader, shasToCheckWriter, &wg) - go pipeline.RevListAllObjects(ctx, revListWriter, &wg, basePath, errChan) - } else { - go pipeline.CatFileBatchCheckAllObjects(ctx, catFileCheckWriter, &wg, basePath, errChan) - } + go pipeline.CatFileBatchCheckAllObjects(ctx, catFileCheckWriter, &wg, basePath, errChan) wg.Wait() close(pointerChan) diff --git a/modules/log/event_format.go b/modules/log/event_format.go index df6b083a92..6835a4ca5b 100644 --- a/modules/log/event_format.go +++ b/modules/log/event_format.go @@ -13,10 +13,9 @@ import ( type Event struct { Time time.Time - GoroutinePid string - Caller string - Filename string - Line int + Caller string + Filename string + Line int Level Level @@ -225,19 +224,6 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms msg = msg[:len(msg)-1] } - if flags&Lgopid == Lgopid { - if event.GoroutinePid != "" { - buf = append(buf, '[') - if mode.Colorize { - buf = append(buf, ColorBytes(FgHiYellow)...) - } - buf = append(buf, event.GoroutinePid...) - if mode.Colorize { - buf = append(buf, resetBytes...) - } - buf = append(buf, ']', ' ') - } - } buf = append(buf, msg...) if event.Stacktrace != "" && mode.StacktraceLevel <= event.Level { diff --git a/modules/log/event_format_test.go b/modules/log/event_format_test.go index 0c6061eaea..f6f9754300 100644 --- a/modules/log/event_format_test.go +++ b/modules/log/event_format_test.go @@ -24,48 +24,45 @@ func TestItoa(t *testing.T) { func TestEventFormatTextMessage(t *testing.T) { res := EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: false, Flags: Flags{defined: true, flags: 0xffffffff}}, &Event{ - Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), - Caller: "caller", - Filename: "filename", - Line: 123, - GoroutinePid: "pid", - Level: ERROR, - Stacktrace: "stacktrace", + Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), + Caller: "caller", + Filename: "filename", + Line: 123, + Level: ERROR, + Stacktrace: "stacktrace", }, "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue), ) - assert.Equal(t, `<3>[PREFIX] 2020/01/02 03:04:05.000000 filename:123:caller [E] [pid] msg format: arg0 arg1 + assert.Equal(t, `<3>[PREFIX] 2020/01/02 03:04:05.000000 filename:123:caller [E] msg format: arg0 arg1 stacktrace `, string(res)) res = EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: true, Flags: Flags{defined: true, flags: 0xffffffff}}, &Event{ - Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), - Caller: "caller", - Filename: "filename", - Line: 123, - GoroutinePid: "pid", - Level: ERROR, - Stacktrace: "stacktrace", + Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), + Caller: "caller", + Filename: "filename", + Line: 123, + Level: ERROR, + Stacktrace: "stacktrace", }, "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue), ) - assert.Equal(t, "<3>[PREFIX] \x1b[36m2020/01/02 03:04:05.000000 \x1b[0m\x1b[32mfilename:123:\x1b[32mcaller\x1b[0m \x1b[1;31m[E]\x1b[0m [\x1b[93mpid\x1b[0m] msg format: arg0 \x1b[34marg1\x1b[0m\n\tstacktrace\n\n", string(res)) + assert.Equal(t, "<3>[PREFIX] \x1b[36m2020/01/02 03:04:05.000000 \x1b[0m\x1b[32mfilename:123:\x1b[32mcaller\x1b[0m \x1b[1;31m[E]\x1b[0m msg format: arg0 \x1b[34marg1\x1b[0m\n\tstacktrace\n\n", string(res)) } func TestEventFormatTextMessageStd(t *testing.T) { res := EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: false, Flags: Flags{defined: true, flags: LstdFlags}}, &Event{ - Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), - Caller: "caller", - Filename: "filename", - Line: 123, - GoroutinePid: "pid", - Level: ERROR, - Stacktrace: "stacktrace", + Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), + Caller: "caller", + Filename: "filename", + Line: 123, + Level: ERROR, + Stacktrace: "stacktrace", }, "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue), ) @@ -77,13 +74,12 @@ func TestEventFormatTextMessageStd(t *testing.T) { res = EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: true, Flags: Flags{defined: true, flags: LstdFlags}}, &Event{ - Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), - Caller: "caller", - Filename: "filename", - Line: 123, - GoroutinePid: "pid", - Level: ERROR, - Stacktrace: "stacktrace", + Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), + Caller: "caller", + Filename: "filename", + Line: 123, + Level: ERROR, + Stacktrace: "stacktrace", }, "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue), ) @@ -96,13 +92,12 @@ func TestEventFormatTextMessageJournal(t *testing.T) { // the proper way here is to attach the backtrace as structured metadata, but we can't do that via stderr res := EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: false, Flags: Flags{defined: true, flags: LjournaldFlags}}, &Event{ - Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), - Caller: "caller", - Filename: "filename", - Line: 123, - GoroutinePid: "pid", - Level: ERROR, - Stacktrace: "stacktrace", + Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), + Caller: "caller", + Filename: "filename", + Line: 123, + Level: ERROR, + Stacktrace: "stacktrace", }, "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue), ) diff --git a/modules/log/event_writer.go b/modules/log/event_writer.go index 4b77e488de..32b5b582c5 100644 --- a/modules/log/event_writer.go +++ b/modules/log/event_writer.go @@ -26,6 +26,7 @@ type WriterMode struct { Flags Flags Expression string + Exclusion string StacktraceLevel Level diff --git a/modules/log/event_writer_base.go b/modules/log/event_writer_base.go index 9189ca4e90..4de2b953c7 100644 --- a/modules/log/event_writer_base.go +++ b/modules/log/event_writer_base.go @@ -68,6 +68,14 @@ func (b *EventWriterBaseImpl) Run(ctx context.Context) { } } + var exclusionRegexp *regexp.Regexp + if b.Mode.Exclusion != "" { + var err error + if exclusionRegexp, err = regexp.Compile(b.Mode.Exclusion); err != nil { + FallbackErrorf("unable to compile exclusion %q for writer %q: %v", b.Mode.Exclusion, b.Name, err) + } + } + handlePaused := func() { if pause := b.GetPauseChan(); pause != nil { select { @@ -95,6 +103,13 @@ func (b *EventWriterBaseImpl) Run(ctx context.Context) { continue } } + if exclusionRegexp != nil { + fileLineCaller := fmt.Sprintf("%s:%d:%s", event.Origin.Filename, event.Origin.Line, event.Origin.Caller) + matched := exclusionRegexp.MatchString(fileLineCaller) || exclusionRegexp.MatchString(event.Origin.MsgSimpleText) + if matched { + continue + } + } var err error switch msg := event.Msg.(type) { diff --git a/modules/log/event_writer_buffer_test.go b/modules/log/event_writer_buffer_test.go index ba9455ba69..d1e37c3673 100644 --- a/modules/log/event_writer_buffer_test.go +++ b/modules/log/event_writer_buffer_test.go @@ -31,3 +31,49 @@ func TestBufferLogger(t *testing.T) { logger.Close() assert.Contains(t, bufferWriter.Buffer.String(), expected) } + +func TestBufferLoggerWithExclusion(t *testing.T) { + prefix := "ExclusionPrefix " + level := log.INFO + message := "something" + + bufferWriter := log.NewEventWriterBuffer("test-buffer", log.WriterMode{ + Level: level, + Prefix: prefix, + Exclusion: message, + }) + + logger := log.NewLoggerWithWriters(t.Context(), "test", bufferWriter) + + logger.SendLogEvent(&log.Event{ + Level: log.INFO, + MsgSimpleText: message, + }) + logger.Close() + assert.NotContains(t, bufferWriter.Buffer.String(), message) +} + +func TestBufferLoggerWithExpressionAndExclusion(t *testing.T) { + prefix := "BothPrefix " + level := log.INFO + expression := ".*foo.*" + exclusion := ".*bar.*" + + bufferWriter := log.NewEventWriterBuffer("test-buffer", log.WriterMode{ + Level: level, + Prefix: prefix, + Expression: expression, + Exclusion: exclusion, + }) + + logger := log.NewLoggerWithWriters(t.Context(), "test", bufferWriter) + + logger.SendLogEvent(&log.Event{Level: log.INFO, MsgSimpleText: "foo expression"}) + logger.SendLogEvent(&log.Event{Level: log.INFO, MsgSimpleText: "bar exclusion"}) + logger.SendLogEvent(&log.Event{Level: log.INFO, MsgSimpleText: "foo bar both"}) + logger.SendLogEvent(&log.Event{Level: log.INFO, MsgSimpleText: "none"}) + logger.Close() + + assert.Contains(t, bufferWriter.Buffer.String(), "foo expression") + assert.NotContains(t, bufferWriter.Buffer.String(), "bar") +} diff --git a/modules/log/flags.go b/modules/log/flags.go index afce30680d..1e4fe830c1 100644 --- a/modules/log/flags.go +++ b/modules/log/flags.go @@ -30,7 +30,6 @@ const ( LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone Llevelinitial // Initial character of the provided level in brackets, eg. [I] for info Llevel // Provided level in brackets [INFO] - Lgopid // the Goroutine-PID of the context Llevelprefix // printk-style logging prefixes as documented in sd-daemon(3), used by journald Lmedfile = Lshortfile | Llongfile // last 20 characters of the filename @@ -57,7 +56,6 @@ var flagFromString = map[string]uint32{ "levelinitial": Llevelinitial, "level": Llevel, "levelprefix": Llevelprefix, - "gopid": Lgopid, "medfile": Lmedfile, "stdflags": LstdFlags, @@ -82,7 +80,6 @@ var flagComboToString = []struct { {LUTC, "utc"}, {Llevelinitial, "levelinitial"}, {Llevel, "level"}, - {Lgopid, "gopid"}, } func (f Flags) Bits() uint32 { diff --git a/modules/log/flags_test.go b/modules/log/flags_test.go index 1af9a58ed6..f39fe440e2 100644 --- a/modules/log/flags_test.go +++ b/modules/log/flags_test.go @@ -15,9 +15,7 @@ import ( func TestFlags(t *testing.T) { assert.Equal(t, Ldefault, Flags{}.Bits()) assert.EqualValues(t, 0, FlagsFromString("").Bits()) - assert.Equal(t, Lgopid, FlagsFromString("", Lgopid).Bits()) - assert.EqualValues(t, 0, FlagsFromString("none", Lgopid).Bits()) - assert.Equal(t, Ldate|Ltime, FlagsFromString("date,time", Lgopid).Bits()) + assert.Equal(t, Ldate|Ltime, FlagsFromString("date,time").Bits()) assert.Equal(t, "stdflags", FlagsFromString("stdflags").String()) assert.Equal(t, "medfile", FlagsFromString("medfile").String()) diff --git a/modules/log/groutinelabel.go b/modules/log/groutinelabel.go deleted file mode 100644 index cd5fb96d52..0000000000 --- a/modules/log/groutinelabel.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build !go1.24 - -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package log - -import "unsafe" - -//go:linkname runtime_getProfLabel runtime/pprof.runtime_getProfLabel -func runtime_getProfLabel() unsafe.Pointer //nolint - -type labelMap map[string]string - -func getGoroutineLabels() map[string]string { - l := (*labelMap)(runtime_getProfLabel()) - if l == nil { - return nil - } - return *l -} diff --git a/modules/log/groutinelabel_go1.24.go b/modules/log/groutinelabel_go1.24.go deleted file mode 100644 index e849811bc2..0000000000 --- a/modules/log/groutinelabel_go1.24.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build go1.24 - -// Copyright 2024 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package log - -import "unsafe" - -//go:linkname runtime_getProfLabel runtime/pprof.runtime_getProfLabel -func runtime_getProfLabel() unsafe.Pointer //nolint - -// Struct definitions copied from: https://github.com/golang/go/blob/ca63101df47a4467bc80faa654fc19d68e583952/src/runtime/pprof/label.go -type label struct { - key string - value string -} - -type LabelSet struct { - list []label -} - -type labelMap struct { - LabelSet -} - -func getGoroutineLabels() map[string]string { - l := (*labelMap)(runtime_getProfLabel()) - if l == nil { - return nil - } - - m := make(map[string]string, len(l.list)) - for _, label := range l.list { - m[label.key] = label.value - } - return m -} diff --git a/modules/log/groutinelabel_test.go b/modules/log/groutinelabel_test.go deleted file mode 100644 index 98d7b4e129..0000000000 --- a/modules/log/groutinelabel_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package log - -import ( - "context" - "runtime/pprof" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_getGoroutineLabels(t *testing.T) { - pprof.Do(t.Context(), pprof.Labels(), func(ctx context.Context) { - currentLabels := getGoroutineLabels() - pprof.ForLabels(ctx, func(key, value string) bool { - assert.Equal(t, value, currentLabels[key]) - return true - }) - - pprof.Do(ctx, pprof.Labels("Test_getGoroutineLabels", "Test_getGoroutineLabels_child1"), func(ctx context.Context) { - currentLabels := getGoroutineLabels() - pprof.ForLabels(ctx, func(key, value string) bool { - assert.Equal(t, value, currentLabels[key]) - return true - }) - if assert.NotNil(t, currentLabels) { - assert.Equal(t, "Test_getGoroutineLabels_child1", currentLabels["Test_getGoroutineLabels"]) - } - }) - }) -} diff --git a/modules/log/logger_impl.go b/modules/log/logger_impl.go index b21e800f52..76d7e6a821 100644 --- a/modules/log/logger_impl.go +++ b/modules/log/logger_impl.go @@ -200,11 +200,6 @@ func (l *LoggerImpl) Log(skip int, level Level, format string, logArgs ...any) { event.Stacktrace = Stack(skip + 1) } - labels := getGoroutineLabels() - if labels != nil { - event.GoroutinePid = labels["pid"] - } - // get a simple text message without color msgArgs := make([]any, len(logArgs)) copy(msgArgs, logArgs) diff --git a/modules/log/logger_test.go b/modules/log/logger_test.go index 6d6ceb69d7..99045b0f4f 100644 --- a/modules/log/logger_test.go +++ b/modules/log/logger_test.go @@ -143,3 +143,19 @@ func TestLoggerExpressionFilter(t *testing.T) { assert.Equal(t, []string{"foo\n", "foo bar\n", "by filename\n"}, w1.GetLogs()) } + +func TestLoggerExclusionFilter(t *testing.T) { + logger := NewLoggerWithWriters(t.Context(), "test") + + w1 := newDummyWriter("dummy-1", DEBUG, 0) + w1.Mode.Exclusion = "foo.*" + logger.AddWriters(w1) + + logger.Info("foo") + logger.Info("bar") + logger.Info("foo bar") + logger.SendLogEvent(&Event{Level: INFO, Filename: "foo.go", MsgSimpleText: "by filename"}) + logger.Close() + + assert.Equal(t, []string{"bar\n"}, w1.GetLogs()) +} diff --git a/modules/log/stack.go b/modules/log/stack.go index 9b22e92867..a8d26d1fc3 100644 --- a/modules/log/stack.go +++ b/modules/log/stack.go @@ -32,7 +32,7 @@ func Stack(skip int) string { } // Print equivalent of debug.Stack() - _, _ = fmt.Fprintf(buf, "%s:%d (0x%x)\n", filename, lineNumber, programCounter) + _, _ = fmt.Fprintf(buf, "\t%s:%d (0x%x)\n", filename, lineNumber, programCounter) // Now try to print the offending line if filename != lastFilename { data, err := os.ReadFile(filename) @@ -44,7 +44,7 @@ func Stack(skip int) string { lines = bytes.Split(data, []byte{'\n'}) lastFilename = filename } - _, _ = fmt.Fprintf(buf, "\t%s: %s\n", functionName(programCounter), source(lines, lineNumber)) + _, _ = fmt.Fprintf(buf, "\t\t%s: %s\n", functionName(programCounter), source(lines, lineNumber)) } return buf.String() } diff --git a/modules/markup/file_preview.go b/modules/markup/file_preview.go index 8b1442ed08..dab6057cf4 100644 --- a/modules/markup/file_preview.go +++ b/modules/markup/file_preview.go @@ -25,7 +25,7 @@ import ( ) // filePreviewPattern matches "http://domain/org/repo/src/commit/COMMIT/filepath#L1-L2" -var filePreviewPattern = regexp.MustCompile(`https?://((?:\S+/){3})src/commit/([0-9a-f]{4,64})/(\S+)#(L\d+(?:-L\d+)?)`) +var filePreviewPattern = regexp.MustCompile(`https?://((?:\S+/){3})src/commit/([0-9a-f]{4,64})/(\S+)#(L\d+(?:-L?\d+)?)`) type FilePreview struct { fileContent []template.HTML @@ -78,17 +78,17 @@ func newFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca commitSha := node.Data[m[4]:m[5]] filePath := node.Data[m[6]:m[7]] + hash := node.Data[m[8]:m[9]] urlFullSource := urlFull if strings.HasSuffix(filePath, "?display=source") { filePath = strings.TrimSuffix(filePath, "?display=source") } else if Type(filePath) != "" { - urlFullSource = node.Data[m[0]:m[6]] + filePath + "?display=source#" + node.Data[m[8]:m[1]] + urlFullSource = node.Data[m[0]:m[6]] + filePath + "?display=source#" + hash } filePath, err := url.QueryUnescape(filePath) if err != nil { return nil } - hash := node.Data[m[8]:m[9]] preview.start = m[0] preview.end = m[1] diff --git a/modules/markup/html.go b/modules/markup/html.go index 7961c5c930..58021f5cb5 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -557,9 +557,13 @@ func createCodeLink(href, content, class string) *html.Node { a.Attr = append(a.Attr, html.Attribute{Key: "class", Val: class}) } + unescaped, err := url.QueryUnescape(content) + if err != nil { + unescaped = content + } text := &html.Node{ Type: html.TextNode, - Data: content, + Data: unescaped, } code := &html.Node{ @@ -1142,7 +1146,7 @@ func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) { converted := emoji.FromAlias(alias) if converted == nil { // check if this is a custom reaction - if _, exist := setting.UI.CustomEmojisMap[alias]; exist { + if setting.UI.CustomEmojisLookup.Contains(alias) { replaceContent(node, m[0], m[1], createCustomEmoji(alias)) node = node.NextSibling.NextSibling start = 0 diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 2bc929bb04..11a6290ca3 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -91,6 +91,9 @@ func TestRender_Commits(t *testing.T) { test(sha[:14]+".", `

`+expected14+`.

`) test(sha[:14]+",", `

`+expected14+`,

`) test("["+sha[:14]+"]", `

[`+expected14+`]

`) + + fileStrangeChars := util.URLJoin(repo, "src", "commit", "eeb243c3395e1921c5d90e73bd739827251fc99d", "path", "to", "file%20%23.txt") + test(fileStrangeChars, `

eeb243c339/path/to/file #.txt

`) } func TestRender_CrossReferences(t *testing.T) { @@ -358,7 +361,7 @@ func TestRender_emoji(t *testing.T) { test( ":custom-emoji:", `

:custom-emoji:

`) - setting.UI.CustomEmojisMap["custom-emoji"] = ":custom-emoji:" + setting.UI.CustomEmojisLookup.Add("custom-emoji") test( ":custom-emoji:", `

:custom-emoji:

`) @@ -743,11 +746,11 @@ func TestRender_FilePreview(t *testing.T) { ``+ ``+ ``+ - `B`+"\n"+``+ + `B`+"\n"+``+ ``+ ``+ ``+ - `C`+"\n"+``+ + `C`+"\n"+``+ ``+ ``+ ``+ @@ -777,11 +780,11 @@ func TestRender_FilePreview(t *testing.T) { ``+ ``+ ``+ - `B`+"\n"+``+ + `B`+"\n"+``+ ``+ ``+ ``+ - `C`+"\n"+``+ + `C`+"\n"+``+ ``+ ``+ ``+ @@ -856,11 +859,11 @@ func TestRender_FilePreview(t *testing.T) { ``+ ``+ ``+ - `B`+"\n"+``+ + `B`+"\n"+``+ ``+ ``+ ``+ - `C`+"\n"+``+ + `C`+"\n"+``+ ``+ ``+ ``+ @@ -887,11 +890,11 @@ func TestRender_FilePreview(t *testing.T) { ``+ ``+ ``+ - `B`+"\n"+``+ + `B`+"\n"+``+ ``+ ``+ ``+ - `C`+"\n"+``+ + `C`+"\n"+``+ ``+ ``+ ``+ @@ -920,11 +923,11 @@ func TestRender_FilePreview(t *testing.T) { ``+ ``+ ``+ - `B`+"\n"+``+ + `B`+"\n"+``+ ``+ ``+ ``+ - `C`+"\n"+``+ + `C`+"\n"+``+ ``+ ``+ ``+ @@ -945,11 +948,11 @@ func TestRender_FilePreview(t *testing.T) { ``+ ``+ ``+ - `B`+"\n"+``+ + `B`+"\n"+``+ ``+ ``+ ``+ - `C`+"\n"+``+ + `C`+"\n"+``+ ``+ ``+ ``+ @@ -976,11 +979,11 @@ func TestRender_FilePreview(t *testing.T) { ``+ ``+ ``+ - `B`+"\n"+``+ + `B`+"\n"+``+ ``+ ``+ ``+ - `C`+"\n"+``+ + `C`+"\n"+``+ ``+ ``+ ``+ @@ -1001,11 +1004,11 @@ func TestRender_FilePreview(t *testing.T) { ``+ ``+ ``+ - `B`+"\n"+``+ + `B`+"\n"+``+ ``+ ``+ ``+ - `C`+"\n"+``+ + `C`+"\n"+``+ ``+ ``+ ``+ @@ -1026,11 +1029,11 @@ func TestRender_FilePreview(t *testing.T) { ``+ ``+ ``+ - `B`+"\n"+``+ + `B`+"\n"+``+ ``+ ``+ ``+ - `C`+"\n"+``+ + `C`+"\n"+``+ ``+ ``+ ``+ @@ -1109,6 +1112,39 @@ func TestRender_FilePreview(t *testing.T) { ) }) + t.Run("rendered file with lines L1-2 instead of L1-L2", func(t *testing.T) { + testRender( + commitFileURL+"#L1-2", + `

`+ + `
`+ + `
`+ + `
`+ + `path/to/file.md`+ + `
`+ + ``+ + `Lines 1 to 2 in c991312`+ + ``+ + `
`+ + `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + `
# A`+"\n"+`
B`+"\n"+`
`+ + `
`+ + `
`+ + `

`, + localMetas, + ) + }) + commitFileURL = util.URLJoin(markup.TestRepoURL, "src", "commit", "190d9492934af498c3f669d6a2431dc5459e5b20", "path", "to", "file.go") t.Run("normal file with ?display=source", func(t *testing.T) { @@ -1129,11 +1165,11 @@ func TestRender_FilePreview(t *testing.T) { ``+ ``+ ``+ - `B`+"\n"+``+ + `B`+"\n"+``+ ``+ ``+ ``+ - `C`+"\n"+``+ + `C`+"\n"+``+ ``+ ``+ ``+ diff --git a/modules/markup/markdown/convertyaml.go b/modules/markup/markdown/convertyaml.go index 1675b68be2..8e5a49d931 100644 --- a/modules/markup/markdown/convertyaml.go +++ b/modules/markup/markdown/convertyaml.go @@ -6,7 +6,7 @@ package markdown import ( "github.com/yuin/goldmark/ast" east "github.com/yuin/goldmark/extension/ast" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) func nodeToTable(meta *yaml.Node) ast.Node { diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index d229afa8e3..8a3da3b08f 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -9,6 +9,8 @@ import ( "strings" "forgejo.org/modules/markup" + "forgejo.org/modules/markup/common" + markdownutil "forgejo.org/modules/markup/markdown/util" "forgejo.org/modules/setting" "github.com/yuin/goldmark/ast" @@ -35,8 +37,8 @@ func (g *ASTTransformer) applyElementDir(n ast.Node) { func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { firstChild := node.FirstChild() tocMode := "" - ctx := pc.Get(renderContextKey).(*markup.RenderContext) - rc := pc.Get(renderConfigKey).(*RenderConfig) + ctx := pc.Get(markdownutil.RenderContextKey).(*markup.RenderContext) + rc := pc.Get(markdownutil.RenderConfigKey).(*RenderConfig) tocList := make([]markup.Header, 0, 20) if rc.yamlNode != nil { @@ -73,6 +75,18 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa } case *ast.CodeSpan: g.transformCodeSpan(ctx, v, reader) + case *common.Footnote: + if scope, found := ctx.Metas["scope"]; found { + v.Name = fmt.Appendf(v.Name, "-%s", scope) + } + case *common.FootnoteLink: + if scope, found := ctx.Metas["scope"]; found { + v.Name = fmt.Appendf(v.Name, "-%s", scope) + } + case *common.FootnoteBackLink: + if scope, found := ctx.Metas["scope"]; found { + v.Name = fmt.Appendf(v.Name, "-%s", scope) + } } return ast.WalkContinue, nil }) diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index e811d29994..2b19e0f1c9 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -16,6 +16,7 @@ import ( "forgejo.org/modules/markup/common" "forgejo.org/modules/markup/markdown/callout" "forgejo.org/modules/markup/markdown/math" + markdownutil "forgejo.org/modules/markup/markdown/util" "forgejo.org/modules/setting" giteautil "forgejo.org/modules/util" @@ -34,11 +35,6 @@ var ( specMarkdownOnce sync.Once ) -var ( - renderContextKey = parser.NewContextKey() - renderConfigKey = parser.NewContextKey() -) - type limitWriter struct { w io.Writer sum int64 @@ -64,7 +60,7 @@ func (l *limitWriter) Write(data []byte) (int, error) { // newParserContext creates a parser.Context with the render context set func newParserContext(ctx *markup.RenderContext) parser.Context { pc := parser.NewContext(parser.WithIDs(newPrefixedIDs())) - pc.Set(renderContextKey, ctx) + pc.Set(markdownutil.RenderContextKey, ctx) return pc } @@ -192,7 +188,7 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) } rc.metaLength = metaLength - pc.Set(renderConfigKey, rc) + pc.Set(markdownutil.RenderConfigKey, rc) if err := converter.Convert(buf, lw, parser.WithContext(pc)); err != nil { log.Error("Unable to render: %v", err) diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index f7955115e0..7c13494a67 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -561,6 +561,14 @@ func TestMathBlock(t *testing.T) { "test $$a$$", `

test a

` + nl, }, + { + `\[ +[\triangle ABC] = \sqrt{s(s-a)(s-b)(s-c)} +\]`, + `

[
+[\triangle ABC] = \sqrt{s(s-a)(s-b)(s-c)}
+]

` + nl, + }, } for _, test := range testcases { @@ -568,6 +576,32 @@ func TestMathBlock(t *testing.T) { require.NoError(t, err, "Unexpected error in testcase: %q", test.testcase) assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase) } + + t.Run("Wiki context", func(t *testing.T) { + testcases := []struct { + testcase string + expected string + }{ + { + "$a$", + `

a

` + nl, + }, + { + `\[ +[\triangle ABC] = \sqrt{s(s-a)(s-b)(s-c)} +\]`, + `

+[\triangle ABC] = \sqrt{s(s-a)(s-b)(s-c)}
+
` + nl, + }, + } + + for _, test := range testcases { + res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext, IsWiki: true}, test.testcase) + require.NoError(t, err, "Unexpected error in testcase: %q", test.testcase) + assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase) + } + }) } func TestFootnote(t *testing.T) { @@ -768,6 +802,49 @@ Citation needed[^0].`, } } +func TestFootnoteWithScope(t *testing.T) { + testcases := []struct { + testcase string + expected string + }{ + { + `Citation needed[^0]. +[^0]: Source`, + `

Citation needed1.

+
+
+
    +
  1. +

    Source โ†ฉ๏ธŽ

    +
  2. +
+
+`, + }, { + `[^0]: Source + +Citation needed[^0].`, + `

Citation needed1.

+
+
+
    +
  1. +

    Source โ†ฉ๏ธŽ

    +
  2. +
+
+`, + }, + } + + for _, test := range testcases { + metas := map[string]string{"scope": "comment-999"} + res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext, Metas: metas}, test.testcase) + require.NoError(t, err, "Unexpected error in testcase: %q", test.testcase) + assert.Equal(t, test.expected, string(res), "Unexpected result in testcase %q", test.testcase) + } +} + func TestTaskList(t *testing.T) { testcases := []struct { testcase string @@ -805,6 +882,27 @@ foo: bar } } +func TestRenderCheckList(t *testing.T) { + input := `- [ ] a +- [x] b +1. [x] a +2. [ ] b +5. [ ] e` + expected := `
    +
  • a
  • +
  • b
  • +
+
    +
  1. a
  2. +
  3. b
  4. +
  5. e
  6. +
+` + res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, input) + require.NoError(t, err) + assert.Equal(t, template.HTML(expected), res) +} + func TestRenderLinks(t *testing.T) { input := ` space @mention-user${SPACE}${SPACE} /just/a/path.bin diff --git a/modules/markup/markdown/math/block_parser.go b/modules/markup/markdown/math/block_parser.go index 527df84975..b0fe1d588a 100644 --- a/modules/markup/markdown/math/block_parser.go +++ b/modules/markup/markdown/math/block_parser.go @@ -6,6 +6,9 @@ package math import ( "bytes" + "forgejo.org/modules/markup" + markdownutil "forgejo.org/modules/markup/markdown/util" + "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/text" @@ -61,6 +64,13 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex return node, parser.Close | parser.NoChildren } + ctx := pc.Get(markdownutil.RenderContextKey).(*markup.RenderContext) + if ctx.IsWiki { + reader.Advance(segment.Len() - 1) + segment.Start += 2 + node.Lines().Append(segment) + return node, parser.NoChildren + } return nil, parser.NoChildren } diff --git a/modules/markup/markdown/meta.go b/modules/markup/markdown/meta.go index e76b253ecd..5bdc680e9e 100644 --- a/modules/markup/markdown/meta.go +++ b/modules/markup/markdown/meta.go @@ -9,7 +9,7 @@ import ( "unicode" "unicode/utf8" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) func isYAMLSeparator(line []byte) bool { diff --git a/modules/markup/markdown/renderconfig.go b/modules/markup/markdown/renderconfig.go index 5c3eb1beec..8c278137dc 100644 --- a/modules/markup/markdown/renderconfig.go +++ b/modules/markup/markdown/renderconfig.go @@ -10,7 +10,7 @@ import ( "forgejo.org/modules/markup" "github.com/yuin/goldmark/ast" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) // RenderConfig represents rendering configuration for this file diff --git a/modules/markup/markdown/renderconfig_test.go b/modules/markup/markdown/renderconfig_test.go index c53acdc77a..478f57443e 100644 --- a/modules/markup/markdown/renderconfig_test.go +++ b/modules/markup/markdown/renderconfig_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) func TestRenderConfig_UnmarshalYAML(t *testing.T) { diff --git a/modules/markup/markdown/util/text.go b/modules/markup/markdown/util/text.go index 8a42e5835b..db6e432e79 100644 --- a/modules/markup/markdown/util/text.go +++ b/modules/markup/markdown/util/text.go @@ -7,6 +7,7 @@ import ( "bytes" "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/parser" ) func textOfChildren(n ast.Node, src []byte, b *bytes.Buffer) { @@ -24,3 +25,8 @@ func Text(n ast.Node, src []byte) []byte { textOfChildren(n, src, &b) return b.Bytes() } + +var ( + RenderContextKey = parser.NewContextKey() + RenderConfigKey = parser.NewContextKey() +) diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go index cdaa9f18ce..71157dc7c7 100644 --- a/modules/markup/orgmode/orgmode_test.go +++ b/modules/markup/orgmode/orgmode_test.go @@ -152,9 +152,9 @@ func HelloWorld() { } #+end_src `, `
-
// HelloWorld prints "Hello World"
-func HelloWorld() {
-	fmt.Println("Hello World")
-}
+
// HelloWorld prints "Hello World"
+func HelloWorld() {
+	fmt.Println("Hello World")
+}
`) } diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index a622d75085..08502e12ab 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -248,15 +248,14 @@ type nopCloser struct { func (nopCloser) Close() error { return nil } func renderIFrame(ctx *RenderContext, output io.Writer) error { - // set height="0" ahead, otherwise the scrollHeight would be max(150, realHeight) + // set height="300", otherwise if the postMessage mechanism breaks, we are left with a 0-height iframe // at the moment, only "allow-scripts" is allowed for sandbox mode. // "allow-same-origin" should never be used, it leads to XSS attack, and it makes the JS in iframe can access parent window's config and CSRF token // TODO: when using dark theme, if the rendered content doesn't have proper style, the default text color is black, which is not easy to read _, err := io.WriteString(output, fmt.Sprintf(` `, setting.AppSubURL, @@ -317,6 +316,12 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr if err1 := renderer.Render(ctx, input, pw); err1 != nil { return err1 } + + 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.Close() wg.Wait() diff --git a/modules/migration/file_format.go b/modules/migration/file_format.go index 8851ad6de7..af5f806444 100644 --- a/modules/migration/file_format.go +++ b/modules/migration/file_format.go @@ -13,7 +13,7 @@ import ( "forgejo.org/modules/log" "github.com/santhosh-tekuri/jsonschema/v6" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) // Load project data from file, with optional validation diff --git a/modules/optional/serialization.go b/modules/optional/serialization.go index 86c1c97341..2e2ccd6786 100644 --- a/modules/optional/serialization.go +++ b/modules/optional/serialization.go @@ -6,7 +6,7 @@ package optional import ( "forgejo.org/modules/json" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) func (o *Option[T]) UnmarshalJSON(data []byte) error { diff --git a/modules/optional/serialization_test.go b/modules/optional/serialization_test.go index 14bf3b7814..5da0923a78 100644 --- a/modules/optional/serialization_test.go +++ b/modules/optional/serialization_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) type testSerializationStruct struct { diff --git a/modules/packages/arch/metadata.go b/modules/packages/arch/metadata.go index f967bd25a0..ca352c828c 100644 --- a/modules/packages/arch/metadata.go +++ b/modules/packages/arch/metadata.go @@ -7,6 +7,7 @@ import ( "archive/tar" "bufio" "bytes" + "context" "encoding/hex" "errors" "fmt" @@ -19,7 +20,7 @@ import ( "forgejo.org/modules/util" "forgejo.org/modules/validation" - "github.com/mholt/archiver/v3" + "github.com/mholt/archives" ) // Arch Linux Packages @@ -109,55 +110,61 @@ func ParsePackage(r *packages.HashedBuffer) (*Package, error) { return nil, err } - var tarball archiver.Reader + var tarball archives.Extractor var tarballType string if bytes.Equal(header[:len(magicZSTD)], magicZSTD) { tarballType = "zst" - tarball = archiver.NewTarZstd() + tarball = archives.CompressedArchive{ + Compression: archives.Zstd{}, + Extraction: archives.Tar{}, + } } else if bytes.Equal(header[:len(magicXZ)], magicXZ) { tarballType = "xz" - tarball = archiver.NewTarXz() + tarball = archives.CompressedArchive{ + Compression: archives.Xz{}, + Extraction: archives.Tar{}, + } } else if bytes.Equal(header[:len(magicGZ)], magicGZ) { tarballType = "gz" - tarball = archiver.NewTarGz() + tarball = archives.CompressedArchive{ + Compression: archives.Gz{}, + Extraction: archives.Tar{}, + } } else { return nil, errors.New("not supported compression") } - err = tarball.Open(r, 0) - if err != nil { - return nil, err - } - defer tarball.Close() var pkg *Package var mTree bool files := make([]string, 0) - for { - f, err := tarball.Read() - if err == io.EOF { - break - } - if err != nil { - return nil, err - } + err = tarball.Extract(context.TODO(), r, func(ctx context.Context, file archives.FileInfo) error { // ref:https://gitlab.archlinux.org/pacman/pacman/-/blob/91546004903eea5d5267d59898a6029ba1d64031/lib/libalpm/add.c#L529-L533 - if !strings.HasPrefix(f.Name(), ".") { - files = append(files, (f.Header.(*tar.Header)).Name) + if !strings.HasPrefix(file.Name(), ".") { + files = append(files, (file.Header.(*tar.Header)).Name) } - switch f.Name() { + switch file.Name() { case ".PKGINFO": + f, err := file.Open() + if err != nil { + return err + } + defer f.Close() + pkg, err = ParsePackageInfo(tarballType, f) if err != nil { - _ = f.Close() - return nil, err + return err } case ".MTREE": mTree = true } - _ = f.Close() + + return nil + }) + if err != nil { + return nil, err } if pkg == nil { diff --git a/modules/packages/arch/metadata_test.go b/modules/packages/arch/metadata_test.go index 16c1c1637d..fbdc9e5ddc 100644 --- a/modules/packages/arch/metadata_test.go +++ b/modules/packages/arch/metadata_test.go @@ -5,7 +5,7 @@ package arch import ( "bytes" - "errors" + "io/fs" "os" "strings" "testing" @@ -14,7 +14,7 @@ import ( "forgejo.org/modules/packages" - "github.com/mholt/archiver/v3" + "github.com/mholt/archives" "github.com/stretchr/testify/require" ) @@ -25,7 +25,7 @@ pkgbase = b pkgver = 1-2 arch = x86_64 ` - fs := fstest.MapFS{ + mapfs := fstest.MapFS{ "pkginfo": &fstest.MapFile{ Data: []byte(PKGINFO), Mode: os.ModePerm, @@ -39,65 +39,111 @@ arch = x86_64 } // Test .PKGINFO file - pinf, err := fs.Stat("pkginfo") - require.NoError(t, err) - - pfile, err := fs.Open("pkginfo") - require.NoError(t, err) - - parcname, err := archiver.NameInArchive(pinf, ".PKGINFO", ".PKGINFO") + pinf, err := mapfs.Stat("pkginfo") require.NoError(t, err) // Test .MTREE file - minf, err := fs.Stat("mtree") + minf, err := mapfs.Stat("mtree") require.NoError(t, err) - mfile, err := fs.Open("mtree") - require.NoError(t, err) + files := []archives.FileInfo{ + { + FileInfo: pinf, + NameInArchive: ".PKGINFO", + Open: func() (fs.File, error) { + return mapfs.Open("pkginfo") + }, + }, + { + FileInfo: minf, + NameInArchive: ".MTREE", + Open: func() (fs.File, error) { + return mapfs.Open("mtree") + }, + }, + } - marcname, err := archiver.NameInArchive(minf, ".MTREE", ".MTREE") - require.NoError(t, err) - - t.Run("normal archive", func(t *testing.T) { + t.Run("normal zst archive", func(t *testing.T) { var buf bytes.Buffer - - archive := archiver.NewTarZstd() - archive.Create(&buf) - - err = archive.Write(archiver.File{ - FileInfo: archiver.FileInfo{ - FileInfo: pinf, - CustomName: parcname, - }, - ReadCloser: pfile, - }) - require.NoError(t, errors.Join(pfile.Close(), err)) - - err = archive.Write(archiver.File{ - FileInfo: archiver.FileInfo{ - FileInfo: minf, - CustomName: marcname, - }, - ReadCloser: mfile, - }) - require.NoError(t, errors.Join(mfile.Close(), archive.Close(), err)) + archive := archives.CompressedArchive{ + Archival: archives.Tar{}, + Compression: archives.Zstd{}, + } + archive.Archive(t.Context(), &buf, files) reader, err := packages.CreateHashedBufferFromReader(&buf) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer reader.Close() - _, err = ParsePackage(reader) + pkg, err := ParsePackage(reader) + + require.Equal(t, "zst", pkg.CompressType) + require.Equal(t, "a", pkg.Name) + require.Equal(t, "b", pkg.VersionMetadata.Base) + require.Equal(t, "x86_64", pkg.FileMetadata.Arch) + require.Equal(t, "1-2", pkg.Version) + + require.NoError(t, err) + }) + + t.Run("normal xz archive", func(t *testing.T) { + var buf bytes.Buffer + archive := archives.CompressedArchive{ + Archival: archives.Tar{}, + Compression: archives.Xz{}, + } + archive.Archive(t.Context(), &buf, files) + + reader, err := packages.CreateHashedBufferFromReader(&buf) + require.NoError(t, err) + defer reader.Close() + pkg, err := ParsePackage(reader) + + require.Equal(t, "xz", pkg.CompressType) + require.Equal(t, "a", pkg.Name) + require.Equal(t, "b", pkg.VersionMetadata.Base) + require.Equal(t, "x86_64", pkg.FileMetadata.Arch) + require.Equal(t, "1-2", pkg.Version) + + require.NoError(t, err) + }) + + t.Run("normal gz archive", func(t *testing.T) { + var buf bytes.Buffer + archive := archives.CompressedArchive{ + Archival: archives.Tar{}, + Compression: archives.Gz{}, + } + archive.Archive(t.Context(), &buf, files) + + reader, err := packages.CreateHashedBufferFromReader(&buf) + require.NoError(t, err) + defer reader.Close() + pkg, err := ParsePackage(reader) + + require.Equal(t, "gz", pkg.CompressType) + require.Equal(t, "a", pkg.Name) + require.Equal(t, "b", pkg.VersionMetadata.Base) + require.Equal(t, "x86_64", pkg.FileMetadata.Arch) + require.Equal(t, "1-2", pkg.Version) require.NoError(t, err) }) t.Run("missing .PKGINFO", func(t *testing.T) { var buf bytes.Buffer - - archive := archiver.NewTarZstd() - archive.Create(&buf) - require.NoError(t, archive.Close()) + archive := archives.CompressedArchive{ + Archival: archives.Tar{}, + Compression: archives.Zstd{}, + } + archive.Archive(t.Context(), &buf, []archives.FileInfo{ + { + FileInfo: minf, + NameInArchive: ".MTREE", + Open: func() (fs.File, error) { + return mapfs.Open("mtree") + }, + }, + }) reader, err := packages.CreateHashedBufferFromReader(&buf) require.NoError(t, err) @@ -111,21 +157,20 @@ arch = x86_64 t.Run("missing .MTREE", func(t *testing.T) { var buf bytes.Buffer - - pfile, err := fs.Open("pkginfo") - require.NoError(t, err) - - archive := archiver.NewTarZstd() - archive.Create(&buf) - - err = archive.Write(archiver.File{ - FileInfo: archiver.FileInfo{ - FileInfo: pinf, - CustomName: parcname, + archive := archives.CompressedArchive{ + Archival: archives.Tar{}, + Compression: archives.Zstd{}, + } + archive.Archive(t.Context(), &buf, []archives.FileInfo{ + { + FileInfo: pinf, + NameInArchive: ".PKGINFO", + Open: func() (fs.File, error) { + return mapfs.Open("pkginfo") + }, }, - ReadCloser: pfile, }) - require.NoError(t, errors.Join(pfile.Close(), archive.Close(), err)) + reader, err := packages.CreateHashedBufferFromReader(&buf) require.NoError(t, err) diff --git a/modules/packages/debian/metadata.go b/modules/packages/debian/metadata.go index e44801654b..1729a2206e 100644 --- a/modules/packages/debian/metadata.go +++ b/modules/packages/debian/metadata.go @@ -46,7 +46,7 @@ var ( // https://www.debian.org/doc/debian-policy/ch-controlfields.html#source namePattern = regexp.MustCompile(`\A[a-z0-9][a-z0-9+-.]+\z`) // https://www.debian.org/doc/debian-policy/ch-controlfields.html#version - versionPattern = regexp.MustCompile(`\A(?:[0-9]:)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`) + versionPattern = regexp.MustCompile(`\A(?:[1-9]?[0-9]:)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`) ) type Package struct { diff --git a/modules/packages/debian/metadata_test.go b/modules/packages/debian/metadata_test.go index cfcbc57ee0..079b9c19c8 100644 --- a/modules/packages/debian/metadata_test.go +++ b/modules/packages/debian/metadata_test.go @@ -167,6 +167,14 @@ func TestParseControlFile(t *testing.T) { require.ErrorIs(t, err, ErrInvalidArchitecture) }) + t.Run("ValidVersionEpoch", func(t *testing.T) { + for _, version := range []string{"0:1.2.3-test", "1:1.2.3-test", "9:1.2.3-test", "10:1.2.3-test", "37:1.2.3-test", "99:1.2.3-test"} { + p, err := ParseControlFile(buildContent(packageName, version, packageArchitecture)) + require.NoError(t, err) + assert.NotNil(t, p) + } + }) + t.Run("Valid", func(t *testing.T) { content := buildContent(packageName, packageVersion, packageArchitecture) full := content.String() diff --git a/modules/packages/helm/metadata.go b/modules/packages/helm/metadata.go index 19a30c5ffa..4d3eaaa40c 100644 --- a/modules/packages/helm/metadata.go +++ b/modules/packages/helm/metadata.go @@ -13,7 +13,7 @@ import ( "forgejo.org/modules/validation" "github.com/hashicorp/go-version" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) var ( diff --git a/modules/packages/pub/metadata.go b/modules/packages/pub/metadata.go index f8afdf7218..e1d4286f97 100644 --- a/modules/packages/pub/metadata.go +++ b/modules/packages/pub/metadata.go @@ -14,7 +14,7 @@ import ( "forgejo.org/modules/validation" "github.com/hashicorp/go-version" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) var ( diff --git a/modules/packages/rubygems/metadata.go b/modules/packages/rubygems/metadata.go index 6d021a17ab..9d3fd18f12 100644 --- a/modules/packages/rubygems/metadata.go +++ b/modules/packages/rubygems/metadata.go @@ -13,7 +13,7 @@ import ( "forgejo.org/modules/util" "forgejo.org/modules/validation" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) var ( diff --git a/modules/private/hook.go b/modules/private/hook.go index 2d64c1dec9..89d44119ea 100644 --- a/modules/private/hook.go +++ b/modules/private/hook.go @@ -11,6 +11,7 @@ import ( "forgejo.org/modules/git" "forgejo.org/modules/git/pushoptions" + "forgejo.org/modules/log" "forgejo.org/modules/repository" "forgejo.org/modules/setting" ) @@ -46,7 +47,7 @@ func (o *HookOptions) GetGitPushOptions() pushoptions.Interface { // SSHLogOption ssh log options type SSHLogOption struct { - IsError bool + Level log.Level Message string } @@ -121,9 +122,9 @@ func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) R } // SSHLog sends ssh error log response -func SSHLog(ctx context.Context, isErr bool, msg string) error { +func SSHLog(ctx context.Context, level log.Level, msg string) error { reqURL := setting.LocalURL + "api/internal/ssh/log" - req := newInternalRequest(ctx, reqURL, "POST", &SSHLogOption{IsError: isErr, Message: msg}) + req := newInternalRequest(ctx, reqURL, "POST", &SSHLogOption{Level: level, Message: msg}) _, extra := requestJSONResp(req, &ResponseText{}) return extra.Error } diff --git a/modules/setting/config_provider.go b/modules/setting/config_provider.go index 19f3b9008a..6325199637 100644 --- a/modules/setting/config_provider.go +++ b/modules/setting/config_provider.go @@ -6,6 +6,7 @@ package setting import ( "errors" "fmt" + "math" "os" "path/filepath" "strconv" @@ -15,6 +16,7 @@ import ( "forgejo.org/modules/log" "forgejo.org/modules/util" + "github.com/dustin/go-humanize" "gopkg.in/ini.v1" //nolint:depguard ) @@ -320,6 +322,16 @@ func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) { } } +// mustBytes returns -1 on parse error, or value out of range 0 to math.MaxInt64 +func mustBytes(section ConfigSection, key string) int64 { + value := section.Key(key).String() + bytes, err := humanize.ParseBytes(value) + if err != nil || bytes > math.MaxInt64 { + return -1 + } + return int64(bytes) +} + // DeprecatedWarnings contains the warning message for various deprecations, including: setting option, file/folder, etc var DeprecatedWarnings []string @@ -331,6 +343,14 @@ func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, n } } +func deprecatedSettingWarning(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey string) { //nolint:unparam + if rootCfg.Section(oldSection).HasKey(oldKey) { + msg := fmt.Sprintf("Deprecated config option `[%s]` `%s` present. Use `[%s]` `%s` instead.", oldSection, oldKey, newSection, newKey) + log.Error("%v", msg) + DeprecatedWarnings = append(DeprecatedWarnings, msg) + } +} + // deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) { if rootCfg.Section(oldSection).HasKey(oldKey) { diff --git a/modules/setting/config_provider_test.go b/modules/setting/config_provider_test.go index 3b99911f38..3588784b81 100644 --- a/modules/setting/config_provider_test.go +++ b/modules/setting/config_provider_test.go @@ -155,3 +155,24 @@ func TestDisableSaving(t *testing.T) { require.NoError(t, err) assert.Equal(t, "k1 = a\nk2 = y\nk3 = z\n", string(bs)) } + +func TestMustBytes(t *testing.T) { + test := func(value string) int64 { + cfg, err := NewConfigProviderFromData("[test]") + require.NoError(t, err) + sec := cfg.Section("test") + sec.NewKey("VALUE", value) + + return mustBytes(sec, "VALUE") + } + + assert.EqualValues(t, -1, test("")) + assert.EqualValues(t, -1, test("-1")) + assert.EqualValues(t, 0, test("0")) + assert.EqualValues(t, 1, test("1")) + assert.EqualValues(t, 10000, test("10000")) + assert.EqualValues(t, 1000000, test("1 mb")) + assert.EqualValues(t, 1048576, test("1mib")) + assert.EqualValues(t, 1782579, test("1.7mib")) + assert.EqualValues(t, -1, test("1 yib")) // too large +} diff --git a/modules/setting/cron_test.go b/modules/setting/cron_test.go index cabfb3a325..ba73c4039d 100644 --- a/modules/setting/cron_test.go +++ b/modules/setting/cron_test.go @@ -42,3 +42,56 @@ EXTEND = true assert.Equal(t, "white rabbit", extended.Second) assert.True(t, extended.Extend) } + +// Test_getCronSettings2 tests that getCronSettings can not handle two levels of embedding +func Test_getCronSettings2(t *testing.T) { + type BaseStruct struct { + Enabled bool + RunAtStart bool + Schedule string + } + + type Extended struct { + BaseStruct + Extend bool + } + type Extended2 struct { + Extended + Third string + } + + iniStr := ` +[cron.test] +ENABLED = TRUE +RUN_AT_START = TRUE +SCHEDULE = @every 1h +EXTEND = true +THIRD = white rabbit +` + cfg, err := NewConfigProviderFromData(iniStr) + require.NoError(t, err) + + extended := &Extended2{ + Extended: Extended{ + BaseStruct: BaseStruct{ + Enabled: false, + RunAtStart: false, + Schedule: "@every 72h", + }, + Extend: false, + }, + Third: "black rabbit", + } + + _, err = getCronSettings(cfg, "test", extended) + require.NoError(t, err) + + // This confirms the first level of embedding works + assert.Equal(t, "white rabbit", extended.Third) + assert.True(t, extended.Extend) + + // This confirms 2 levels of embedding doesn't work + assert.False(t, extended.Enabled) + assert.False(t, extended.RunAtStart) + assert.Equal(t, "@every 72h", extended.Schedule) +} diff --git a/modules/setting/incoming_email.go b/modules/setting/incoming_email.go index e592220de6..a890a4a328 100644 --- a/modules/setting/incoming_email.go +++ b/modules/setting/incoming_email.go @@ -44,9 +44,14 @@ func loadIncomingEmailFrom(rootCfg ConfigProvider) { if sec.HasKey("USER") && !sec.HasKey("USERNAME") { IncomingEmail.Username = sec.Key("USER").String() } + if sec.HasKey("PASSWD") && !sec.HasKey("PASSWORD") { - IncomingEmail.Password = sec.Key("PASSWD").String() + sec.Key("PASSWORD").SetValue(sec.Key("PASSWD").String()) } + if sec.HasKey("PASSWD_URI") && !sec.HasKey("PASSWORD_URI") { + sec.Key("PASSWORD_URI").SetValue(sec.Key("PASSWD_URI").String()) + } + IncomingEmail.Password = loadSecret(sec, "PASSWORD_URI", "PASSWORD") // Infer Port if not set if IncomingEmail.Port == 0 { diff --git a/modules/setting/incoming_email_test.go b/modules/setting/incoming_email_test.go index 6d181cae3c..4ea740bafd 100644 --- a/modules/setting/incoming_email_test.go +++ b/modules/setting/incoming_email_test.go @@ -4,6 +4,8 @@ package setting import ( + "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -35,6 +37,22 @@ func Test_loadIncomingEmailFrom(t *testing.T) { assert.Equal(t, "y0u'll n3v3r gUess th1S!!1", IncomingEmail.Password) }) + t.Run("Secrets", func(t *testing.T) { + uri := filepath.Join(t.TempDir(), "email_incoming_password") + + if err := os.WriteFile(uri, []byte("th1S gUess n3v3r y0u'll!!1"), 0o644); err != nil { + t.Fatal(err) + } + + cfg, sec := makeBaseConfig() + sec.NewKey("PASSWORD_URI", "file:"+uri) + + IncomingEmail.Password = "" + loadIncomingEmailFrom(cfg) + + assert.Equal(t, "th1S gUess n3v3r y0u'll!!1", IncomingEmail.Password) + }) + t.Run("Port settings", func(t *testing.T) { t.Run("no port, no tls", func(t *testing.T) { defer resetIncomingEmailPort()() diff --git a/modules/setting/log.go b/modules/setting/log.go index 0747ac4dac..ecc591fd35 100644 --- a/modules/setting/log.go +++ b/modules/setting/log.go @@ -23,8 +23,6 @@ type LogGlobalConfig struct { StacktraceLogLevel log.Level BufferLen int - EnableSSHLog bool - AccessLogTemplate string RequestIDHeaders []string } @@ -47,8 +45,6 @@ func loadLogGlobalFrom(rootCfg ConfigProvider) { } Log.RootPath = util.FilePathJoinAbs(Log.RootPath) - Log.EnableSSHLog = sec.Key("ENABLE_SSH_LOG").MustBool(false) - Log.AccessLogTemplate = sec.Key("ACCESS_LOG_TEMPLATE").MustString(accessLogTemplateDefault) Log.RequestIDHeaders = sec.Key("REQUEST_ID_HEADERS").Strings(",") } @@ -56,41 +52,83 @@ func loadLogGlobalFrom(rootCfg ConfigProvider) { func prepareLoggerConfig(rootCfg ConfigProvider) { sec := rootCfg.Section("log") - if !sec.HasKey("logger.default.MODE") { - sec.Key("logger.default.MODE").MustString(",") + // Priority: `LOGGER_DEFAULT_MODE` -> `logger.default.MODE` + deprecatedSettingWarning(rootCfg, "log", "logger.default.MODE", "log", "LOGGER_DEFAULT_MODE") + hasNoValue := !sec.HasKey("LOGGER_DEFAULT_MODE") + if hasNoValue && sec.HasKey("logger.default.MODE") { + sec.Key("LOGGER_DEFAULT_MODE").SetValue(sec.Key("logger.default.MODE").String()) + hasNoValue = false + } + if hasNoValue { + sec.Key("LOGGER_DEFAULT_MODE").SetValue(",") // use default logger } - deprecatedSetting(rootCfg, "log", "ACCESS", "log", "logger.access.MODE", "1.21") - deprecatedSetting(rootCfg, "log", "ENABLE_ACCESS_LOG", "log", "logger.access.MODE", "1.21") - if val := sec.Key("ACCESS").String(); val != "" { - sec.Key("logger.access.MODE").MustString(val) + // Priority: `ENABLE_ACCESS_LOG` -> `LOGGER_ACCESS_MODE` -> `logger.access.MODE` -> `ACCESS` + deprecatedSettingWarning(rootCfg, "log", "ACCESS", "log", "LOGGER_ACCESS_MODE") + deprecatedSettingWarning(rootCfg, "log", "ENABLE_ACCESS_LOG", "log", "LOGGER_ACCESS_MODE") + deprecatedSettingWarning(rootCfg, "log", "logger.access.MODE", "log", "LOGGER_ACCESS_MODE") + hasNoValue = !sec.HasKey("LOGGER_ACCESS_MODE") + if hasNoValue && sec.HasKey("logger.access.MODE") { + sec.Key("LOGGER_ACCESS_MODE").SetValue(sec.Key("logger.access.MODE").String()) + hasNoValue = false + } + if val := sec.Key("ACCESS").String(); hasNoValue && val != "" { + sec.Key("LOGGER_ACCESS_MODE").SetValue(val) } if sec.HasKey("ENABLE_ACCESS_LOG") && !sec.Key("ENABLE_ACCESS_LOG").MustBool() { - sec.Key("logger.access.MODE").SetValue("") + sec.Key("LOGGER_ACCESS_MODE").SetValue("") } - deprecatedSetting(rootCfg, "log", "ROUTER", "log", "logger.router.MODE", "1.21") - deprecatedSetting(rootCfg, "log", "DISABLE_ROUTER_LOG", "log", "logger.router.MODE", "1.21") - if val := sec.Key("ROUTER").String(); val != "" { - sec.Key("logger.router.MODE").MustString(val) + // Priority: `DISABLE_ROUTER_LOG` -> `LOGGER_ROUTER_MODE` -> `logger.router.MODE` -> `ROUTER` + deprecatedSettingWarning(rootCfg, "log", "ROUTER", "log", "LOGGER_ROUTER_MODE") + deprecatedSettingWarning(rootCfg, "log", "DISABLE_ROUTER_LOG", "log", "LOGGER_ROUTER_MODE") + deprecatedSettingWarning(rootCfg, "log", "logger.router.MODE", "log", "LOGGER_ROUTER_MODE") + hasNoValue = !sec.HasKey("LOGGER_ROUTER_MODE") + if hasNoValue && sec.HasKey("logger.router.MODE") { + sec.Key("LOGGER_ROUTER_MODE").SetValue(sec.Key("logger.router.MODE").String()) + hasNoValue = false } - if !sec.HasKey("logger.router.MODE") { - sec.Key("logger.router.MODE").MustString(",") // use default logger + if val := sec.Key("ROUTER").String(); hasNoValue && val != "" { + sec.Key("LOGGER_ROUTER_MODE").SetValue(val) + hasNoValue = false } if sec.HasKey("DISABLE_ROUTER_LOG") && sec.Key("DISABLE_ROUTER_LOG").MustBool() { - sec.Key("logger.router.MODE").SetValue("") + sec.Key("LOGGER_ROUTER_MODE").SetValue("") + hasNoValue = false + } + if hasNoValue { + sec.Key("LOGGER_ROUTER_MODE").SetValue(",") // use default logger } - deprecatedSetting(rootCfg, "log", "XORM", "log", "logger.xorm.MODE", "1.21") - deprecatedSetting(rootCfg, "log", "ENABLE_XORM_LOG", "log", "logger.xorm.MODE", "1.21") - if val := sec.Key("XORM").String(); val != "" { - sec.Key("logger.xorm.MODE").MustString(val) + // Priority: `ENABLE_XORM_LOG` -> `LOGGER_XORM_MODE` -> `logger.xorm.MODE` -> `XORM` + deprecatedSettingWarning(rootCfg, "log", "XORM", "log", "LOGGER_XORM_MODE") + deprecatedSettingWarning(rootCfg, "log", "ENABLE_XORM_LOG", "log", "LOGGER_XORM_MODE") + deprecatedSettingWarning(rootCfg, "log", "logger.xorm.MODE", "log", "LOGGER_XORM_MODE") + hasNoValue = !sec.HasKey("LOGGER_XORM_MODE") + if hasNoValue && sec.HasKey("logger.xorm.MODE") { + sec.Key("LOGGER_XORM_MODE").SetValue(sec.Key("logger.xorm.MODE").String()) + hasNoValue = false } - if !sec.HasKey("logger.xorm.MODE") { - sec.Key("logger.xorm.MODE").MustString(",") // use default logger + if val := sec.Key("XORM").String(); hasNoValue && val != "" { + sec.Key("LOGGER_XORM_MODE").SetValue(val) + hasNoValue = false } if sec.HasKey("ENABLE_XORM_LOG") && !sec.Key("ENABLE_XORM_LOG").MustBool() { - sec.Key("logger.xorm.MODE").SetValue("") + sec.Key("LOGGER_XORM_MODE").SetValue("") + hasNoValue = false + } + if hasNoValue { + sec.Key("LOGGER_XORM_MODE").SetValue(",") // use default logger + } + + // Priority: `LOGGER_SSH_MODE` -> `ENABLE_SSH_LOG` + deprecatedSettingWarning(rootCfg, "log", "ENABLE_SSH_LOG", "log", "LOGGER_SSH_MODE") + if !sec.HasKey("LOGGER_SSH_MODE") && sec.HasKey("ENABLE_SSH_LOG") { + if sec.Key("ENABLE_SSH_LOG").MustBool() { + sec.Key("LOGGER_SSH_MODE").SetValue(",") // use default logger + } else { + sec.Key("LOGGER_SSH_MODE").SetValue("") // disable ssh logger + } } } @@ -133,6 +171,7 @@ func loadLogModeByName(rootCfg ConfigProvider, loggerName, modeName string) (wri writerMode.StacktraceLevel = log.LevelFromString(ConfigInheritedKeyString(sec, "STACKTRACE_LEVEL", Log.StacktraceLogLevel.String())) writerMode.Prefix = ConfigInheritedKeyString(sec, "PREFIX") writerMode.Expression = ConfigInheritedKeyString(sec, "EXPRESSION") + writerMode.Exclusion = ConfigInheritedKeyString(sec, "EXCLUSION") // flags are updated and set below switch writerType { @@ -212,18 +251,19 @@ func initManagedLoggers(manager *log.LoggerManager, cfg ConfigProvider) { initLoggerByName(manager, cfg, "access") initLoggerByName(manager, cfg, "router") initLoggerByName(manager, cfg, "xorm") + initLoggerByName(manager, cfg, "ssh") } func initLoggerByName(manager *log.LoggerManager, rootCfg ConfigProvider, loggerName string) { sec := rootCfg.Section("log") - keyPrefix := "logger." + loggerName + key := "LOGGER_" + strings.ToUpper(loggerName) + "_MODE" - disabled := sec.HasKey(keyPrefix+".MODE") && sec.Key(keyPrefix+".MODE").String() == "" + disabled := sec.HasKey(key) && sec.Key(key).String() == "" if disabled { return } - modeVal := sec.Key(keyPrefix + ".MODE").String() + modeVal := sec.Key(key).String() if modeVal == "," { modeVal = Log.Mode } diff --git a/modules/setting/log_test.go b/modules/setting/log_test.go index eda6dc36af..1e523d50d7 100644 --- a/modules/setting/log_test.go +++ b/modules/setting/log_test.go @@ -10,16 +10,13 @@ import ( "forgejo.org/modules/json" "forgejo.org/modules/log" + "forgejo.org/modules/test" "github.com/stretchr/testify/require" ) func initLoggersByConfig(t *testing.T, config string) (*log.LoggerManager, func()) { - oldLogConfig := Log - Log = LogGlobalConfig{} - defer func() { - Log = oldLogConfig - }() + defer test.MockVariableValue(&Log, LogGlobalConfig{})() cfg, err := NewConfigProviderFromData(config) require.NoError(t, err) @@ -29,6 +26,17 @@ func initLoggersByConfig(t *testing.T, config string) (*log.LoggerManager, func( return manager, manager.Close } +func initLoggerConfig(t *testing.T, config string) ConfigProvider { + defer test.MockVariableValue(&Log, LogGlobalConfig{})() + + cfg, err := NewConfigProviderFromData(config) + require.NoError(t, err) + + prepareLoggerConfig(cfg) + + return cfg +} + func toJSON(v any) string { b, _ := json.MarshalIndent(v, "", "\t") return string(b) @@ -44,6 +52,7 @@ func TestLogConfigDefault(t *testing.T) { "BufferLen": 10000, "Colorize": false, "Expression": "", + "Exclusion": "", "Flags": "stdflags", "Level": "info", "Prefix": "", @@ -83,6 +92,7 @@ logger.xorm.MODE = "BufferLen": 10000, "Colorize": false, "Expression": "", + "Exclusion": "", "Flags": "stdflags", "Level": "info", "Prefix": "", @@ -121,6 +131,7 @@ MODE = console "BufferLen": 10000, "Colorize": false, "Expression": "", + "Exclusion": "", "Flags": "stdflags", "Level": "info", "Prefix": "", @@ -168,6 +179,7 @@ ACCESS = file "BufferLen": 10000, "Colorize": false, "Expression": "", + "Exclusion": "", "Flags": "stdflags", "Level": "info", "Prefix": "", @@ -191,6 +203,7 @@ ACCESS = file "BufferLen": 10000, "Colorize": false, "Expression": "", + "Exclusion": "", "Flags": "none", "Level": "info", "Prefix": "", @@ -238,8 +251,8 @@ ENABLE_ACCESS_LOG = false func TestLogConfigNewConfig(t *testing.T) { manager, managerClose := initLoggersByConfig(t, ` [log] -logger.access.MODE = console -logger.xorm.MODE = console, console-1 +LOGGER_ACCESS_MODE = console +LOGGER_XORM_MODE = console, console-1 [log.console] LEVEL = warn @@ -257,6 +270,7 @@ STDERR = true "BufferLen": 10000, "Colorize": false, "Expression": "", + "Exclusion": "", "Flags": "stdflags", "Level": "warn", "Prefix": "", @@ -270,6 +284,7 @@ STDERR = true "BufferLen": 10000, "Colorize": false, "Expression": "", + "Exclusion": "", "Flags": "stdflags", "Level": "error", "Prefix": "", @@ -287,6 +302,7 @@ STDERR = true "BufferLen": 10000, "Colorize": false, "Expression": "", + "Exclusion": "", "Flags": "none", "Level": "warn", "Prefix": "", @@ -323,6 +339,7 @@ MODE = file LEVEL = error STACKTRACE_LEVEL = fatal EXPRESSION = filter +EXCLUSION = not FLAGS = medfile PREFIX = "[Prefix] " FILE_NAME = file-xxx.log @@ -341,6 +358,7 @@ COMPRESSION_LEVEL = 4 "BufferLen": 10, "Colorize": false, "Expression": "", + "Exclusion": "", "Flags": "stdflags", "Level": "info", "Prefix": "", @@ -360,6 +378,7 @@ COMPRESSION_LEVEL = 4 "BufferLen": 10, "Colorize": false, "Expression": "filter", + "Exclusion": "not", "Flags": "medfile", "Level": "error", "Prefix": "[Prefix] ", @@ -384,3 +403,216 @@ COMPRESSION_LEVEL = 4 expected = strings.ReplaceAll(expected, "$FILENAME-1", tempPath("file-xxx.log")) require.JSONEq(t, expected, toJSON(dump)) } + +func TestLegacyLoggerMigrations(t *testing.T) { + type Cases = []struct { + name string + cfg string + exp string + } + + runCases := func(t *testing.T, key string, cases Cases) { + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + cfg := initLoggerConfig(t, c.cfg) + require.Equal(t, c.exp, cfg.Section("log").Key(key).String()) + }) + } + } + + t.Run("default", func(t *testing.T) { + runCases(t, "LOGGER_DEFAULT_MODE", Cases{ + { + "uses default value for default logger", + "", + ",", + }, + { + "uses logger.default.MODE for default logger", + `[log] +logger.default.MODE = file +`, + "file", + }, + }) + }) + + t.Run("access", func(t *testing.T) { + runCases(t, "LOGGER_ACCESS_MODE", Cases{ + { + "uses default value for access logger", + "", + "", + }, + { + "uses ACCESS for access logger", + `[log] +ACCESS = file +`, + "file", + }, + { + "ENABLE_ACCESS_LOG=true doesn't change access logger", + `[log] +ENABLE_ACCESS_LOG = true +logger.access.MODE = console +`, + "console", + }, + { + "ENABLE_ACCESS_LOG=false disables access logger", + `[log] +ENABLE_ACCESS_LOG = false +logger.access.MODE = console +`, + "", + }, + { + "logger.access.MODE has precedence over ACCESS for access logger", + `[log] +ACCESS = file +logger.access.MODE = console +`, + "console", + }, + { + "LOGGER_ACCESS_MODE has precedence over logger.access.MODE for access logger", + `[log] +LOGGER_ACCESS_MODE = file +logger.access.MODE = console +`, + "file", + }, + { + "ENABLE_ACCESS_LOG doesn't enable access logger", + `[log] +ENABLE_ACCESS_LOG = true +`, + "", // should be `,` + }, + }) + }) + + t.Run("router", func(t *testing.T) { + runCases(t, "LOGGER_ROUTER_MODE", Cases{ + { + "uses default value for router logger", + "", + ",", + }, + { + "uses ROUTER for router logger", + `[log] +ROUTER = file +`, + "file", + }, + { + "DISABLE_ROUTER_LOG=false doesn't change router logger", + `[log] +ROUTER = file +DISABLE_ROUTER_LOG = false +`, + "file", + }, + { + "DISABLE_ROUTER_LOG=true disables router logger", + `[log] +DISABLE_ROUTER_LOG = true +logger.router.MODE = console +`, + "", + }, + { + "logger.router.MODE as precedence over ROUTER for router logger", + `[log] +ROUTER = file +logger.router.MODE = console +`, + "console", + }, + { + "LOGGER_ROUTER_MODE has precedence over logger.router.MODE for router logger", + `[log] +LOGGER_ROUTER_MODE = file +logger.router.MODE = console +`, + "file", + }, + }) + }) + + t.Run("xorm", func(t *testing.T) { + runCases(t, "LOGGER_XORM_MODE", Cases{ + { + "uses default value for xorm logger", + "", + ",", + }, + { + "uses XORM for xorm logger", + `[log] +XORM = file +`, + "file", + }, + { + "ENABLE_XORM_LOG=true doesn't change xorm logger", + `[log] +ENABLE_XORM_LOG = true +logger.xorm.MODE = console +`, + "console", + }, + { + "ENABLE_XORM_LOG=false disables xorm logger", + `[log] +ENABLE_XORM_LOG = false +logger.xorm.MODE = console +`, + "", + }, + { + "logger.xorm.MODE has precedence over XORM for xorm logger", + `[log] +XORM = file +logger.xorm.MODE = console +`, + "console", + }, + { + "LOGGER_XORM_MODE has precedence over logger.xorm.MODE for xorm logger", + `[log] +LOGGER_XORM_MODE = file +logger.xorm.MODE = console +`, + "file", + }, + }) + }) + + t.Run("ssh", func(t *testing.T) { + runCases(t, "LOGGER_SSH_MODE", Cases{ + { + "uses default value for ssh logger", + "", + "", + }, + { + "deprecated config can enable logger", + `[log] +ENABLE_SSH_LOG = true +`, + ",", + }, + { + "check priority", + `[log] +LOGGER_SSH_MODE = file +ENABLE_SSH_LOG = true +`, + "file", + }, + }) + }) +} diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go index 9c004c6ce0..b43484a90f 100644 --- a/modules/setting/mailer.go +++ b/modules/setting/mailer.go @@ -147,6 +147,10 @@ func loadMailerFrom(rootCfg ConfigProvider) { if sec.HasKey("PASSWORD") && !sec.HasKey("PASSWD") { sec.Key("PASSWD").SetValue(sec.Key("PASSWORD").String()) } + if sec.HasKey("PASSWORD_URI") && !sec.HasKey("PASSWD_URI") { + sec.Key("PASSWD_URI").SetValue(sec.Key("PASSWORD_URI").String()) + } + sec.Key("PASSWD").SetValue(loadSecret(sec, "PASSWD_URI", "PASSWD")) // Set default values & validate sec.Key("NAME").MustString(AppName) diff --git a/modules/setting/mailer_test.go b/modules/setting/mailer_test.go index 4523cc91dd..47eaf3ffbb 100644 --- a/modules/setting/mailer_test.go +++ b/modules/setting/mailer_test.go @@ -4,6 +4,8 @@ package setting import ( + "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -52,6 +54,24 @@ func Test_loadMailerFrom(t *testing.T) { assert.Equal(t, "y0u'll n3v3r gUess th1S!!1", MailService.Passwd) }) + t.Run("Secrets", func(t *testing.T) { + uri := filepath.Join(t.TempDir(), "mailer_passwd") + + if err := os.WriteFile(uri, []byte("th1S gUess n3v3r y0u'll!!1"), 0o644); err != nil { + t.Fatal(err) + } + + cfg, _ := NewConfigProviderFromData("") + sec := cfg.Section("mailer") + sec.NewKey("ENABLED", "true") + sec.NewKey("PASSWD_URI", "file:"+uri) + + MailService.Passwd = "" + loadMailerFrom(cfg) + + assert.Equal(t, "th1S gUess n3v3r y0u'll!!1", MailService.Passwd) + }) + t.Run("sendmail argument sanitization", func(t *testing.T) { cfg, _ := NewConfigProviderFromData("") sec := cfg.Section("mailer") diff --git a/modules/setting/moderation.go b/modules/setting/moderation.go index 5f35a284d6..799efed761 100644 --- a/modules/setting/moderation.go +++ b/modules/setting/moderation.go @@ -3,13 +3,28 @@ package setting +import ( + "fmt" + "time" +) + // Moderation settings var Moderation = struct { - Enabled bool `ini:"ENABLED"` + Enabled bool `ini:"ENABLED"` + KeepResolvedReportsFor time.Duration `ini:"KEEP_RESOLVED_REPORTS_FOR"` }{ Enabled: false, } -func loadModerationFrom(rootCfg ConfigProvider) { - mustMapSetting(rootCfg, "moderation", &Moderation) +func loadModerationFrom(rootCfg ConfigProvider) error { + sec := rootCfg.Section("moderation") + err := sec.MapTo(&Moderation) + if err != nil { + return fmt.Errorf("failed to map Moderation settings: %v", err) + } + + // keep reports for one week by default. Since time.Duration stops at the unit of an hour + // we are using the value of 24 (hours) * 7 (days) which gives us the value of 168 + Moderation.KeepResolvedReportsFor = sec.Key("KEEP_RESOLVED_REPORTS_FOR").MustDuration(168 * time.Hour) + return nil } diff --git a/modules/setting/packages.go b/modules/setting/packages.go index 87e41fb5a0..ba4768c66b 100644 --- a/modules/setting/packages.go +++ b/modules/setting/packages.go @@ -5,12 +5,9 @@ package setting import ( "fmt" - "math" "net/url" "os" "path/filepath" - - "github.com/dustin/go-humanize" ) // Package registry settings @@ -110,17 +107,3 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) { Packages.LimitSizeAlt = mustBytes(sec, "LIMIT_SIZE_ALT") return nil } - -func mustBytes(section ConfigSection, key string) int64 { - const noLimit = "-1" - - value := section.Key(key).MustString(noLimit) - if value == noLimit { - return -1 - } - bytes, err := humanize.ParseBytes(value) - if err != nil || bytes > math.MaxInt64 { - return -1 - } - return int64(bytes) -} diff --git a/modules/setting/packages_test.go b/modules/setting/packages_test.go index 85a4656da0..a2cfdc6e35 100644 --- a/modules/setting/packages_test.go +++ b/modules/setting/packages_test.go @@ -10,27 +10,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestMustBytes(t *testing.T) { - test := func(value string) int64 { - cfg, err := NewConfigProviderFromData("[test]") - require.NoError(t, err) - sec := cfg.Section("test") - sec.NewKey("VALUE", value) - - return mustBytes(sec, "VALUE") - } - - assert.EqualValues(t, -1, test("")) - assert.EqualValues(t, -1, test("-1")) - assert.EqualValues(t, 0, test("0")) - assert.EqualValues(t, 1, test("1")) - assert.EqualValues(t, 10000, test("10000")) - assert.EqualValues(t, 1000000, test("1 mb")) - assert.EqualValues(t, 1048576, test("1mib")) - assert.EqualValues(t, 1782579, test("1.7mib")) - assert.EqualValues(t, -1, test("1 yib")) // too large -} - func Test_getStorageInheritNameSectionTypeForPackages(t *testing.T) { // packages storage inherits from storage if nothing configured iniStr := ` diff --git a/modules/setting/quota.go b/modules/setting/quota.go index 05e14baa9c..01b3f16bbd 100644 --- a/modules/setting/quota.go +++ b/modules/setting/quota.go @@ -23,4 +23,7 @@ var Quota = struct { func loadQuotaFrom(rootCfg ConfigProvider) { mustMapSetting(rootCfg, "quota", &Quota) + + sec := rootCfg.Section("quota.default") + Quota.Default.Total = mustBytes(sec, "TOTAL") } diff --git a/modules/setting/quota_test.go b/modules/setting/quota_test.go new file mode 100644 index 0000000000..baec9ccb4b --- /dev/null +++ b/modules/setting/quota_test.go @@ -0,0 +1,63 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "fmt" + "testing" + + "forgejo.org/modules/test" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestConfigQuotaDefaultTotal(t *testing.T) { + iniStr := `` + cfg, err := NewConfigProviderFromData(iniStr) + require.NoError(t, err) + loadQuotaFrom(cfg) + + assert.False(t, Quota.Enabled) + assert.EqualValues(t, -1, Quota.Default.Total) + + testSets := []struct { + iniTotal string + expectTotal int64 + }{ + {"0", 0}, + {"5000", 5000}, + {"12,345,678", 12_345_678}, + {"2k", 2000}, + {"2MiB", 2 * 1024 * 1024}, + {"3G", 3_000_000_000}, + {"3GiB", 3 * 1024 * 1024 * 1024}, + {"9EB", 9_000_000_000_000_000_000}, + {"42EB", -1}, + {"-1", -1}, + {"-42", -1}, + {"-1MiB", -1}, + {"hello", -1}, + {"unlimited", -1}, + } + + for _, testSet := range testSets { + t.Run(testSet.iniTotal, func(t *testing.T) { + defer test.MockVariableValue(&Quota.Default.Total, -404)() + + iniStr := fmt.Sprintf(` +[quota] +ENABLED = true +[quota.default] +TOTAL = %s`, testSet.iniTotal) + + cfg, err := NewConfigProviderFromData(iniStr) + require.NoError(t, err) + loadQuotaFrom(cfg) + + assert.True(t, Quota.Enabled) + assert.Equal(t, testSet.expectTotal, Quota.Default.Total) + }) + } +} diff --git a/modules/setting/security.go b/modules/setting/security.go index c38d8dae79..c591a7c90a 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -20,6 +20,7 @@ var ( SecretKey string InternalToken string // internal access token LogInRememberDays int + GlobalTwoFactorRequirement TwoFactorRequirementType CookieRememberName string ReverseProxyAuthUser string ReverseProxyAuthEmail string @@ -35,6 +36,7 @@ var ( PasswordHashAlgo string PasswordCheckPwn bool SuccessfulTokensCacheSize int + DisableQueryAuthToken bool CSRFCookieName = "_csrf" CSRFCookieHTTPOnly = true ) @@ -112,6 +114,8 @@ func loadSecurityFrom(rootCfg ConfigProvider) { } keying.Init([]byte(SecretKey)) + GlobalTwoFactorRequirement = NewTwoFactorRequirementType(sec.Key("GLOBAL_TWO_FACTOR_REQUIREMENT").String()) + CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").MustString("gitea_incredible") ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER") @@ -159,4 +163,50 @@ func loadSecurityFrom(rootCfg ConfigProvider) { PasswordComplexity = append(PasswordComplexity, name) } } + + sectionHasDisableQueryAuthToken := sec.HasKey("DISABLE_QUERY_AUTH_TOKEN") + + // TODO: default value should be true in future releases + DisableQueryAuthToken = sec.Key("DISABLE_QUERY_AUTH_TOKEN").MustBool(false) + + // warn if the setting is set to false explicitly + if sectionHasDisableQueryAuthToken && !DisableQueryAuthToken { + log.Warn("Enabling Query API Auth tokens is not recommended. DISABLE_QUERY_AUTH_TOKEN will be removed in Forgejo v13.0.0.") + } +} + +type TwoFactorRequirementType string + +// llu:TrKeysSuffix admin.config.global_2fa_requirement. +const ( + NoneTwoFactorRequirement TwoFactorRequirementType = "none" + AllTwoFactorRequirement TwoFactorRequirementType = "all" + AdminTwoFactorRequirement TwoFactorRequirementType = "admin" +) + +func NewTwoFactorRequirementType(twoFactorRequirement string) TwoFactorRequirementType { + switch twoFactorRequirement { + case AllTwoFactorRequirement.String(): + return AllTwoFactorRequirement + case AdminTwoFactorRequirement.String(): + return AdminTwoFactorRequirement + default: + return NoneTwoFactorRequirement + } +} + +func (r TwoFactorRequirementType) String() string { + return string(r) +} + +func (r TwoFactorRequirementType) IsNone() bool { + return r == NoneTwoFactorRequirement +} + +func (r TwoFactorRequirementType) IsAll() bool { + return r == AllTwoFactorRequirement +} + +func (r TwoFactorRequirementType) IsAdmin() bool { + return r == AdminTwoFactorRequirement } diff --git a/modules/setting/server.go b/modules/setting/server.go index bff51f787d..3ff91d2cde 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -16,6 +16,8 @@ import ( "forgejo.org/modules/json" "forgejo.org/modules/log" "forgejo.org/modules/util" + + "github.com/caddyserver/certmagic" ) // Scheme describes protocol types @@ -206,7 +208,7 @@ func loadServerFrom(rootCfg ConfigProvider) { EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false) } if EnableAcme { - AcmeURL = sec.Key("ACME_URL").MustString("") + AcmeURL = sec.Key("ACME_URL").MustString(certmagic.LetsEncryptProductionCA) AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("") if sec.HasKey("ACME_ACCEPTTOS") { diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 75c24580b2..9644d9b83b 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -140,6 +140,10 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error { if err := loadActionsFrom(cfg); err != nil { return err } + if err := loadModerationFrom(cfg); err != nil { + return err + } + loadUIFrom(cfg) loadAdminFrom(cfg) loadAPIFrom(cfg) @@ -221,7 +225,6 @@ func LoadSettings() { loadProjectFrom(CfgProvider) loadMimeTypeMapFrom(CfgProvider) loadF3From(CfgProvider) - loadModerationFrom(CfgProvider) } // LoadSettingsForInstall initializes the settings for install diff --git a/modules/setting/ui.go b/modules/setting/ui.go index 2e6a3df4c6..9dafe350eb 100644 --- a/modules/setting/ui.go +++ b/modules/setting/ui.go @@ -31,7 +31,7 @@ var UI = struct { Reactions []string ReactionsLookup container.Set[string] `ini:"-"` CustomEmojis []string - CustomEmojisMap map[string]string `ini:"-"` + CustomEmojisLookup container.Set[string] `ini:"-"` SearchRepoDescription bool OnlyShowRelevantRepos bool ExploreDefaultSort string `ini:"EXPLORE_PAGING_DEFAULT_SORT"` @@ -87,7 +87,6 @@ var UI = struct { 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`}, - CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:", "forgejo": ":forgejo:"}, ExploreDefaultSort: "recentupdate", PreferredTimestampTense: "mixed", @@ -163,8 +162,6 @@ func loadUIFrom(rootCfg ConfigProvider) { for _, reaction := range UI.Reactions { UI.ReactionsLookup.Add(reaction) } - UI.CustomEmojisMap = make(map[string]string) - for _, emoji := range UI.CustomEmojis { - UI.CustomEmojisMap[emoji] = ":" + emoji + ":" - } + UI.CustomEmojisLookup = make(container.Set[string]) + UI.CustomEmojisLookup.AddMultiple(UI.CustomEmojis...) } diff --git a/modules/ssh/init.go b/modules/ssh/init.go index 1ccd95b18b..09b96a7d8a 100644 --- a/modules/ssh/init.go +++ b/modules/ssh/init.go @@ -15,6 +15,8 @@ import ( "forgejo.org/modules/setting" ) +var logger = log.GetManager().GetLogger("ssh") + func Init() error { if setting.SSH.Disabled { builtinUnused() diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go index 19cac0b603..502fcd070f 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -61,10 +61,10 @@ func sessionHandler(session ssh.Session) { command := session.RawCommand() - log.Trace("SSH: Payload: %v", command) + logger.Trace("SSH: Payload: %v", command) args := []string{"--config=" + setting.CustomConf, "serv", "key-" + keyID} - log.Trace("SSH: Arguments: %v", args) + logger.Trace("SSH: Arguments: %v", args) ctx, cancel := context.WithCancel(session.Context()) defer cancel() @@ -87,21 +87,21 @@ func sessionHandler(session ssh.Session) { stdout, err := cmd.StdoutPipe() if err != nil { - log.Error("SSH: StdoutPipe: %v", err) + logger.Error("SSH: StdoutPipe: %v", err) return } defer stdout.Close() stderr, err := cmd.StderrPipe() if err != nil { - log.Error("SSH: StderrPipe: %v", err) + logger.Error("SSH: StderrPipe: %v", err) return } defer stderr.Close() stdin, err := cmd.StdinPipe() if err != nil { - log.Error("SSH: StdinPipe: %v", err) + logger.Error("SSH: StdinPipe: %v", err) return } defer stdin.Close() @@ -112,14 +112,14 @@ func sessionHandler(session ssh.Session) { wg.Add(2) if err = cmd.Start(); err != nil { - log.Error("SSH: Start: %v", err) + logger.Error("SSH: Start: %v", err) return } go func() { defer stdin.Close() if _, err := io.Copy(stdin, session); err != nil { - log.Error("Failed to write session to stdin. %s", err) + logger.Error("Failed to write session to stdin. %s", err) } }() @@ -127,7 +127,7 @@ func sessionHandler(session ssh.Session) { defer wg.Done() defer stdout.Close() if _, err := io.Copy(session, stdout); err != nil { - log.Error("Failed to write stdout to session. %s", err) + logger.Error("Failed to write stdout to session. %s", err) } }() @@ -135,7 +135,7 @@ func sessionHandler(session ssh.Session) { defer wg.Done() defer stderr.Close() if _, err := io.Copy(session.Stderr(), stderr); err != nil { - log.Error("Failed to write stderr to session. %s", err) + logger.Error("Failed to write stderr to session. %s", err) } }() @@ -149,41 +149,41 @@ func sessionHandler(session ssh.Session) { // Cannot use errors.Is here because ExitError doesn't implement Is // Thus errors.Is will do equality test NOT type comparison if _, ok := err.(*exec.ExitError); !ok { - log.Error("SSH: Wait: %v", err) + logger.Error("SSH: Wait: %v", err) } } if err := session.Exit(getExitStatusFromError(err)); err != nil && !errors.Is(err, io.EOF) { - log.Error("Session failed to exit. %s", err) + logger.Error("Session failed to exit. %s", err) } } func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { - if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary - log.Debug("Handle Public Key: Fingerprint: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr()) + if logger.LevelEnabled(log.DEBUG) { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary + logger.Debug("Handle Public Key: Fingerprint: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr()) } if ctx.User() != setting.SSH.BuiltinServerUser { - log.Warn("Invalid SSH username %s - must use %s for all git operations via ssh", ctx.User(), setting.SSH.BuiltinServerUser) - log.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) + logger.Warn("Invalid SSH username %s - must use %s for all git operations via ssh", ctx.User(), setting.SSH.BuiltinServerUser) + logger.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) return false } // check if we have a certificate if cert, ok := key.(*gossh.Certificate); ok { - if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary - log.Debug("Handle Certificate: %s Fingerprint: %s is a certificate", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) + if logger.LevelEnabled(log.DEBUG) { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary + logger.Debug("Handle Certificate: %s Fingerprint: %s is a certificate", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) } if len(setting.SSH.TrustedUserCAKeys) == 0 { - log.Warn("Certificate Rejected: No trusted certificate authorities for this server") - log.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) + logger.Warn("Certificate Rejected: No trusted certificate authorities for this server") + logger.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) return false } if cert.CertType != gossh.UserCert { - log.Warn("Certificate Rejected: Not a user certificate") - log.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) + logger.Warn("Certificate Rejected: Not a user certificate") + logger.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) return false } @@ -193,10 +193,10 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { pkey, err := asymkey_model.SearchPublicKeyByContentExact(ctx, principal) if err != nil { if asymkey_model.IsErrKeyNotExist(err) { - log.Debug("Principal Rejected: %s Unknown Principal: %s", ctx.RemoteAddr(), principal) + logger.Debug("Principal Rejected: %s Unknown Principal: %s", ctx.RemoteAddr(), principal) continue principalLoop } - log.Error("SearchPublicKeyByContentExact: %v", err) + logger.Error("SearchPublicKeyByContentExact: %v", err) return false } @@ -215,8 +215,8 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { // check the CA of the cert if !c.IsUserAuthority(cert.SignatureKey) { - if log.IsDebug() { - log.Debug("Principal Rejected: %s Untrusted Authority Signature Fingerprint %s for Principal: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(cert.SignatureKey), principal) + if logger.LevelEnabled(log.DEBUG) { + logger.Debug("Principal Rejected: %s Untrusted Authority Signature Fingerprint %s for Principal: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(cert.SignatureKey), principal) } continue principalLoop } @@ -224,14 +224,14 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { // validate the cert for this principal if err := c.CheckCert(principal, cert); err != nil { // User is presenting an invalid certificate - STOP any further processing - log.Error("Invalid Certificate KeyID %s with Signature Fingerprint %s presented for Principal: %s from %s", cert.KeyId, gossh.FingerprintSHA256(cert.SignatureKey), principal, ctx.RemoteAddr()) - log.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) + logger.Error("Invalid Certificate KeyID %s with Signature Fingerprint %s presented for Principal: %s from %s", cert.KeyId, gossh.FingerprintSHA256(cert.SignatureKey), principal, ctx.RemoteAddr()) + logger.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) return false } - if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary - log.Debug("Successfully authenticated: %s Certificate Fingerprint: %s Principal: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key), principal) + if logger.LevelEnabled(log.DEBUG) { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary + logger.Debug("Successfully authenticated: %s Certificate Fingerprint: %s Principal: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key), principal) } if ctx.Permissions().Extensions == nil { ctx.Permissions().Extensions = map[string]string{} @@ -241,28 +241,28 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { return true } - log.Warn("From %s Fingerprint: %s is a certificate, but no valid principals found", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) - log.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) + logger.Warn("From %s Fingerprint: %s is a certificate, but no valid principals found", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) + logger.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) return false } - if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary - log.Debug("Handle Public Key: %s Fingerprint: %s is not a certificate", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) + if logger.LevelEnabled(log.DEBUG) { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary + logger.Debug("Handle Public Key: %s Fingerprint: %s is not a certificate", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) } pkey, err := asymkey_model.SearchPublicKeyByContent(ctx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key)))) if err != nil { if asymkey_model.IsErrKeyNotExist(err) { - log.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr()) - log.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) + logger.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr()) + logger.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) return false } - log.Error("SearchPublicKeyByContent: %v", err) + logger.Error("SearchPublicKeyByContent: %v", err) return false } - if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary - log.Debug("Successfully authenticated: %s Public Key Fingerprint: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) + if logger.LevelEnabled(log.DEBUG) { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary + logger.Debug("Successfully authenticated: %s Public Key Fingerprint: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) } if ctx.Permissions().Extensions == nil { ctx.Permissions().Extensions = map[string]string{} @@ -276,9 +276,9 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { // - this mainly exists to give a nice function name in logging func sshConnectionFailed(conn net.Conn, err error) { // Log the underlying error with a specific message - log.Warn("Failed connection from %s with error: %v", conn.RemoteAddr(), err) + logger.Warn("Failed connection from %s with error: %v", conn.RemoteAddr(), err) // Log with the standard failed authentication from message for simpler fail2ban configuration - log.Warn("Failed authentication attempt from %s", conn.RemoteAddr()) + logger.Warn("Failed authentication attempt from %s", conn.RemoteAddr()) } // Listen starts a SSH server listens on given port. @@ -317,22 +317,22 @@ func Listen(host string, port int, ciphers, keyExchanges, macs []string) { filePath := filepath.Dir(setting.SSH.ServerHostKeys[0]) if err := os.MkdirAll(filePath, os.ModePerm); err != nil { - log.Error("Failed to create dir %s: %v", filePath, err) + logger.Error("Failed to create dir %s: %v", filePath, err) } err := GenKeyPair(setting.SSH.ServerHostKeys[0]) if err != nil { log.Fatal("Failed to generate private key: %v", err) } - log.Trace("New private key is generated: %s", setting.SSH.ServerHostKeys[0]) + logger.Trace("New private key is generated: %s", setting.SSH.ServerHostKeys[0]) keys = append(keys, setting.SSH.ServerHostKeys[0]) } for _, key := range keys { - log.Info("Adding SSH host key: %s", key) + logger.Info("Adding SSH host key: %s", key) err := srv.SetOption(ssh.HostKeyFile(key)) if err != nil { - log.Error("Failed to set Host Key. %s", err) + logger.Error("Failed to set Host Key. %s", err) } } @@ -359,7 +359,7 @@ func GenKeyPair(keyPath string) error { } defer func() { if err = f.Close(); err != nil { - log.Error("Close: %v", err) + logger.Error("Close: %v", err) } }() @@ -380,7 +380,7 @@ func GenKeyPair(keyPath string) error { } defer func() { if err = p.Close(); err != nil { - log.Error("Close: %v", err) + logger.Error("Close: %v", err) } }() _, err = p.Write(public) diff --git a/modules/ssh/ssh_graceful.go b/modules/ssh/ssh_graceful.go index 825313ab1c..98ddc18ae7 100644 --- a/modules/ssh/ssh_graceful.go +++ b/modules/ssh/ssh_graceful.go @@ -20,12 +20,12 @@ func listen(server *ssh.Server) { if err != nil { select { case <-graceful.GetManager().IsShutdown(): - log.Critical("Failed to start SSH server: %v", err) + logger.Critical("Failed to start SSH server: %v", err) default: log.Fatal("Failed to start SSH server: %v", err) } } - log.Info("SSH Listener: %s Closed", server.Addr) + logger.Info("SSH Listener: %s Closed", server.Addr) } // builtinUnused informs our cleanup routine that we will not be using a ssh port diff --git a/modules/storage/minio.go b/modules/storage/minio.go index bf51a1642a..8d4f9d6627 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -76,8 +76,13 @@ var getBucketVersioning = func(ctx context.Context, minioClient *minio.Client, b return err } +var initializationTimeout = 30 * time.Second + // NewMinioStorage returns a minio storage func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage, error) { + initCtx, cancel := context.WithTimeout(ctx, initializationTimeout) + defer cancel() + config := cfg.MinioConfig if config.ChecksumAlgorithm != "" && config.ChecksumAlgorithm != "default" && config.ChecksumAlgorithm != "md5" { return nil, fmt.Errorf("invalid minio checksum algorithm: %s", config.ChecksumAlgorithm) @@ -112,7 +117,7 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage, // Otherwise even if the request itself fails (403, 404, etc), the code should still continue because the parameters seem "good" enough. // Keep in mind that GetBucketVersioning requires "owner" to really succeed, so it can't be used to check the existence. // Not using "BucketExists (HeadBucket)" because it doesn't include detailed failure reasons. - err = getBucketVersioning(ctx, minioClient, config.Bucket) + err = getBucketVersioning(initCtx, minioClient, config.Bucket) if err != nil { errResp, ok := err.(minio.ErrorResponse) if !ok { @@ -125,13 +130,13 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage, } // Check to see if we already own this bucket - exists, err := minioClient.BucketExists(ctx, config.Bucket) + exists, err := minioClient.BucketExists(initCtx, config.Bucket) if err != nil { return nil, convertMinioErr(err) } if !exists { - if err := minioClient.MakeBucket(ctx, config.Bucket, minio.MakeBucketOptions{ + if err := minioClient.MakeBucket(initCtx, config.Bucket, minio.MakeBucketOptions{ Region: config.Location, }); err != nil { return nil, convertMinioErr(err) diff --git a/modules/storage/minio_test.go b/modules/storage/minio_test.go index ec1b2fc77a..18fe91edfb 100644 --- a/modules/storage/minio_test.go +++ b/modules/storage/minio_test.go @@ -9,8 +9,10 @@ import ( "net/http/httptest" "os" "testing" + "time" "forgejo.org/modules/setting" + "forgejo.org/modules/test" "github.com/minio/minio-go/v7" "github.com/stretchr/testify/assert" @@ -217,3 +219,41 @@ func TestMinioCredentials(t *testing.T) { }) }) } + +func TestNewMinioStorageInitializationTimeout(t *testing.T) { + defer test.MockVariableValue(&getBucketVersioning, func(ctx context.Context, minioClient *minio.Client, bucket string) error { + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(1 * time.Millisecond): + return minio.ErrorResponse{ + StatusCode: http.StatusBadRequest, + Code: "TestError", + Message: "Mocked error for testing", + } + } + })() + + settings := &setting.Storage{ + MinioConfig: setting.MinioStorageConfig{ + Endpoint: "localhost", + AccessKeyID: "123456", + SecretAccessKey: "12345678", + Bucket: "bucket", + Location: "us-east-1", + }, + } + + // Verify that we reach `getBucketVersioning` and return the error from our mock. + storage, err := NewMinioStorage(t.Context(), settings) + require.ErrorContains(t, err, "Mocked error for testing") + assert.Nil(t, storage) + + defer test.MockVariableValue(&initializationTimeout, 1*time.Nanosecond)() + + // Now that the timeout is super low, verify that we get a context deadline exceeded error from our mock. + storage, err = NewMinioStorage(t.Context(), settings) + require.Error(t, err) + require.ErrorIs(t, err, context.DeadlineExceeded, "err must be a context deadline exceeded error, but was %v", err) + assert.Nil(t, storage) +} diff --git a/modules/storage/storage_test.go b/modules/storage/storage_test.go index af3dd9520e..76589d941a 100644 --- a/modules/storage/storage_test.go +++ b/modules/storage/storage_test.go @@ -5,6 +5,7 @@ package storage import ( "bytes" + "io" "testing" "forgejo.org/modules/setting" @@ -13,22 +14,39 @@ import ( "github.com/stretchr/testify/require" ) +type spyCloser struct { + io.Reader + closed int +} + +func (s *spyCloser) Close() error { + s.closed++ + return nil +} + +var _ io.ReadCloser = &spyCloser{} + func testStorageIterator(t *testing.T, typStr Type, cfg *setting.Storage) { l, err := NewStorage(typStr, cfg) require.NoError(t, err) - testFiles := [][]string{ - {"a/1.txt", "a1"}, - {"/a/1.txt", "aa1"}, // same as above, but with leading slash that will be trim - {"ab/1.txt", "ab1"}, - {"b/1.txt", "b1"}, - {"b/2.txt", "b2"}, - {"b/3.txt", "b3"}, - {"b/x 4.txt", "bx4"}, + testFiles := []struct { + path, content string + size int64 + }{ + {"a/1.txt", "a1", -1}, + {"/a/1.txt", "aa1", -1}, // same as above, but with leading slash that will be trim + {"ab/1.txt", "ab1", 3}, + {"b/1.txt", "b1", 2}, // minio closes when the size is set + {"b/2.txt", "b2", -1}, + {"b/3.txt", "b3", -1}, + {"b/x 4.txt", "bx4", -1}, } for _, f := range testFiles { - _, err = l.Save(f[0], bytes.NewBufferString(f[1]), -1) + sc := &spyCloser{bytes.NewBufferString(f.content), 0} + _, err = l.Save(f.path, sc, f.size) require.NoError(t, err) + assert.Equal(t, 0, sc.closed) } expectedList := map[string][]string{ diff --git a/modules/structs/activitypub.go b/modules/structs/activitypub.go index 117eb0bed2..0cc257ff95 100644 --- a/modules/structs/activitypub.go +++ b/modules/structs/activitypub.go @@ -1,4 +1,5 @@ // Copyright 2022 The Gitea Authors. All rights reserved. +// Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package structs @@ -7,3 +8,15 @@ package structs type ActivityPub struct { Context string `json:"@context"` } + +type APRemoteFollowOption struct { + Target string `json:"target"` +} + +type APPersonFollowItem struct { + ActorID string `json:"actor_id"` + Note string `json:"note"` + + OriginalURL string `json:"original_url"` + OriginalItem string `json:"original_item"` +} diff --git a/modules/structs/issue.go b/modules/structs/issue.go index a67bdcf50e..7b7397dc4b 100644 --- a/modules/structs/issue.go +++ b/modules/structs/issue.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) // StateType issue state type diff --git a/modules/structs/issue_test.go b/modules/structs/issue_test.go index 2003e22e0a..7adb843206 100644 --- a/modules/structs/issue_test.go +++ b/modules/structs/issue_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) func TestIssueTemplate_Type(t *testing.T) { diff --git a/modules/structs/mirror.go b/modules/structs/mirror.go index 1b6566803a..4909ae20ca 100644 --- a/modules/structs/mirror.go +++ b/modules/structs/mirror.go @@ -13,6 +13,7 @@ type CreatePushMirrorOption struct { Interval string `json:"interval"` SyncOnCommit bool `json:"sync_on_commit"` UseSSH bool `json:"use_ssh"` + BranchFilter string `json:"branch_filter"` } // PushMirror represents information of a push mirror @@ -29,4 +30,6 @@ type PushMirror struct { Interval string `json:"interval"` SyncOnCommit bool `json:"sync_on_commit"` PublicKey string `json:"public_key"` + + BranchFilter string `json:"branch_filter"` } diff --git a/modules/structs/repo.go b/modules/structs/repo.go index c9cd729cf3..01cbf26f61 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -327,6 +327,7 @@ const ( GitBucketService // 7 gitbucket service CodebaseService // 8 codebase service ForgejoService // 9 forgejo service + PagureService // 10 pagure service ) // Name represents the service type's name @@ -354,6 +355,8 @@ func (gt GitServiceType) Title() string { return "Codebase" case ForgejoService: return "Forgejo" + case PagureService: + return "Pagure" case PlainGitService: return "Git" } @@ -412,6 +415,7 @@ var SupportedFullGitService = []GitServiceType{ OneDevService, GitBucketService, CodebaseService, + PagureService, } // RepoTransfer represents a pending repo transfer diff --git a/modules/templates/context.go b/modules/templates/context.go new file mode 100644 index 0000000000..d2b896391b --- /dev/null +++ b/modules/templates/context.go @@ -0,0 +1,23 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package templates + +import ( + "context" + + "forgejo.org/modules/translation" +) + +type Context struct { + context.Context + Locale translation.Locale + AvatarUtils *AvatarUtils + Data map[string]any +} + +var _ context.Context = Context{} + +func NewContext(ctx context.Context) *Context { + return &Context{Context: ctx} +} diff --git a/modules/templates/context_test.go b/modules/templates/context_test.go new file mode 100644 index 0000000000..d854fbf0ff --- /dev/null +++ b/modules/templates/context_test.go @@ -0,0 +1,18 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later +package templates + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestContext(t *testing.T) { + type ctxKey struct{} + + // Test that the original context is used for its context functions. + ctx := NewContext(context.WithValue(t.Context(), ctxKey{}, "there")) + assert.Equal(t, "there", ctx.Value(ctxKey{})) +} diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 02b175e6f6..848d4b4ad4 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -6,6 +6,8 @@ package templates import ( + "bytes" + "context" "fmt" "html" "html/template" @@ -29,6 +31,23 @@ func NewFuncMap() template.FuncMap { return map[string]any{ "ctx": func() any { return nil }, // template context function + "ExecuteTemplate": func(ctx context.Context, tmplName string, args any) template.HTML { + h := HTMLRenderer() + tmpl, err := h.TemplateLookup(tmplName, ctx) + if err != nil { + panic("Template not found: " + tmplName) + } + + buf := bytes.Buffer{} + if err := tmpl.Execute(&buf, args); err != nil { + panic("Error while executing template") + } + + // We can safely return this as `template.HTML` as html/template will + // already make sure it's sanitized. + return template.HTML(buf.String()) + }, + "DumpVar": dumpVar, // ----------------------------------------------------------------- @@ -128,8 +147,8 @@ func NewFuncMap() template.FuncMap { "AllowedReactions": func() []string { return setting.UI.Reactions }, - "CustomEmojis": func() map[string]string { - return setting.UI.CustomEmojisMap + "CustomEmojis": func() []string { + return setting.UI.CustomEmojis }, "MetaAuthor": func() string { return setting.UI.Meta.Author @@ -188,6 +207,7 @@ func NewFuncMap() template.FuncMap { "RenderMarkdownToHtml": RenderMarkdownToHtml, "RenderLabel": RenderLabel, "RenderLabels": RenderLabels, + "RenderUser": RenderUser, "RenderReviewRequest": RenderReviewRequest, // ----------------------------------------------------------------- diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index a4d7a82eea..bec8d5f5e3 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -7,6 +7,7 @@ import ( "context" "encoding/hex" "fmt" + "html" "html/template" "math" "net/url" @@ -15,18 +16,18 @@ import ( "unicode" issues_model "forgejo.org/models/issues" + user_model "forgejo.org/models/user" "forgejo.org/modules/emoji" "forgejo.org/modules/log" "forgejo.org/modules/markup" "forgejo.org/modules/markup/markdown" "forgejo.org/modules/setting" - "forgejo.org/modules/translation" "forgejo.org/modules/util" ) // RenderCommitMessage renders commit message with XSS-safe and special links. func RenderCommitMessage(ctx context.Context, msg string, metas map[string]string) template.HTML { - cleanMsg := template.HTMLEscapeString(msg) + cleanMsg := html.EscapeString(msg) // we can safely assume that it will not return any error, since there // shouldn't be any special HTML. fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ @@ -63,7 +64,7 @@ func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlDefault string, Ctx: ctx, DefaultLink: urlDefault, Metas: metas, - }, template.HTMLEscapeString(msgLine)) + }, html.EscapeString(msgLine)) if err != nil { log.Error("RenderCommitMessageSubject: %v", err) return template.HTML("") @@ -88,7 +89,7 @@ func RenderCommitBody(ctx context.Context, msg string, metas map[string]string) renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ Ctx: ctx, Metas: metas, - }, template.HTMLEscapeString(msgLine)) + }, html.EscapeString(msgLine)) if err != nil { log.Error("RenderCommitMessage: %v", err) return "" @@ -122,7 +123,7 @@ func RenderIssueTitle(ctx context.Context, text string, metas map[string]string) renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{ Ctx: ctx, Metas: metas, - }, template.HTMLEscapeString(text)) + }, html.EscapeString(text)) if err != nil { log.Error("RenderIssueTitle: %v", err) return template.HTML("") @@ -132,7 +133,7 @@ func RenderIssueTitle(ctx context.Context, text string, metas map[string]string) // RenderRefIssueTitle renders referenced issue/pull title with defined post processors func RenderRefIssueTitle(ctx context.Context, text string) template.HTML { - renderedText, err := markup.RenderRefIssueTitle(&markup.RenderContext{Ctx: ctx}, template.HTMLEscapeString(text)) + renderedText, err := markup.RenderRefIssueTitle(&markup.RenderContext{Ctx: ctx}, html.EscapeString(text)) if err != nil { log.Error("RenderRefIssueTitle: %v", err) return "" @@ -143,18 +144,18 @@ func RenderRefIssueTitle(ctx context.Context, text string) template.HTML { // RenderLabel renders a label // locale is needed due to an import cycle with our context providing the `Tr` function -func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML { +func RenderLabel(ctx *Context, label *issues_model.Label) template.HTML { var ( archivedCSSClass string textColor = util.ContrastColor(label.Color) labelScope = label.ExclusiveScope() ) - description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description)) + description := emoji.ReplaceAliases(html.EscapeString(label.Description)) if label.IsArchived() { archivedCSSClass = "archived-label" - description = locale.TrString("repo.issues.archived_label_description", description) + description = ctx.Locale.TrString("repo.issues.archived_label_description", description) } if labelScope == "" { @@ -212,7 +213,7 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m // RenderEmoji renders html text with emoji post processors func RenderEmoji(ctx context.Context, text string) template.HTML { renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ctx}, - template.HTMLEscapeString(text)) + html.EscapeString(text)) if err != nil { log.Error("RenderEmoji: %v", err) return template.HTML("") @@ -244,7 +245,7 @@ func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //n return output } -func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string, isPull bool) template.HTML { +func RenderLabels(ctx *Context, labels []*issues_model.Label, repoLink string, isPull bool) template.HTML { htmlCode := `` for _, label := range labels { // Protect against nil value in labels - shouldn't happen but would cause a panic if so @@ -257,16 +258,34 @@ func RenderLabels(ctx context.Context, locale translation.Locale, labels []*issu issuesOrPull = "pulls" } htmlCode += fmt.Sprintf("%s ", - repoLink, issuesOrPull, label.ID, RenderLabel(ctx, locale, label)) + repoLink, issuesOrPull, label.ID, RenderLabel(ctx, label)) } htmlCode += "" return template.HTML(htmlCode) } -func RenderReviewRequest(users []issues_model.RequestReviewTarget) template.HTML { +func RenderUser(ctx context.Context, user user_model.User) template.HTML { + if user.ID > 0 { + return template.HTML(fmt.Sprintf( + "%s", + user.HomeLink(), html.EscapeString(user.GetDisplayName()))) + } + return template.HTML(fmt.Sprintf("%s", + html.EscapeString(user.GetDisplayName()))) +} + +func RenderReviewRequest(ctx context.Context, users []issues_model.RequestReviewTarget) template.HTML { usernames := make([]string, 0, len(users)) for _, user := range users { - usernames = append(usernames, template.HTMLEscapeString(user.Name())) + if user.ID() > 0 { + usernames = append(usernames, fmt.Sprintf( + "%s", + user.Link(ctx), html.EscapeString(user.Name()))) + } else { + usernames = append(usernames, fmt.Sprintf( + "%s", + html.EscapeString(user.Name()))) + } } htmlCode := `` diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index 62e063213c..3cfd572491 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -10,7 +10,11 @@ import ( "forgejo.org/models/db" issues_model "forgejo.org/models/issues" + org_model "forgejo.org/models/organization" "forgejo.org/models/unittest" + user_model "forgejo.org/models/user" + "forgejo.org/modules/setting" + "forgejo.org/modules/test" "forgejo.org/modules/translation" "github.com/stretchr/testify/assert" @@ -215,9 +219,70 @@ func TestRenderLabels(t *testing.T) { tr := &translation.MockLocale{} label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) + labelScoped := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 7}) + labelMalicious := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 11}) + labelArchived := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 12}) - assert.Contains(t, RenderLabels(db.DefaultContext, tr, []*issues_model.Label{label}, "user2/repo1", false), - "user2/repo1/issues?labels=1") - assert.Contains(t, RenderLabels(db.DefaultContext, tr, []*issues_model.Label{label}, "user2/repo1", true), - "user2/repo1/pulls?labels=1") + ctx := NewContext(t.Context()) + ctx.Locale = tr + + rendered := RenderLabels(ctx, []*issues_model.Label{label}, "user2/repo1", false) + assert.Contains(t, rendered, "user2/repo1/issues?labels=1") + assert.Contains(t, rendered, ">label1<") + assert.Contains(t, rendered, "title='First label'") + rendered = RenderLabels(ctx, []*issues_model.Label{label}, "user2/repo1", true) + assert.Contains(t, rendered, "user2/repo1/pulls?labels=1") + assert.Contains(t, rendered, ">label1<") + rendered = RenderLabels(ctx, []*issues_model.Label{labelScoped}, "user2/repo1", false) + assert.Contains(t, rendered, "user2/repo1/issues?labels=7") + assert.Contains(t, rendered, ">scope<") + assert.Contains(t, rendered, ">label1<") + rendered = RenderLabels(ctx, []*issues_model.Label{labelMalicious}, "user2/repo1", false) + assert.Contains(t, rendered, "user2/repo1/issues?labels=11") + assert.Contains(t, rendered, "> <script>malicious</script> <") + assert.Contains(t, rendered, ">'?&<") + assert.Contains(t, rendered, "title='Malicious label ' <script>malicious</script>'") + rendered = RenderLabels(ctx, []*issues_model.Label{labelArchived}, "user2/repo1", false) + assert.Contains(t, rendered, "user2/repo1/issues?labels=12") + assert.Contains(t, rendered, ">archived label<><") + assert.Contains(t, rendered, "title='repo.issues.archived_label_description'") +} + +func TestRenderUser(t *testing.T) { + unittest.PrepareTestEnv(t) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + ghost := user_model.NewGhostUser() + + assert.Contains(t, RenderUser(db.DefaultContext, *user), + "user2") + assert.Contains(t, RenderUser(db.DefaultContext, *org), + "org3") + assert.Contains(t, RenderUser(db.DefaultContext, *ghost), + "Ghost") + + defer test.MockVariableValue(&setting.UI.DefaultShowFullName, true)() + assert.Contains(t, RenderUser(db.DefaultContext, *user), + "< U<se>r Tw<o > ><") + assert.Contains(t, RenderUser(db.DefaultContext, *org), + "<<<< >> >> > >> > >>> >>") + assert.Contains(t, RenderUser(db.DefaultContext, *ghost), + "Ghost") +} + +func TestRenderReviewRequest(t *testing.T) { + unittest.PrepareTestEnv(t) + + target1 := issues_model.RequestReviewTarget{User: &user_model.User{ID: 1, Name: "user1", FullName: "User "}} + target2 := issues_model.RequestReviewTarget{Team: &org_model.Team{ID: 2, Name: "Team2", OrgID: 3}} + target3 := issues_model.RequestReviewTarget{Team: org_model.NewGhostTeam()} + assert.Contains(t, RenderReviewRequest(db.DefaultContext, []issues_model.RequestReviewTarget{target1, target2, target3}), + "user1, "+ + "Team2, "+ + "Ghost team") + + defer test.MockVariableValue(&setting.UI.DefaultShowFullName, true)() + assert.Contains(t, RenderReviewRequest(db.DefaultContext, []issues_model.RequestReviewTarget{target1}), + "User <One>") } diff --git a/modules/test/distant_federation_server_mock.go b/modules/test/distant_federation_server_mock.go index 9bd908e2b9..ea8a69e9b4 100644 --- a/modules/test/distant_federation_server_mock.go +++ b/modules/test/distant_federation_server_mock.go @@ -10,56 +10,79 @@ import ( "net/http/httptest" "strings" "testing" + + "forgejo.org/modules/util" ) type FederationServerMockPerson struct { - ID int64 - Name string - PubKey string + ID int64 + Name string + PubKey string + PrivKey string } type FederationServerMockRepository struct { ID int64 } +type ApActorMock struct { + PrivKey string + PubKey string +} type FederationServerMock struct { + ApActor ApActorMock Persons []FederationServerMockPerson Repositories []FederationServerMockRepository LastPost string } func NewFederationServerMockPerson(id int64, name string) FederationServerMockPerson { + priv, pub, _ := util.GenerateKeyPair(3072) return FederationServerMockPerson{ - ID: id, - Name: name, - PubKey: `"-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA18H5s7N6ItZUAh9tneII\nIuZdTTa3cZlLa/9ejWAHTkcp3WLW+/zbsumlMrWYfBy2/yTm56qasWt38iY4D6ul\n` + - `CPiwhAqX3REvVq8tM79a2CEqZn9ka6vuXoDgBg/sBf/BUWqf7orkjUXwk/U0Egjf\nk5jcurF4vqf1u+rlAHH37dvSBaDjNj6Qnj4OP12bjfaY/yvs7+jue/eNXFHjzN4E\n` + - `T2H4B/yeKTJ4UuAwTlLaNbZJul2baLlHelJPAsxiYaziVuV5P+IGWckY6RSerRaZ\nAkc4mmGGtjAyfN9aewe+lNVfwS7ElFx546PlLgdQgjmeSwLX8FWxbPE5A/PmaXCs\n` + - `nx+nou+3dD7NluULLtdd7K+2x02trObKXCAzmi5/Dc+yKTzpFqEz+hLNCz7TImP/\ncK//NV9Q+X67J9O27baH9R9ZF4zMw8rv2Pg0WLSw1z7lLXwlgIsDapeMCsrxkVO4\n` + - `LXX5AQ1xQNtlssnVoUBqBrvZsX2jUUKUocvZqMGuE4hfAgMBAAE=\n-----END PUBLIC KEY-----\n"`, + ID: id, + Name: name, + PubKey: pub, + PrivKey: priv, } } +func (p *FederationServerMockPerson) KeyID(host string) string { + return fmt.Sprintf("%[1]v/api/v1/activitypub/user-id/%[2]v#main-key", host, p.ID) +} + func NewFederationServerMockRepository(id int64) FederationServerMockRepository { return FederationServerMockRepository{ ID: id, } } +func NewApActorMock() ApActorMock { + priv, pub, _ := util.GenerateKeyPair(1024) + return ApActorMock{ + PrivKey: priv, + PubKey: pub, + } +} + +func (u *ApActorMock) KeyID(host string) string { + return fmt.Sprintf("%[1]v/api/v1/activitypub/actor#main-key", host) +} + func (p FederationServerMockPerson) marshal(host string) string { return fmt.Sprintf(`{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],`+ - `"id":"http://%[1]v/api/activitypub/user-id/%[2]v",`+ + `"id":"http://%[1]v/api/v1/activitypub/user-id/%[2]v",`+ `"type":"Person",`+ `"icon":{"type":"Image","mediaType":"image/png","url":"http://%[1]v/avatars/1bb05d9a5f6675ed0272af9ea193063c"},`+ `"url":"http://%[1]v/%[2]v",`+ - `"inbox":"http://%[1]v/api/activitypub/user-id/%[2]v/inbox",`+ - `"outbox":"http://%[1]v/api/activitypub/user-id/%[2]v/outbox",`+ + `"inbox":"http://%[1]v/api/v1/activitypub/user-id/%[2]v/inbox",`+ + `"outbox":"http://%[1]v/api/v1/activitypub/user-id/%[2]v/outbox",`+ `"preferredUsername":"%[3]v",`+ - `"publicKey":{"id":"http://%[1]v/api/activitypub/user-id/%[2]v#main-key",`+ - `"owner":"http://%[1]v/api/activitypub/user-id/%[2]v",`+ - `"publicKeyPem":%[4]v}}`, host, p.ID, p.Name, p.PubKey) + `"publicKey":{"id":"http://%[1]v/api/v1/activitypub/user-id/%[2]v#main-key",`+ + `"owner":"http://%[1]v/api/v1/activitypub/user-id/%[2]v",`+ + `"publicKeyPem":%[4]q}}`, host, p.ID, p.Name, p.PubKey) } func NewFederationServerMock() *FederationServerMock { return &FederationServerMock{ + ApActor: NewApActorMock(), Persons: []FederationServerMockPerson{ NewFederationServerMockPerson(15, "stargoose1"), NewFederationServerMockPerson(30, "stargoose2"), @@ -71,8 +94,18 @@ func NewFederationServerMock() *FederationServerMock { } } +func (mock *FederationServerMock) recordLastPost(t *testing.T, req *http.Request) { + buf := new(strings.Builder) + _, err := io.Copy(buf, req.Body) + if err != nil { + t.Errorf("Error reading body: %q", err) + } + mock.LastPost = strings.ReplaceAll(buf.String(), req.Host, "DISTANT_FEDERATION_HOST") +} + func (mock *FederationServerMock) DistantServer(t *testing.T) *httptest.Server { federatedRoutes := http.NewServeMux() + federatedRoutes.HandleFunc("/.well-known/nodeinfo", func(res http.ResponseWriter, req *http.Request) { // curl -H "Accept: application/json" https://federated-repo.prod.meissa.de/.well-known/nodeinfo @@ -87,30 +120,28 @@ func (mock *FederationServerMock) DistantServer(t *testing.T) *httptest.Server { `"protocols":["activitypub"],"services":{"inbound":[],"outbound":["rss2.0"]},`+ `"openRegistrations":true,"usage":{"users":{"total":14,"activeHalfyear":2}},"metadata":{}}`) }) + for _, person := range mock.Persons { federatedRoutes.HandleFunc(fmt.Sprintf("/api/v1/activitypub/user-id/%v", person.ID), func(res http.ResponseWriter, req *http.Request) { // curl -H "Accept: application/json" https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/2 fmt.Fprint(res, person.marshal(req.Host)) }) - } - for _, repository := range mock.Repositories { - federatedRoutes.HandleFunc(fmt.Sprintf("/api/v1/activitypub/repository-id/%v/inbox", repository.ID), + federatedRoutes.HandleFunc(fmt.Sprintf("POST /api/v1/activitypub/user-id/%v/inbox", person.ID), func(res http.ResponseWriter, req *http.Request) { - if req.Method != "POST" { - t.Errorf("POST expected at: %q", req.URL.EscapedPath()) - } - buf := new(strings.Builder) - _, err := io.Copy(buf, req.Body) - if err != nil { - t.Errorf("Error reading body: %q", err) - } - mock.LastPost = buf.String() + mock.recordLastPost(t, req) + }) + } + + for _, repository := range mock.Repositories { + federatedRoutes.HandleFunc(fmt.Sprintf("POST /api/v1/activitypub/repository-id/%v/inbox", repository.ID), + func(res http.ResponseWriter, req *http.Request) { + mock.recordLastPost(t, req) }) } federatedRoutes.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { - t.Errorf("Unhandled request: %q", req.URL.EscapedPath()) + t.Errorf("Unhandled %v request: %q", req.Method, req.URL.EscapedPath()) }) federatedSrv := httptest.NewServer(federatedRoutes) return federatedSrv diff --git a/modules/translation/plural_rules.go b/modules/translation/plural_rules.go index 59665da255..587ee48850 100644 --- a/modules/translation/plural_rules.go +++ b/modules/translation/plural_rules.go @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT // Some useful links: +// https://codeberg.org/forgejo/forgejo/src/branch/forgejo/web_src/js/webcomponents/relative-time.js // https://www.unicode.org/cldr/charts/46/supplemental/language_plural_rules.html // https://translate.codeberg.org/languages/$LANGUAGE_CODE/#information // https://github.com/WeblateOrg/language-data/blob/main/languages.csv @@ -16,7 +17,7 @@ import ( "forgejo.org/modules/translation/i18n" ) -// The constants refer to indices below in `PluralRules` and also in i18n.js, keep them in sync! +// The constants refer to indices below in `PluralRules` and also in web_src/js/webcomponents/relative-time.js, keep them in sync! const ( PluralRuleDefault = 0 PluralRuleBengali = 1 diff --git a/modules/typesniffer/typesniffer.go b/modules/typesniffer/typesniffer.go index 262feb2b05..8cb1513a88 100644 --- a/modules/typesniffer/typesniffer.go +++ b/modules/typesniffer/typesniffer.go @@ -124,7 +124,7 @@ func (ct SniffedType) GetMimeType() string { } // DetectContentType extends http.DetectContentType with more content types. Defaults to text/unknown if input is empty. -func DetectContentType(data []byte) SniffedType { +func DetectContentType(data []byte, filename string) SniffedType { if len(data) == 0 { return SniffedType{"text/unknown"} } @@ -176,6 +176,13 @@ func DetectContentType(data []byte) SniffedType { } } + if ct == "application/octet-stream" && + filename != "" && + !strings.HasSuffix(strings.ToUpper(filename), ".LCOM") && + bytes.Contains(data, []byte("(DEFINE-FILE-INFO ")) { + ct = "text/vnd.interlisp" + } + // GLTF is unsupported by http.DetectContentType // hexdump -n 4 -C glTF.glb if bytes.HasPrefix(data, []byte("glTF")) { @@ -186,7 +193,7 @@ func DetectContentType(data []byte) SniffedType { } // DetectContentTypeFromReader guesses the content type contained in the reader. -func DetectContentTypeFromReader(r io.Reader) (SniffedType, error) { +func DetectContentTypeFromReader(r io.Reader, filename string) (SniffedType, error) { buf := make([]byte, sniffLen) n, err := util.ReadAtMost(r, buf) if err != nil { @@ -194,5 +201,5 @@ func DetectContentTypeFromReader(r io.Reader) (SniffedType, error) { } buf = buf[:n] - return DetectContentType(buf), nil + return DetectContentType(buf, filename), nil } diff --git a/modules/typesniffer/typesniffer_test.go b/modules/typesniffer/typesniffer_test.go index 176d3658bb..d2b7ed4f21 100644 --- a/modules/typesniffer/typesniffer_test.go +++ b/modules/typesniffer/typesniffer_test.go @@ -16,63 +16,63 @@ import ( func TestDetectContentTypeLongerThanSniffLen(t *testing.T) { // Pre-condition: Shorter than sniffLen detects SVG. - assert.Equal(t, "image/svg+xml", DetectContentType([]byte(``)).contentType) + assert.Equal(t, "image/svg+xml", DetectContentType([]byte(``), "").contentType) // Longer than sniffLen detects something else. - assert.NotEqual(t, "image/svg+xml", DetectContentType([]byte(``)).contentType) + assert.NotEqual(t, "image/svg+xml", DetectContentType([]byte(``), "").contentType) } func TestIsTextFile(t *testing.T) { - assert.True(t, DetectContentType([]byte{}).IsText()) - assert.True(t, DetectContentType([]byte("lorem ipsum")).IsText()) + assert.True(t, DetectContentType([]byte{}, "").IsText()) + assert.True(t, DetectContentType([]byte("lorem ipsum"), "").IsText()) } func TestIsSvgImage(t *testing.T) { - assert.True(t, DetectContentType([]byte("")).IsSvgImage()) - assert.True(t, DetectContentType([]byte(" ")).IsSvgImage()) - assert.True(t, DetectContentType([]byte(``)).IsSvgImage()) - assert.True(t, DetectContentType([]byte(``)).IsSvgImage()) + assert.True(t, DetectContentType([]byte(""), "").IsSvgImage()) + assert.True(t, DetectContentType([]byte(" "), "").IsSvgImage()) + assert.True(t, DetectContentType([]byte(``), "").IsSvgImage()) + assert.True(t, DetectContentType([]byte(``), "").IsSvgImage()) assert.True(t, DetectContentType([]byte(` - `)).IsSvgImage()) + `), "").IsSvgImage()) assert.True(t, DetectContentType([]byte(` - `)).IsSvgImage()) + `), "").IsSvgImage()) assert.True(t, DetectContentType([]byte(` - `)).IsSvgImage()) + `), "").IsSvgImage()) assert.True(t, DetectContentType([]byte(` - `)).IsSvgImage()) + `), "").IsSvgImage()) assert.True(t, DetectContentType([]byte(` - `)).IsSvgImage()) + `), "").IsSvgImage()) assert.True(t, DetectContentType([]byte(` - `)).IsSvgImage()) + `), "").IsSvgImage()) assert.True(t, DetectContentType([]byte(` - `)).IsSvgImage()) + `), "").IsSvgImage()) assert.True(t, DetectContentType([]byte(` - `)).IsSvgImage()) + `), "").IsSvgImage()) // the DetectContentType should work for incomplete data, because only beginning bytes are used for detection - assert.True(t, DetectContentType([]byte(`....`)).IsSvgImage()) + assert.True(t, DetectContentType([]byte(`....`), "").IsSvgImage()) - assert.False(t, DetectContentType([]byte{}).IsSvgImage()) - assert.False(t, DetectContentType([]byte("svg")).IsSvgImage()) - assert.False(t, DetectContentType([]byte("")).IsSvgImage()) - assert.False(t, DetectContentType([]byte("text")).IsSvgImage()) - assert.False(t, DetectContentType([]byte("")).IsSvgImage()) - assert.False(t, DetectContentType([]byte(``)).IsSvgImage()) + assert.False(t, DetectContentType([]byte{}, "").IsSvgImage()) + assert.False(t, DetectContentType([]byte("svg"), "").IsSvgImage()) + assert.False(t, DetectContentType([]byte(""), "").IsSvgImage()) + assert.False(t, DetectContentType([]byte("text"), "").IsSvgImage()) + assert.False(t, DetectContentType([]byte(""), "").IsSvgImage()) + assert.False(t, DetectContentType([]byte(``), "").IsSvgImage()) assert.False(t, DetectContentType([]byte(` - `)).IsSvgImage()) + `), "").IsSvgImage()) assert.False(t, DetectContentType([]byte(` - `)).IsSvgImage()) + `), "").IsSvgImage()) assert.False(t, DetectContentType([]byte(` @@ -80,7 +80,7 @@ func TestIsSvgImage(t *testing.T) { -`)).IsSvgImage()) +`), "").IsSvgImage()) assert.False(t, DetectContentType([]byte(` -`)).IsSvgImage()) - assert.False(t, DetectContentType([]byte(``)).IsSvgImage()) - assert.False(t, DetectContentType([]byte(``)).IsSvgImage()) +`), "").IsSvgImage()) + assert.False(t, DetectContentType([]byte(``), "").IsSvgImage()) + assert.False(t, DetectContentType([]byte(``), "").IsSvgImage()) } func TestIsPDF(t *testing.T) { pdf, _ := base64.StdEncoding.DecodeString("JVBERi0xLjYKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0ZpbHRlci9GbGF0ZURlY29kZT4+CnN0cmVhbQp4nF3NPwsCMQwF8D2f4s2CNYk1baF0EHRwOwg4iJt/NsFb/PpevUE4Mjwe") - assert.True(t, DetectContentType(pdf).IsPDF()) - assert.False(t, DetectContentType([]byte("plain text")).IsPDF()) + assert.True(t, DetectContentType(pdf, "").IsPDF()) + assert.False(t, DetectContentType([]byte("plain text"), "").IsPDF()) } func TestIsVideo(t *testing.T) { mp4, _ := base64.StdEncoding.DecodeString("AAAAGGZ0eXBtcDQyAAAAAGlzb21tcDQyAAEI721vb3YAAABsbXZoZAAAAADaBlwX2gZcFwAAA+gA") - assert.True(t, DetectContentType(mp4).IsVideo()) - assert.False(t, DetectContentType([]byte("plain text")).IsVideo()) + assert.True(t, DetectContentType(mp4, "").IsVideo()) + assert.False(t, DetectContentType([]byte("plain text"), "").IsVideo()) } func TestIsAudio(t *testing.T) { mp3, _ := base64.StdEncoding.DecodeString("SUQzBAAAAAABAFRYWFgAAAASAAADbWFqb3JfYnJhbmQAbXA0MgBUWFhYAAAAEQAAA21pbm9yX3Zl") - assert.True(t, DetectContentType(mp3).IsAudio()) - assert.False(t, DetectContentType([]byte("plain text")).IsAudio()) + assert.True(t, DetectContentType(mp3, "").IsAudio()) + assert.False(t, DetectContentType([]byte("plain text"), "").IsAudio()) - assert.True(t, DetectContentType([]byte("ID3Toy\000")).IsAudio()) - assert.True(t, DetectContentType([]byte("ID3Toy\n====\t* hi ๐ŸŒž, ...")).IsText()) // test ID3 tag for plain text - assert.True(t, DetectContentType([]byte("ID3Toy\n====\t* hi ๐ŸŒž, ..."+"๐ŸŒ›"[0:2])).IsText()) // test ID3 tag with incomplete UTF8 char + assert.True(t, DetectContentType([]byte("ID3Toy\000"), "").IsAudio()) + assert.True(t, DetectContentType([]byte("ID3Toy\n====\t* hi ๐ŸŒž, ..."), "").IsText()) // test ID3 tag for plain text + assert.True(t, DetectContentType([]byte("ID3Toy\n====\t* hi ๐ŸŒž, ..."+"๐ŸŒ›"[0:2]), "").IsText()) // test ID3 tag with incomplete UTF8 char } func TestIsGLB(t *testing.T) { glb, _ := hex.DecodeString("676c5446") - assert.True(t, DetectContentType(glb).IsGLB()) - assert.True(t, DetectContentType(glb).Is3DModel()) - assert.False(t, DetectContentType([]byte("plain text")).IsGLB()) - assert.False(t, DetectContentType([]byte("plain text")).Is3DModel()) + assert.True(t, DetectContentType(glb, "").IsGLB()) + assert.True(t, DetectContentType(glb, "").Is3DModel()) + assert.False(t, DetectContentType([]byte("plain text"), "").IsGLB()) + assert.False(t, DetectContentType([]byte("plain text"), "").Is3DModel()) } func TestDetectContentTypeFromReader(t *testing.T) { mp3, _ := base64.StdEncoding.DecodeString("SUQzBAAAAAABAFRYWFgAAAASAAADbWFqb3JfYnJhbmQAbXA0MgBUWFhYAAAAEQAAA21pbm9yX3Zl") - st, err := DetectContentTypeFromReader(bytes.NewReader(mp3)) + st, err := DetectContentTypeFromReader(bytes.NewReader(mp3), "") require.NoError(t, err) assert.True(t, st.IsAudio()) } func TestDetectContentTypeOgg(t *testing.T) { oggAudio, _ := hex.DecodeString("4f67675300020000000000000000352f0000000000007dc39163011e01766f72626973000000000244ac0000000000000071020000000000b8014f6767530000") - st, err := DetectContentTypeFromReader(bytes.NewReader(oggAudio)) + st, err := DetectContentTypeFromReader(bytes.NewReader(oggAudio), "") require.NoError(t, err) assert.True(t, st.IsAudio()) oggVideo, _ := hex.DecodeString("4f676753000200000000000000007d9747ef000000009b59daf3012a807468656f7261030201001e00110001e000010e00020000001e00000001000001000001") - st, err = DetectContentTypeFromReader(bytes.NewReader(oggVideo)) + st, err = DetectContentTypeFromReader(bytes.NewReader(oggVideo), "") require.NoError(t, err) assert.True(t, st.IsVideo()) } @@ -148,7 +148,7 @@ func TestDetectContentTypeAvif(t *testing.T) { avifImage, err := hex.DecodeString("000000206674797061766966") require.NoError(t, err) - st, err := DetectContentTypeFromReader(bytes.NewReader(avifImage)) + st, err := DetectContentTypeFromReader(bytes.NewReader(avifImage), "") require.NoError(t, err) assert.True(t, st.IsImage()) @@ -158,10 +158,24 @@ func TestDetectContentTypeModelGLB(t *testing.T) { glb, err := hex.DecodeString("676c5446") require.NoError(t, err) - st, err := DetectContentTypeFromReader(bytes.NewReader(glb)) + st, err := DetectContentTypeFromReader(bytes.NewReader(glb), "") require.NoError(t, err) // print st for debugging assert.Equal(t, "model/gltf-binary", st.GetMimeType()) assert.True(t, st.IsGLB()) } + +func TestDetectInterlisp(t *testing.T) { + interlisp, err := base64.StdEncoding.DecodeString("ICAKKERFRklORS1GSUxFLUlORk8gHlBBQ0tBR0UgIklOVEVSTElTUCIgHlJFQURUQUJMRSAiSU5URVJMSVNQIiAeQkFTRSAxMCkKCgYB") + require.NoError(t, err) + st, err := DetectContentTypeFromReader(bytes.NewReader(interlisp), "test") + require.NoError(t, err) + assert.True(t, st.IsText()) + st, err = DetectContentTypeFromReader(bytes.NewReader(interlisp), "") + require.NoError(t, err) + assert.False(t, st.IsText()) + st, err = DetectContentTypeFromReader(bytes.NewReader(interlisp), "test.lcom") + require.NoError(t, err) + assert.False(t, st.IsText()) +} diff --git a/modules/util/shellquote_test.go b/modules/util/shellquote_test.go index 969998c592..6c1b778a08 100644 --- a/modules/util/shellquote_test.go +++ b/modules/util/shellquote_test.go @@ -3,7 +3,11 @@ package util -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) func TestShellEscape(t *testing.T) { tests := []struct { @@ -79,13 +83,23 @@ func TestShellEscape(t *testing.T) { "Single quotes don't need to escape except for '...", "~/ ${gitea} `gitea` (gitea) !gitea! \"gitea\" \\gitea\\ 'gitea'", "~/' ${gitea} `gitea` (gitea) !gitea! \"gitea\" \\gitea\\ '\\''gitea'\\'", + }, { + "Inline command", + "some`echo foo`thing", + "\"some\\`echo foo\\`thing\"", + }, { + "Substitution", + `;${HOME}`, + `";\${HOME}"`, + }, { + "ANSI Escape codes (not escaped)", + "\033[31;1;4mHello\033[0m", + "\"\x1b[31;1;4mHello\x1b[0m\"", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := ShellEscape(tt.toEscape); got != tt.want { - t.Errorf("ShellEscape(%q):\nGot: %s\nWanted: %s", tt.toEscape, got, tt.want) - } + assert.Equal(t, tt.want, ShellEscape(tt.toEscape)) }) } } diff --git a/modules/validation/email.go b/modules/validation/email.go index 8e1ffc203d..7960a80a1f 100644 --- a/modules/validation/email.go +++ b/modules/validation/email.go @@ -72,16 +72,23 @@ func validateEmailBasic(email string) error { } func validateEmailDomain(email string) error { - if !IsEmailDomainAllowed(email) { + if _, ok := IsEmailDomainAllowed(email); !ok { return ErrEmailInvalid{email} } return nil } -func IsEmailDomainAllowed(email string) bool { - return isEmailDomainAllowedInternal( - email, +func IsEmailDomainAllowed(email string) (validEmail, ok bool) { + // Normalized the address. This strips for example comments which could be + // used to smuggle a different domain + parsedAddress, err := mail.ParseAddress(email) + if err != nil { + return false, false + } + + return true, isEmailDomainAllowedInternal( + parsedAddress.Address, setting.Service.EmailDomainAllowList, setting.Service.EmailDomainBlockList) } diff --git a/modules/validation/email_test.go b/modules/validation/email_test.go index b7ee766ddb..28158cae53 100644 --- a/modules/validation/email_test.go +++ b/modules/validation/email_test.go @@ -67,8 +67,3 @@ func TestEmailAddressValidate(t *testing.T) { }) } } - -func TestEmailDomainAllowList(t *testing.T) { - res := IsEmailDomainAllowed("someuser@localhost.localdomain") - assert.True(t, res) -} diff --git a/modules/validation/validatable.go b/modules/validation/validatable.go index 4500f6e53d..7bcca03bf8 100644 --- a/modules/validation/validatable.go +++ b/modules/validation/validatable.go @@ -45,7 +45,7 @@ func IsValid(v Validateable) (bool, error) { func ValidateIDExists(value ap.Item, name string) []string { if value == nil { - return []string{fmt.Sprintf("%v should not be nil", name)} + return []string{fmt.Sprintf("Field %v must not be nil", name)} } return ValidateNotEmpty(value.GetID().String(), name) } @@ -76,12 +76,12 @@ func ValidateNotEmpty(value any, name string) []string { if isValid { return []string{} } - return []string{fmt.Sprintf("%v should not be empty", name)} + return []string{fmt.Sprintf("Value %v should not be empty", name)} } func ValidateMaxLen(value string, maxLen int, name string) []string { if utf8.RuneCountInString(value) > maxLen { - return []string{fmt.Sprintf("Value %v was longer than %v", name, maxLen)} + return []string{fmt.Sprintf("Value %v is longer than expected length %v", name, maxLen)} } return []string{} } diff --git a/options/locale/locale_ar.ini b/options/locale/locale_ar.ini index ca74a477ce..23351d9cbf 100644 --- a/options/locale/locale_ar.ini +++ b/options/locale/locale_ar.ini @@ -23,9 +23,7 @@ explore = ุฅูƒุชุดู return_to_forgejo = ุงู„ุนูˆุฏุฉ ุฅู„ู‰ ููˆุฑุฌูŠูˆ write = ุงูƒุชุจ webauthn_error_unknown = ุญุฏุซ ุฎุทุฃ ุบูŠุฑ ู…ุนุฑูˆู. ู…ู† ูุถู„ูƒ ุญุงูˆู„ ู…ุฌุฏุฏุงู‹. -webauthn_reload = ุฅุนุงุฏุฉ ุชุญู…ูŠู„ twofa = ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ -account_settings = ุฅุนุฏุงุฏุงุช ุงู„ุญุณุงุจ version = ุงู„ุฅุตุฏุงุฑ copy_success = ุชู… ุงู„ู†ุณุฎ! help = ู…ุณุงุนุฏุฉ @@ -66,7 +64,6 @@ webauthn_unsupported_browser = ู…ุชุตูุญูƒ ู„ุง ูŠุฏุนู… ูˆูŠุจ ุขูˆุซู† ุญุงู„ copy = ุงู†ุณุฎ enabled = ู…ูููŽุนู‘ูŽู„ rerun = ุฃุนูุฏ ุงู„ุชุดุบูŠู„ -new_org = ู…ู†ุธู…ุฉ ุฌุฏูŠุฏุฉ milestones = ุฃู‡ุฏุงู webauthn_error_insecure = ูˆูŠุจ ุขูˆุซู† ูŠุฏุนู… ูู‚ุท ุงู„ุงุชุตุงู„ุงุช ุงู„ุขู…ู†ุฉ. ู„ู„ุงุฎุชุจุงุฑ ุนู„ู‰ HTTPุŒ ูŠู…ูƒู†ูƒ ุงุณุชุฎุฏุงู… "localhost" ุฃูˆ "127.0.0.1" show_timestamps = ุฅุธู‡ุงุฑ ุงู„ุทูˆุงุจุน ุงู„ุฒู…ู†ูŠุฉ @@ -89,12 +86,9 @@ new_project_column = ุนู…ูˆุฏ ุฌุฏูŠุฏ add = ุฃุถู active_stopwatch = ู…ุชุชุจู‘ูุน ูˆู‚ุช ุงู„ู†ุดุงุท organization = ู…ู†ุธู…ุฉ -new_migrate = ุชุฑุญูŠู„ ุฌุฏูŠุฏ save = ุงุญูุธ sign_in_with_provider = ุณุฌู„ ุงู„ุฏุฎูˆู„ ุจู€ %s ok = ูˆุงูู‚ -manage_org = ุฅุฏุงุฑุฉ ุงู„ู…ู†ุธู…ุงุช -new_repo = ู…ุณุชูˆุฏุน ุฌุฏูŠุฏ webauthn_error_unable_to_process = ุงู„ุฎุงุฏู… ู„ุง ูŠู…ูƒู†ู‡ ู…ุนุงู„ุฌุฉ ุทู„ุจูƒ. register = ุณุฌู„ mirror = ู…ุฑุขุฉ @@ -144,10 +138,9 @@ new_migrate.title = ุงู†ุชู‚ุงู„ ุฌุฏูŠุฏ new_org.title = ู…ู†ุธู…ุฉ ุฌุฏูŠุฏุฉ new_repo.link = ู…ุณุชูˆุฏุน ุฌุฏูŠุฏ new_migrate.link = ุงู†ุชู‚ุงู„ ุฌุฏูŠุฏ - -new_org.link = ู…ู†ุธู…ุฉ ุฌุฏูŠุฏุฉ -test = ุงุฎุชุจุงุฑ copy_path = ู†ุณุฎ ุงู„ู…ุณุงุฑ +test = ุงุฎุชุจุงุฑ +new_org.link = ู…ู†ุธู…ุฉ ุฌุฏูŠุฏุฉ error413 = ู„ู‚ุฏ ุงุณุชู†ูุฏุช ุญุตุชูƒ. [install] @@ -167,7 +160,7 @@ ssl_mode = SSL db_title = ุฅุนุฏุงุฏุงุช ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช install = ุงู„ุชุซุจูŠุช allow_dots_in_usernames = ุงู„ุณู…ุงุญ ู„ู„ู…ุณุชุฎุฏู…ูŠู† ุจูˆุถุน ู†ู‚ุงุท ููŠ ุฃุณู…ุงุฆู‡ู…. ู„ุง ูŠุคุซุฑ ุนู„ู‰ ุงู„ุญุณุงุจุงุช ุงู„ู…ูˆุฌูˆุฏุฉ. -enable_update_checker_helper_forgejo = ูŠูุญุต ุฏูˆุฑูŠุงู‹ ู„ู†ุณุฎ ุฌุฏูŠุฏุฉ ู…ู† ููˆุฑุฌูŠูˆ ุนู† ุทุฑูŠู‚ ุงู„ุชุญู‚ู‚ ู…ู† ุณุฌู„ DNS TXT ุนู†ุฏ release.forgejo.org. +enable_update_checker_helper_forgejo = ูŠูุญุต ุฏูˆุฑูŠุงู‹ ู„ู†ุณุฎ ุฌุฏูŠุฏุฉ ู…ู† ููˆุฑุฌูŠูˆ ุนู† ุทุฑูŠู‚ ุงู„ุชุญู‚ู‚ ู…ู† ุณุฌู„ TXT DNS ุนู†ุฏ release.forgejo.org. db_schema_helper = ุงุชุฑูƒู‡ ูุงุฑุบุงู‹ ู„ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช ุงู„ุงูุชุฑุงุถูŠุฉ ("ุนุงู…"). db_type = ู†ูˆุน ู‚ุงุนุฏุฉ ุงู„ุจูŠุงู†ุงุช reinstall_confirm_check_1 = ุงู„ุจูŠุงู†ุงุช ุงู„ู…ุดูุฑุฉ ุจูˆุงุณุทุฉ SECRET_KEY ููŠ ู…ูู„ูŽูู‘ app.ini ู‚ุฏ ุชููู‚ุฏ: ู‚ุฏ ู„ุง ูŠุชู…ูƒู† ุงู„ู…ุณุชุฎุฏู…ูˆู† ู…ู† ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุจุงุณุชุฎุฏุงู… ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ (2FA/OTP) ูˆ ุงู„ู…ุฑุงูŠุงุช ู‚ุฏ ู„ุง ุชุนู…ู„ ุจุงู„ุดูƒู„ ุงู„ุตุญูŠุญ. ู…ู† ุฎู„ุงู„ ุชุญุฏูŠุฏ ู‡ุฐุง ุงู„ู…ุฑุจุน ุฃู†ุช ุชุคูƒุฏ ุฃู† ู…ูู„ูŽูู‘ app.ini ุงู„ุญุงู„ูŠ ูŠุญุชูˆูŠ ุนู„ู‰ ุงู„ู€SECRET_KEY ุงู„ุตุญูŠุญ. @@ -227,9 +220,9 @@ enable_captcha.description = ู…ุทุงู„ุจุฉ ุงู„ู…ุณุชุฎุฏู…ูŠู† ุจุงุฌุชูŠุงุฒ ุง openid_signup.description = ูุนู‘ู„ ุงู„ุชุณุฌูŠู„ ุงู„ุฐุงุชูŠ ู„ู„ู…ุณุชุฎุฏู…ูŠู† ุนุจุฑ OpenID. require_sign_in_view = ูŠุชุทู„ุจ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ู„ุนุฑุถ ู…ุญุชูˆู‰ ุงู„ู…ุซูŠู„ require_sign_in_view.description = ู…ูƒู‘ู† ูˆุตูˆู„ ุงู„ุตูุญุงุช ู„ู„ู…ุณุชุฎุฏู…ูŠู† ูู‚ุท. ู„ู† ูŠุฑู‰ ุงู„ุฒุงุฆุฑูˆู† ุณูˆู‰ ุตูุญุงุช ุงู„ุชุณุฌูŠู„ ูˆุงู„ุชุณุฌูŠู„. -admin_setting.description = ุฅู†ุดุงุก ุญุณุงุจ ุฅุฏุงุฑูŠ ู‡ูˆ ุงุฎุชูŠุงุฑูŠ. ุฃูˆู„ ู…ุณุชุฎุฏู… ู…ูุณุฌู„ ุณูŠุตุจุญ ุชู„ู‚ุงุฆูŠุง ู…ุฏูŠุฑุง. +admin_setting.description = ุฅู†ุดุงุก ุญุณุงุจ ุฅุฏุงุฑูŠ ู‡ูˆ ุงุฎุชูŠุงุฑูŠ. ุฃูˆู„ ู…ุณุชุฎุฏู… ู…ูุณุฌู„ ุณูŠุตุจุญ ุชู„ู‚ุงุฆูŠุง ู…ุฏูŠุฑู‹ุง. admin_password = ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ -admin_email = ุนู†ูˆุงู† ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ +admin_email = ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ install_btn_confirm = ุชุซุจุช ููˆุฑุฌูŠูˆ secret_key_failed = ู„ู… ูŠุชู… ุชูˆู„ูŠุฏ ู…ูุชุงุญ ุณุฑูŠ: %v save_config_failed = ูุดู„ ููŠ ุญูุธ ุงู„ุฅุนุฏุงุฏ: %s @@ -251,22 +244,20 @@ default_keep_email_private.description = ู‚ู… ุจุชู…ูƒูŠู† ุฅุฎูุงุก ุนู†ูˆุงู† env_config_keys = ุฅุนุฏุงุฏุงุช ุจูŠุฆูŠุฉ default_allow_create_organization = ุงุณู…ุญ ุจุฅู†ุดุงุก ุงู„ู…ู†ุธู…ุงุช ุจุดูƒู„ ุงูุชุฑุงุถูŠ invalid_app_data_path = ู…ุณุงุฑ ุจูŠุงู†ุงุช ุงู„ุชุทุจูŠู‚ ุบูŠุฑ ุตุงู„ุญ: %v -enable_update_checker_helper = ูŠูุญุต ู„ุฅูŠุฌุงุฏ ุงุตุฏุงุฑุงุช ุฌุฏูŠุฏุฉ ุนู† ุทุฑูŠู‚ ุงู„ุฅุชุตุงู„ ุจุณูŠุฑูุฑุงุช ููˆุฑุฌูŠูˆ. invalid_repo_path = ุงู„ู…ุณุงุฑ ุงู„ุฌุฒุฑูŠ ู„ู„ู…ุณุชูˆุฏุน ุบูŠุฑ ุตุงู„ุญ: %v internal_token_failed = ูุดู„ ุชูˆู„ูŠุฏ ุงู„ุฑู…ุฒ ุงู„ุฏุงุฎู„ูŠ: %v -no_reply_address = ู†ุทุงู‚ุงุช ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ุงู„ู…ุฎููŠุฉ +no_reply_address = ู†ุทุงู‚ ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ู…ุฎููŠ default_keep_email_private = ุฃุฎูู ุนู†ุงูˆูŠู† ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ู…ุจุฏุฆูŠุง admin_name = ุงุณู… ู…ุณุชุฎุฏู… ุงู„ู…ุฏูŠุฑ default_allow_create_organization.description = ุงู„ุณู…ุงุญ ู„ู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ุฌุฏุฏ ุจุฅู†ุดุงุก ู…ู†ุชุฏูŠุงุช ุงู„ู…ุฌู…ูˆุนุฉ ุจุดูƒู„ ุงูุชุฑุงุถูŠ. ุนู†ุฏ ุชุนุทูŠู„ ู‡ุฐุง ุงู„ุฎูŠุงุฑุŒ ุณูŠุชุนูŠู† ุนู„ู‰ ุงู„ู…ุณุคูˆู„ ู…ู†ุญ ุฅุฐู† ู„ุฅู†ุดุงุก ู…ู†ุชุฏูŠุงุช ุงู„ู…ุฌู…ูˆุนุฉ ู„ู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ุฌุฏุฏ. password_algorithm = ุฎูˆุงุฑุฒู…ูŠุฉ ุชุฌุฒุฆุฉ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ invalid_password_algorithm = ุฎูˆุงุฑุฒู…ูŠุฉ ุจุตู…ุฉ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุบูŠุฑ ุตุงู„ุญุฉ -password_algorithm_helper = ุงุฎุชุฑ ุฎูˆุงุฑุฒู…ูŠุฉ ุจุตู…ุฉ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ. ุชุฎุชู„ู ุงู„ุฎูˆุงุฑุฒู…ูŠุงุช ููŠ ู…ุชุทู„ุจุงุชู‡ุง ูˆู‚ูˆุชู‡ุง. ุฎูˆุงุฑุฒู…ูŠุฉ argon2 ุขู…ู†ุฉ ู„ูƒู† ุชุชุทู„ุจ ุงู„ูƒุซูŠุฑ ู…ู† ุงู„ุฐุงูƒุฑุฉ ูˆู„ุฐู„ูƒ ู‚ุฏ ุชูƒูˆู† ุบูŠุฑ ู…ู„ุงุฆู…ุฉ ู„ู„ุฃู†ุธู…ุฉ ุงู„ุตุบูŠุฑุฉ. - -app_slogan = ุดุนุงุฑ ุงู„ู…ุซูŠู„ +password_algorithm_helper = ุงุฎุชุฑ ุฎูˆุงุฑุฒู…ูŠุฉ ุจุตู…ุฉ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ. ุชุฎุชู„ู ุงู„ุฎูˆุงุฑุฒู…ูŠุงุช ููŠ ู…ุชุทู„ุจุงุชู‡ุง ูˆู‚ูŠูˆุงู‡ุง. ุฎูˆุงุฑุฒู…ูŠุฉ argon2 ุขู…ู†ุฉ ู„ูƒู† ุชุชุทู„ุจ ุงู„ูƒุซูŠุฑ ู…ู† ุงู„ุฐุงูƒุฑุฉ ูˆู„ุฐู„ูƒ ู‚ุฏ ุชูƒูˆู† ุบูŠุฑ ู…ู„ุงุฆู…ุฉ ู„ู„ุฃู†ุธู…ุฉ ุงู„ุตุบูŠุฑุฉ. app_slogan_helper = ุฃุฏุฎู„ ุดุนุงุฑ ุงู„ู…ุซูŠู„ ุงู„ุฎุงุต ุจูƒ ู‡ู†ุง. ุงุชุฑูƒู‡ ูุงุฑุบุงู‹ ู„ุชุนุทูŠู„ู‡. -smtp_from_invalid = ุนู†ูˆุงู† "ุŒุจุฑูŠุฏ ุงู„ุฅุฑุณุงู„ ูƒู€" ุบูŠุฑ ุตุงู„ุญ +app_slogan = ุดุนุงุฑ ุงู„ู…ุซูŠู„ allow_only_external_registration = ุงู„ุณู…ุงุญ ุจุงู„ุชุณุฌูŠู„ ุนุจุฑ ุงู„ุฎุฏู…ุงุช ุงู„ุฎุงุฑุฌูŠุฉ ูู‚ุท config_location_hint = ุณูŠุชู… ุญูุธ ุฎูŠุงุฑุงุช ุงู„ุชู‡ูŠุฆุฉ ู‡ุฐู‡ ููŠ: +smtp_from_invalid = ุนู†ูˆุงู† "ุŒุจุฑูŠุฏ ุงู„ุฅุฑุณุงู„ ูƒู€" ุบูŠุฑ ุตุงู„ุญ [editor] buttons.list.ordered.tooltip = ุฃุถู ู‚ุงุฆู…ุฉ ู…ุฑู‚ู…ุฉ @@ -283,18 +274,17 @@ buttons.mention.tooltip = ุงุฐูƒุฑ ู…ุณุชุฎุฏู…ู‹ุง ุฃูˆ ูุฑูŠู‚ู‹ุง buttons.italic.tooltip = ุฃุถู ู†ุตู‹ุง ู…ุงุฆู„ู‹ุง buttons.link.tooltip = ุงุถู ุฑุงุจุท buttons.disable_monospace_font = ุนุทู‘ู„ ุงู„ุฎุท ุงู„ุซุงุจุช ุงู„ุนุฑุถ - -buttons.indent.tooltip = ุชุฏุงุฎู„ ุงู„ุนู†ุงุตุฑ ุจู†ูุณ ุงู„ู…ุณุชูˆู‰ buttons.unindent.tooltip = โ€ชุนู†ุงุตุฑ ุบูŠุฑ ู…ุชุณุงูˆูŠุฉ ู…ู† ู†ูุณ ุงู„ู…ุณุชูˆู‰ -buttons.new_table.tooltip = ุฅุถุงูุฉ ุฌุฏูˆู„ +buttons.indent.tooltip = ุชุฏุงุฎู„ ุงู„ุนู†ุงุตุฑ ุจู†ูุณ ุงู„ู…ุณุชูˆู‰ table_modal.header = ุฅุถุงูุฉ ุฌุฏูˆู„ table_modal.placeholder.header = ุงู„ุชุฑูˆูŠุณุฉ table_modal.placeholder.content = ุงู„ู…ุญุชูˆู‰ table_modal.label.rows = ุงู„ุตููˆู table_modal.label.columns = ุงู„ุฃุนู…ุฏุฉ -link_modal.header = ุฅุถุงูุฉ ุฑุงุจุท link_modal.url = Url link_modal.description = ุงู„ูˆุตู +buttons.new_table.tooltip = ุฅุถุงูุฉ ุฌุฏูˆู„ +link_modal.header = ุฅุถุงูุฉ ุฑุงุจุท link_modal.paste_reminder = ุชู„ู…ูŠุญ: ุจุงุณุชุฎุฏุงู… ุนู†ูˆุงู† URL ููŠ ุญุงูุธุชูƒุŒ ูŠู…ูƒู†ูƒ ุงู„ู„ุตู‚ ู…ุจุงุดุฑุฉู‹ ููŠ ุงู„ู…ุญุฑุฑ ู„ุฅู†ุดุงุก ุฑุงุจุท. [aria] @@ -312,31 +302,31 @@ blocked_since = ู…ุญุธูˆุฑ ู…ู†ุฐ %s comment_type_group_milestone = ุงู„ุฃู‡ุฏุงู ui = ุงู„ุณู…ุฉ email_notifications.disable = ุนุทู‘ู„ ุฅุดุนุงุฑุงุช ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ -passcode_invalid = ุฑู…ุฒ ุงู„ุฏุฎูˆู„ ุฎุทุฃ. ุญุงูˆู„ ู…ุฑุฉ ุฃุฎุฑู‰. +passcode_invalid = ุฑู…ุฒ ุงู„ุฏุฎูˆู„ ุฎุทุฃ. ุญุงูˆู„ ู…ุฌุฏุฏุงู‹. openid_deletion = ุฃุฒู„ ุนู†ูˆุงู† OpenID activate_email = ุฃุฑุณู„ ุงู„ุชูุนูŠู„ uploaded_avatar_not_a_image = ุงู„ู…ู„ู ุงู„ู…ุฑููˆุน ู„ูŠุณ ุตูˆุฑุฉ. theme_update_error = ุงู„ุณู…ุฉ ุงู„ู…ุฎุชุงุฑุฉ ุบูŠุฑ ู…ูˆุฌูˆุฏุฉ. -twofa_disabled = ุนูุทู‘ูู„ ุงู„ุงุณุชูŠุซุงู‚ ุงู„ุซู†ุงุฆูŠ. +twofa_disabled = ุชู… ุชุนุทูŠู„ ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ. theme_desc = ุณุชูƒูˆู† ู‡ุฐู‡ ุงู„ุณู…ุฉ ุงู„ู…ุจุฏุฆูŠุฉ ู„ูƒ ุนุจุฑ ุงู„ู…ูˆู‚ุน. new_password = ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุงู„ุฌุฏูŠุฏุฉ -twofa_disable_desc = ุชุนุทูŠู„ ุงู„ุงุณุชูŠุซุงู‚ ุงู„ุซู†ุงุฆูŠ ุณูŠุฌุนู„ ุญุณุงุจูƒ ุฃู‚ู„ ุฃู…ุงู†ู‹ุง. ุฃุชุฑูŠุฏ ุงู„ุงุณุชู…ุฑุงุฑุŸ +twofa_disable_desc = ุชุนุทูŠู„ ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ ุณูŠุฌุนู„ ุญุณุงุจูƒ ุฃู‚ู„ ุฃู…ุงู†ุงู‹. ุงู„ุงุณุชู…ุฑุงุฑุŸ manage_themes = ุงู„ู…ูˆุถูˆุน ุงู„ุงูุชุฑุงุถูŠ -delete_prompt = ู‡ุฐู‡ ุงู„ุนู…ู„ูŠุฉ ุณุชุญุฐู ุญุณุงุจูƒ ุฅู„ู‰ ุงู„ุฃุจุฏ. ู„ุง ูŠู…ูƒู† ุงู„ุชุฑุงุฌุน ุนู†ู‡ุง ุจุนุฏ ุฐู„ูƒ. -cancel = ุฃู„ุบ -repos_none = ู„ูŠุณ ู„ุฏูŠูƒ ุฃูŠ ู…ุณุชูˆุฏุน. +delete_prompt = ู‡ุฐู‡ ุงู„ุนู…ู„ูŠุฉ ุณุชุญุฐู ุญุณุงุจูƒ ุฅู„ู‰ ุงู„ุฃุจุฏ. ู„ุง ูŠู…ูƒู† ุงู„ุชุฑุงุฌุน ุนู† ุฐู„ูƒ. +cancel = ุฅู„ุบุงุก +repos_none = ู„ุง ุชู…ู„ูƒ ุฃูŠุฉ ู…ุณุชูˆุฏุน. twofa_desc = ู„ุญู…ุงูŠุฉ ุญุณุงุจูƒ ู…ู† ุณุฑู‚ุฉ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑุŒ ูŠู…ูƒู†ูƒ ุงุณุชุฎุฏุงู… ู‡ุงุชู ุฐูƒูŠ ุฃูˆ ุฌู‡ุงุฒ ุขุฎุฑ ู„ุงุณุชู„ุงู… ูƒู„ู…ุฉ ู…ุฑูˆุฑ ู…ุคู‚ุชุฉ ุฐุงุช ุงุณุชุฎุฏุงู… ูˆุงุญุฏ ("TOTP"). -email_notifications.submit = ุงุถุจุท ุชูุถูŠู„ุงุช ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ +email_notifications.submit = ุถุจุท ุชูุถูŠู„ุงุช ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ update_user_avatar_success = ุญูุฏู‘ูุซุช ุตูˆุฑุฉ ุงู„ู…ุณุชุฎุฏู… ุงู„ุฑู…ุฒูŠุฉ. activations_pending = ููŠ ุงู†ุชุธุงุฑ ุงู„ุชูุนูŠู„ -language = ุงู„ู„ุบุฉ +language = ุงู„ู„ู‘ุบุฉ primary = ุงู„ุฃุณุงุณูŠ update_avatar_success = ุญูุฏู‘ูุซุช ุตูˆุฑุชูƒ ุงู„ุฑู…ุฒูŠุฉ. keep_email_private = ุฃุฎูู ุนู†ูˆุงู† ุงู„ุจุฑูŠุฏ delete_current_avatar = ุงุญุฐู ุงู„ุตูˆุฑุฉ ุงู„ุฑู…ุฒูŠุฉ ุงู„ุญุงู„ูŠุฉ avatar = ุตูˆุฑุฉ ุฑู…ุฒูŠุฉ email_preference_set_success = ุญูุฏู‘ูุซุช ุชูุถูŠู„ุงุช ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ. -scan_this_image = ุงู…ุณุญ ู‡ุฐู‡ ุงู„ุตูˆุฑุฉ ุจุชุทุจูŠู‚ ุงู„ุงุณุชูŠุซุงู‚ ุงู„ุฐูŠ ุชุณุชุฎุฏู…ู‡: +scan_this_image = ุงู…ุณุญ ู‡ุฐู‡ ุงู„ุตูˆุฑุฉ ุจุชุทุจูŠู‚ ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ุฐูŠ ุชุณุชุฎุฏู…ู‡: orgs_none = ู„ุณุช ุนุถูˆู‹ุง ููŠ ุฃูŠ ู…ู†ุธู…ุฉ. delete_email = ุฃุฒู„ู‡ theme_update_success = ุญูุฏู‘ูุซุช ุงู„ุณู…ุฉ. @@ -363,19 +353,17 @@ openid_deletion_success = ุฃุฒูŠู„ ุนู†ูˆุงู† OpenID. add_email_success = ุฃุถูŠู ุนู†ูˆุงู† ุงู„ุจุฑูŠุฏ ุงู„ุฌุฏูŠุฏ. enable_custom_avatar = ุงุณุชุฎุฏู… ุตูˆุฑุฉ ุฑู…ุฒูŠุฉ ู…ุฎุตุตุฉ update_avatar = ุญุฏู‘ุซ ุงู„ุตูˆุฑุฉ ุงู„ุฑู…ุฒูŠุฉ -twofa_disable = ุชุนุทูŠู„ ุงู„ุงุณุชูŠุซุงู‚ ุงู„ุซู†ุงุฆูŠ +twofa_disable = ุชุนุทูŠู„ ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ retype_new_password = ุชุฃูƒูŠุฏ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุงู„ุฌุฏูŠุฏุฉ manage_emails = ุฃุฏุฑ ุนู†ุงูˆูŠู† ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ then_enter_passcode = ูˆุฃุฏุฎู„ ุฑู…ุฒ ุงู„ุฏุฎูˆู„ ุงู„ุธุงู‡ุฑ ููŠ ุงู„ุชุทุจูŠู‚: update_password = ุญุฏู‘ุซ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ continue = ุงุณุชู…ุฑ -emails = ุนู†ุงูˆูŠู† ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ -confirm_delete_account = ุฃูƒูุฏ ุงู„ุญุฐู +confirm_delete_account = ุชุฃูƒูŠุฏ ุงู„ุญุฐู change_password_success = ุญูุฏู‘ูุซุช ูƒู„ู…ุฉ ู…ุฑูˆุฑูƒ. ุณุฌู‘ู„ ุงู„ุฏุฎูˆู„ ุจูƒู„ู…ุฉ ู…ุฑูˆุฑูƒ ุงู„ุฌุฏูŠุฏุฉ ู…ู† ุงู„ุขู† ูุตุงุนุฏุง. email_deletion_desc = ุณูŠูุฒุงู„ ุนู†ูˆุงู† ุงู„ุจุฑูŠุฏ ู‡ุฐุง ู…ุน ูƒู„ ุงู„ู…ุนู„ูˆู…ุงุช ุงู„ู…ุฑุชุทุจุฉ ุจู‡ ู…ู† ุญุณุงุจูƒ. ู„ูƒู† ุณุชุจู‚ู‰ ุฅูŠุฏุงุนุงุช Git ุงู„ู…ูˆุฏุนุฉ ุจู‡ ุจู„ุง ุชุบูŠูŠุฑ. ุฃุชุฑูŠุฏ ุงู„ุงุณุชู…ุฑุงุฑุŸ or_enter_secret = ุฃูˆ ุฃุฏุฎู„ ุงู„ุณุฑ: %s applications = ุงู„ุชุทุจูŠู‚ุงุช -access_token_deletion_cancel_action = ุฃู„ุบู location = ุงู„ู…ูˆู‚ุน ุงู„ุฌุบุฑุงููŠ password = ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ comment_type_group_title = ุงู„ุนู†ูˆุงู† @@ -383,20 +371,17 @@ location_placeholder = ุดุงุฑูƒ ู…ูƒุงู†ูƒ ุงู„ุชู‚ุฑูŠุจูŠ ู…ุน ุงู„ุขุฎุฑูŠู† appearance = ุงู„ู…ุธู‡ุฑ repos = ุงู„ู…ุณุชูˆุฏุนุงุช change_username = ุชู… ุชุญุฏูŠุซ ุงุณู… ู…ุณุชุฎุฏู…ูƒ. -social = ุงู„ุญุณุงุจุงุช ุงู„ุงุฌุชู…ุงุนูŠุฉ twofa = ุงู„ุงุณุชูŠุซุงู‚ ุงู„ุซู†ุงุฆูŠ saved_successfully = ุญูุฏู‘ูุซุช ุฅุนุฏุงุฏุงุชูƒ ุจู†ุฌุงุญ. update_theme = ุญุฏู‘ูุซ ุงู„ุณู…ุฉ -access_token_deletion_confirm_action = ุงุญุฐู website = ุงู„ู…ูˆู‚ุน ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ -delete_token = ุงุญุฐู +delete_token = ุญุฐู hidden_comment_types.ref_tooltip = ุงู„ุชุนู„ูŠู‚ุงุช ุงู„ุชูŠ ุชู‚ูˆู„ ุฃู† ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ ู‚ุฏ ุฃุดูŠุฑ ุฅู„ูŠู‡ุง ููŠ ู…ุณุฃู„ุฉ ุฃุฎุฑู‰ ุฃูˆ ุฅูŠุฏุงุน ุฃูˆ ุบูŠุฑ ุฐู„ูƒโ€ฆ update_language_success = ุชู… ุชุญุฏูŠุซ ุงู„ู„ุบุฉ. privacy = ุงู„ุฎุตูˆุตูŠุฉ comment_type_group_label = ุงู„ุชุตู†ูŠูุงุช -account_link = ุงู„ุญุณุงุจุงุช ุงู„ู…ุฑุชุจุทุฉ comment_type_group_assignee = ุงู„ู…ูƒู„ููˆู† -update_language = ุญุฏู‘ูุซ ุงู„ู„ุบุฉ +update_language = ุชุบูŠูŠุฑ ุงู„ู„ุบุฉ organization = ุงู„ู…ู†ุธู…ุงุช update_language_not_found = ุงู„ู„ุบุฉ "%s" ุบูŠุฑ ู…ุชุงุญุฉ. update_profile_success = ุชู… ุชุญุฏูŠุซ ู…ู„ููƒ ุงู„ุดุฎุตูŠ. @@ -424,7 +409,7 @@ can_write_info = ูƒุชุงุจุฉ delete = ุงุญุฐู ุงู„ุญุณุงุจ oauth2_application_name = ุงุณู… ุงู„ุชุทุจูŠู‚ key_state_desc = ู‡ุฐุง ุงู„ู…ูุชุงุญ ุฃุณุชูุนู…ู„ ุฎู„ุงู„ ุขุฎุฑ 7 ุฃูŠุงู… -webauthn_delete_key = ุฃุฒูู„ ู…ูุชุงุญ ุงู„ุฃู…ุงู† +webauthn_delete_key = ุฅุฒุงู„ุฉ ู…ูุชุงุญ ุงู„ุฃู…ุงู† valid_forever = ุตุงู„ุญ ู„ู„ุฃุจุฏ can_read_info = ู‚ุฑุงุกุฉ create_oauth2_application_button = ุฃู†ุดุฆ ุชุทุจูŠู‚ุง @@ -436,14 +421,13 @@ select_permissions = ุฃุฎุชุฑ ุงู„ุชุตุงุฑูŠุญ added_on = ู…ูุถุงู ููŠ %s show_openid = ุฃุธู‡ุฑ ุนู„ู‰ ุงู„ู…ู„ู ุงู„ุดุฎุตูŠ hide_openid = ุฃุฎููŠ ู…ู† ุงู„ู…ู„ู ุงู„ุดุฎุตูŠ -webauthn_delete_key_desc = ุฅุฐุง ุฃุฒู„ุช ู…ูุชุงุญ ุงู„ุฃู…ุงู†ุŒ ูู„ู† ุชุชู…ูƒู† ู…ู† ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุจุงุณุชุฎุฏุงู…ู‡. ุงูƒู…ู„ุŸ +webauthn_delete_key_desc = ุฅุฐุง ุฃุฒู„ุช ู…ูุชุงุญ ุงู„ุฃู…ุงู†ุŒ ูู„ู† ุชุชู…ูƒู† ู…ู† ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุจุงุณุชุฎุฏุงู…ู‡. ุงู„ู…ุชุงุจุนุฉุŸ permissions_list = ุงู„ุชุตุงุฑูŠุญ: webauthn_key_loss_warning = ุฅุฐุง ูู‚ุฏุช ู…ูุงุชูŠุญ ุงู„ุฃู…ุงู† ุงู„ุฎุงุตุฉ ุจูƒุŒ ูุณูˆู ุชูู‚ุฏ ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ. -hooks.desc = ุฃุถู ุฎุทุงุทูŠู ูˆูŠุจ ุชูุทู„ู‚ ู„ูƒู„ ู…ุณุชูˆุฏุนุงุชูƒ. -keep_activity_private_popup = ูŠุฌุนู„ ุงู„ู†ุดุงุท ู…ุฑุฃูŠุงู‹ ู„ูƒ ูˆู„ู„ู…ุฏูŠุฑูŠู† ูู‚ุท +hooks.desc = ุฅุถุงูุฉ ุฎุทุงุทูŠู ูˆูŠุจ ุณูŠุชู… ุชุดุบูŠู„ู‡ุง ู„ู€ ุฌู…ูŠุน ุงู„ู…ุณุชูˆุฏุนุงุช ุงู„ุชูŠ ุชู…ุชู„ูƒู‡ุง. keep_email_private_popup = ุณูŠุคุฏูŠ ู‡ุฐุง ุฅู„ู‰ ุฅุฎูุงุก ุนู†ูˆุงู† ุจุฑูŠุฏูƒ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ู…ู† ู…ู„ููƒ ุงู„ุดุฎุตูŠุŒ ูˆูƒุฐู„ูƒ ุนู†ุฏ ุชู‚ุฏูŠู… ุทู„ุจ ุณุญุจ ุฃูˆ ุชุญุฑูŠุฑ ู…ู„ู ุจุงุณุชุฎุฏุงู… ูˆุงุฌู‡ุฉ ุงู„ูˆูŠุจ. ู„ู† ูŠุชู… ุชุนุฏูŠู„ ุงู„ุงู„ุชุฒุงู…ุงุช ุงู„ู…ุฏููˆุนุฉ. ุงุณุชุฎุฏู… %s ููŠ ุงู„ุฅูŠุฏุงุนุงุช ู„ุฑุจุทู‡ุง ุจุญุณุงุจูƒ. ssh_key_name_used = ู‡ู†ุงูƒ ู…ูุชุงุญ SSH ุจู†ูุณ ุงู„ุงุณู… ู…ูˆุฌูˆุฏ ุจุงู„ูุนู„ ุนู„ู‰ ุญุณุงุจูƒ. -authorized_oauth2_applications = ุชุทุจูŠู‚ุงุช OAuth2 ุงู„ู…ุฃุฐูˆู†ุฉ +authorized_oauth2_applications = ุชุทุจูŠู‚ุงุช OAuth2 ุงู„ู…ุฃุฐูˆู† ู„ู‡ุง uid = ุงู„ู…ุนุฑู‘ู ุงู„ุฑู…ุฒูŠ manage_openid = ุนู†ุงูˆูŠู† OpenID webauthn = ุงุณุชูŠุซุงู‚ ุซู†ุงุฆูŠ (ู…ูุงุชูŠุญ ุงู„ุฃู…ุงู†) @@ -454,7 +438,6 @@ keep_activity_private = ุงุฎู ุงู„ู†ุดุงุท ู…ู† ุตูุญุฉ ุงู„ู…ู„ู ุงู„ุดุฎุต profile_desc = ุชุญูƒู… ููŠ ูƒูŠููŠุฉ ุธู‡ูˆุฑ ู…ู„ููƒ ุงู„ุดุฎุตูŠ ู„ู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ุขุฎุฑูŠู†. ุณูŠุชู… ุงุณุชุฎุฏุงู… ุนู†ูˆุงู† ุจุฑูŠุฏูƒ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ุงู„ุฃุณุงุณูŠ ู„ู„ุฅุดุนุงุฑุงุช ูˆุงุณุชุนุงุฏุฉ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ูˆุนู…ู„ูŠุงุช Git ุงู„ู…ุนุชู…ุฏุฉ ุนู„ู‰ ุงู„ูˆูŠุจ. can_not_add_email_activations_pending = ู‡ู†ุงูƒ ุชูุนูŠู„ ู‚ูŠุฏ ุงู„ุงู†ุชุธุงุฑุŒ ุญุงูˆู„ ู…ุฌุฏุฏุงู‹ ุฎู„ุงู„ ุจุถุน ุฏู‚ุงุฆู‚ ุฅู† ุฃุฑุฏุช ุฃุถุงูู‡ ุจุฑูŠุฏ ุฅู„ูƒุชุฑูˆู†ูŠ ุฌุฏูŠุฏ. gpg_key_id_used = ู‡ู†ุงูƒ ู…ูุชุงุญ GPG ุนุงู… ุจู†ูุณ ุงู„ู…ุนุฑู ู…ูˆุฌูˆุฏ ุจุงู„ูุนู„. -add_new_gpg_key = ุฃุถู ู…ูุชุงุญ GPG manage_gpg_keys = ุฅุฏุงุฑุฉ ู…ูุงุชูŠุญ GPG password_username_disabled = ู„ุง ูŠู…ูƒู† ู„ู„ู…ุณุชุฎุฏู…ูŠู† ุบูŠุฑ ุงู„ู…ุญู„ูŠูŠู† ุฃู† ูŠุบูŠุฑูˆุง ุงุณู…ู‡ู…. ูŠูุฑุฌู‰ ุงู„ุชูˆุงุตู„ ู…ุน ู…ุฏูŠุฑ ุงู„ู…ูˆู‚ุน ู„ุชูุงุตูŠู„ ุฃูƒุซุฑ. comment_type_group_issue_ref = ู…ุฑุฌุน ุงู„ู…ุณุฃู„ุฉ @@ -462,17 +445,16 @@ gpg_desc = ุชุฑุชุจุท ู…ูุงุชูŠุญ GPG ุงู„ุนุงู…ุฉ ู‡ุฐู‡ ุจุญุณุงุจูƒ. ุญุงู manage_ssh_keys = ุฅุฏุงุฑุฉ ู…ูุงุชูŠุญ SSH openid_desc = ูŠุชูŠุญ ู„ูƒ OpenID ุจุชููˆูŠุถ ุงู„ุงุณุชูŠุซุงู‚ ุฅู„ู‰ ู…ุฒูˆู‘ุฏ ุฎุงุฑุฌูŠ. 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' -add_new_key = ุฃุถู ู…ูุชุงุญ SSH hidden_comment_types_description = ุฃู†ูˆุงุน ุงู„ุชุนู„ูŠู‚ ุงู„ู…ูุฎุชุงุฑุฉ ู‡ู†ุง ู„ู† ุชุธู‡ุฑ ุฏุงุฎู„ ุตูุญุงุช ุงู„ู…ุณุงุฆู„. ุฃุฎุชูŠุงุฑ "ุชุตู†ูŠู" ู…ุซู„ุงู‹ ูŠู…ุณุญ ูƒู„ ุชุนู„ูŠู‚ุงุช "<ู…ุณุชุฎุฏู…> ุฃุถุงู/ู…ุณุญ <ุชุตู†ูŠู>". key_content_gpg_placeholder = ูŠุจุฏุฃ ุจู€ '-----BEGIN PGP PUBLIC KEY BLOCK-----' add_email_confirmation_sent = ุจุฑูŠุฏ ุชูุนูŠู„ ุฌุฏูŠุฏ ุชู… ุฅุฑุณุงู„ู‡ ุฅู„ู‰ "%s". ูŠูุฑุฌู‰ ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ุจุฑูŠุฏ ุงู„ูˆุงุฑุฏ ุฎู„ุงู„ %s ู„ุชุฃูƒูŠุฏ ุนู†ูˆุงู† ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ. ssh_desc = ู…ูุงุชูŠุญ SSH ุงู„ุนู…ูˆู…ูŠุฉ ู‡ุฐู‡ ู…ุฑุชุจุทุฉ ุจุญุณุงุจูƒ. ูˆุชุณู…ุญ ุงู„ู…ูุงุชูŠุญ ุงู„ุฎุตูˆุตูŠุฉ ุงู„ู…ุฑุงูู‚ุฉ ุจุงู„ูˆุตูˆู„ ุงู„ูƒุงู…ู„ ุฅู„ู‰ ู…ุณุชูˆุฏุนุงุชูƒ. ูˆูŠู…ูƒู† ุงุณุชุนู…ุงู„ ู…ูุงุชูŠุญ SSH ุงู„ู…ูˆุซู‘ูŽู‚ุฉ ู„ุชูˆุซูŠู‚ ุฅูŠุฏุงุนุงุช ุฌุช ุงู„ู…ูˆู‚ู‘ูŽุนุฉ ุจู…ูุงุชูŠุญ SSH. ssh_gpg_keys = ู…ูุงุชูŠุญ SSH / GPG -authorized_oauth2_applications_description = ู„ู‚ุฏ ู…ู†ุญุชูŽ ุฅู…ูƒุงู†ูŠุฉ ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ ุงู„ุดุฎุตูŠ ุนู„ู‰ ููˆุฑุฌูŠูˆ ู„ู‡ุฐู‡ ุงู„ุชุทุจูŠู‚ุงุช ู…ู† ุชุทุจูŠู‚ุงุช ุฎุงุฑุฌูŠุฉ. ุงู„ุฑุฌุงุก ุฅู„ุบุงุก ูˆุตูˆู„ ุงู„ุชุทุจูŠู‚ุงุช ุงู„ุชูŠ ู„ู… ุชุนุฏ ุจุญุงุฌุฉ ุฅู„ูŠู‡ุง. +authorized_oauth2_applications_description = ู„ู‚ุฏ ู…ู†ุญุช ุญู‚ ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ ุงู„ุดุฎุตูŠ ููŠ Forgejo ู„ู‡ุฐู‡ ุงู„ุชุทุจูŠู‚ุงุช ุงู„ุฎุงุฑุฌูŠุฉ. ูŠุฑุฌู‰ ุฅู„ุบุงุก ุงู„ูˆุตูˆู„ ู„ู„ุชุทุจูŠู‚ุงุช ุงู„ุชูŠ ู„ู… ุชุนุฏ ู‚ูŠุฏ ุงู„ุงุณุชุฎุฏุงู…. ssh_key_been_used = ู‡ุฐุง ุงู„ู…ูุชุงุญ ุงู„ู€SSH ุชู… ุฅุถุงูุชู‡ ุจุงู„ูุนู„ ุฅู„ู‰ ู‡ุฐุง ุงู„ุฎุงุฏู…. password_change_disabled = ุงู„ู…ุณุชุฎุฏู…ูŠู† ุบูŠุฑ ุงู„ู…ุญู„ูŠูŠู† ู„ุง ูŠู…ูƒู†ู‡ู… ุชุบูŠูŠุฑ ูƒู„ู…ุฉ ู…ุฑูˆุฑู‡ู… ุนู† ุทุฑูŠู‚ ูˆุงุฌู‡ุฉ ูˆูŠุจ ููˆุฑุฌูŠูˆ. token_state_desc = ู‡ุฐุง ุงู„ุฑู…ุฒ ุงุณุชุฎุฏู… ุฎู„ุงู„ ุขุฎุฑ 7 ุฃูŠุงู… -delete_key = ุฃุฒู„ู‡ +delete_key = ุฅุฒุงู„ุฉ ssh_invalid_token_signature = ู…ูุชุงุญ SSH ุงู„ู…ุฒูˆุฏุŒ ูˆุงู„ุชูˆู‚ูŠุน ูˆุงู„ุฑู…ุฒ ู„ุง ูŠุชุทุงุจู‚ูˆุง ุฃูˆ ุงู„ุฑู…ุฒ ู‚ุฏูŠู…. ssh_token_help = ูŠู…ูƒู†ูƒ ุชูˆู„ูŠุฏ ุชูˆู‚ูŠุน ุจุงุณุชุฎุฏุงู…: gpg_key_verify = ุชุญู‚ู‚ @@ -482,14 +464,13 @@ last_used = ุงุณุชุนู…ู„ ุขุฎุฑ ู…ุฑุฉ ููŠ gpg_token_signature = ุชูˆู‚ูŠุน GPG ู…ุตูุญ add_openid = ุงุถู ุฑุงุจุท OpenID add_gpg_key_success = ุชู… ุฅุถุงูุฉ ู…ูุชุงุญ GPG "%s". -unbind = ุงู„ุบ ุงู„ุฑุจุท verify_ssh_key_success = ุชู… ุงู„ุชุญู‚ู‚ ู…ู† ู…ูุชุงุญ SSH "%s". gpg_token_required = ูŠุฌุจ ุฃู† ุชู‚ุฏู… ุชูˆู‚ูŠุนุงู‹ ู„ู„ุฑู…ุฒ ุงู„ุชุงู„ูŠ ssh_key_verified_long = ุชู… ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ู…ูุชุงุญ ู…ุน ุฑู…ุฒ ูˆูŠู…ูƒู† ุงุณุชุฎุฏุงู…ู‡ ู„ู„ุชุญู‚ู‚ ู…ู† ุงู„ุฅูŠุฏุงุนุงุช ุงู„ู…ุชุทุงุจู‚ุฉ ู…ุน ุฃูŠ ุนู†ุงูˆูŠู† ุงู„ุจุฑูŠุฏ ุงู„ู…ูุนู„ุฉ ู„ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู…. -gpg_key_deletion = ุฃุฒู„ ู…ูุชุงุญ GPG +gpg_key_deletion = ุฅุฒุงู„ุฉ ู…ูุชุงุญ GPG gpg_token_help = ูŠู…ูƒู†ูƒ ุชูˆู„ูŠุฏ ุชูˆู‚ูŠุน ุจุงุณุชุฎุฏุงู…: -ssh_key_deletion = ุฃุฒู„ ู…ูุชุงุญ SSH -ssh_token = ุฑู…ุฒ +ssh_key_deletion = ุฅุฒุงู„ุฉ ู…ูุชุงุญ SSH +ssh_token = ุฑู…ุฒ ูุฑูŠุฏ ssh_disabled = SSH ู…ูุนุทู„ gpg_key_verified_long = ุชู… ุงู„ุชุญู‚ู‚ ู…ู† ุงู„ู…ูุชุงุญ ุจูˆุงุณุทุฉ ุฑู…ุฒ ูˆูŠู…ูƒู† ุงุณุชุฎุฏุงู…ู‡ ู„ู„ุชุญู‚ู‚ ู…ู† ุฅูŠุฏุงุนุงุช ู…ุชุทุงุจู‚ุฉ ู„ุนู†ุงูˆูŠู† ุงู„ุจุฑูŠุฏ ุงู„ู…ูุนู„ุฉ ู„ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู… ุจุงู„ุฅุถุงูุฉ ุฅู„ู‰ ุฃูŠ ู‡ูˆูŠุงุช ู…ุชุทุงุจู‚ุฉ ู„ู‡ุฐุง ุงู„ู…ูุชุงุญ. change_username_redirect_prompt = ุงุณู… ุงู„ู…ุณุชุฎุฏู… ุงู„ู‚ุฏูŠู… ุณูˆู ูŠุนุงุฏ ุชูˆุฌูŠู‡ู‡ ุญุชู‰ ูŠุทุงู„ุจ ุจู‡ ุดุฎุต ุขุฎุฑ. @@ -497,10 +478,9 @@ add_key_success = ุชู… ุฅุถุงูุฉ ู…ูุชุงุญ SSH "%s". key_name = ุงุณู… ุงู„ู…ูุชุงุญ comment_type_group_time_tracking = ุชุชุจุน ุงู„ูˆู‚ุช gpg_invalid_token_signature = ู…ูุชุงุญ GPG ุงู„ู…ุฒูˆุฏุŒ ูˆุงู„ุชูˆู‚ูŠุน ูˆุงู„ุฑู…ุฒ ู„ุง ูŠุชุทุงุจู‚ูˆุง ุฃูˆ ุงู„ุฑู…ุฒ ู‚ุฏูŠู…. -ssh_key_verified = ู…ูุชุงุญ ู…ูุชุญู‚ู‚ ู…ู†ู‡ +ssh_key_verified = ู…ูุชุงุญ ุชู… ุงู„ุชุญู‚ู‚ ู…ู†ู‡ ssh_key_deletion_success = ุชู… ุฅุฒุงู„ุฉ ู…ูุชุงุญ SSH. key_signature_ssh_placeholder = ูŠุจุฏุฃ ุจู€'-----BEGIN SSH SIGNATURE-----' -gpg_token_code = echo "%s" | gpg -a --default-key %s --detach-sig key_id = ู…ุนุฑู ุงู„ู…ูุชุงุญ gpg_key_deletion_success = ุชู… ุฅุฒุงู„ุฉ ู…ูุชุงุญ GPG. verify_gpg_key_success = ุชู… ุงู„ุชุญู‚ู‚ ู…ู† ู…ูุชุงุญ GPG "%s". @@ -511,7 +491,7 @@ gpg_key_deletion_desc = ุฅุฒุงู„ุฉ ู…ูุชุงุญ GPG ูŠูู„ุบูŠ ุชุญู‚ู‚ ุงู„ุฅูŠุฏ permission_read = ุงู„ู‚ุฑุงุกุฉ ssh_token_signature = ุชูˆู‚ูŠุน SSH ู…ุตูุญ ssh_key_deletion_desc = ุฅุฒุงู„ุฉ ู…ูุชุงุญ SSH ูŠู„ุบูŠ ูˆุตูˆู„ู‡ ุฅู„ู‰ ุญุณุงุจูƒ. ุฃูƒู…ู„ุŸ -revoke_key = ุฃุณุญุจ +revoke_key = ุณุญุจ gpg_token = ุฑู…ุฒ gpg_key_matched_identities_long = ุงู„ู‡ูˆูŠุงุช ุงู„ู…ุฏู…ุฌุฉ ููŠ ู‡ุฐุง ุงู„ู…ูุชุงุญ ุชุทุงุจู‚ ุนู†ุงูˆูŠู† ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ุงู„ู…ูุนู„ุฉ ุงู„ุชุงู„ูŠุฉ ู„ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู…. ูˆูŠู…ูƒู† ุงู„ุชุญู‚ู‚ ู…ู† ุตุญุฉ ุงู„ุฅูŠุฏุงุนุงุช ุงู„ู…ุทุงุจู‚ุฉ ู„ู‡ุฐู‡ ุงู„ุนู†ุงูˆูŠู† ุงู„ุจุฑูŠุฏูŠุฉ ู…ุน ู‡ุฐุง ุงู„ู…ูุชุงุญ. key_content = ุงู„ู…ุญุชูˆู‰ @@ -525,9 +505,120 @@ remove_oauth2_application_success = ุฃูุฒูŠู„ ุงู„ุชุทุจูŠู‚. update_oauth2_application_success = ู„ู‚ุฏ ุญุฏู‘ุซุช ุจู†ุฌุงุญ ุชุทุจูŠู‚ OAuth2. oauth2_redirect_uris = ุฑูˆุงุจุท ุฅุนุงุฏุฉ ุงู„ุชูˆุฌูŠู‡. ู†ุฑุฌูˆ ูˆุถุน ูƒู„ ุฑุงุจุท ููŠ ุณุทุฑ ูˆุญุฏู‡. remove_account_link = ุฃุฒู„ ุงู„ุญุณุงุจ ุงู„ู…ุฑุจูˆุท -remove_account_link_success = ุฃูุฒูŠู„ ุงู„ุญุณุงุจ ุงู„ู…ุฑุจูˆุท. - +remove_account_link_success = ุฃูุฒูŠู„ ุงู„ุญุณุงุจ ุงู„ู…ุฑุชุจุท. quota = ูƒูˆุชุง +update_hints = ุญุฏู‘ูุซ ุงู„ุชู„ู…ูŠุญุงุช +keep_activity_private.description = ุณูŠูƒูˆู† ู†ุดุงุทูƒ ุงู„ุนุงู… ู…ุฑุฆูŠู‹ุง ู„ูƒ ูˆู„ู…ุดุฑููŠ ุงู„ู…ุซูŠู„ ูู‚ุท. +manage_ssh_principals = ุฅุฏุงุฑุฉ ู…ุฏุฑุงุก ุดู‡ุงุฏุงุช SSH ุงู„ุฑุฆูŠุณูŠุฉ +pronouns = ุงู„ุถู…ุงุฆุฑ +pronouns_unspecified = ุบูŠุฑ ู…ุญุฏุฏ +ssh_principal_been_used = ุชู…ุช ุฅุถุงูุฉ ู‡ุฐู‡ ุงู„ู‡ูˆูŠุฉ ุงู„ุฑุฆูŠุณูŠุฉ ุฅู„ู‰ ุงู„ุฎุงุฏู… ู…ุณุจู‚ุงู‹. +principal_desc = ู‡ุฐู‡ ุงู„ู‡ููˆูŠุงุช ุงู„ุฑุฆูŠุณูŠุฉ ู„ุดู‡ุงุฏุงุช SSH ู…ุฑุชุจุทุฉ ุจุญุณุงุจูƒ ูˆุชุชูŠุญ ูˆุตูˆู„ุงู‹ ูƒุงู…ู„ุงู‹ ุฅู„ู‰ ู…ุณุชูˆุฏุนุงุชูƒ. +gpg_helper = ุชุญุชุงุฌ ู„ู…ุณุงุนุฏุฉุŸ ุงุทู‘ู„ุน ุนู„ู‰ ุงู„ุฏู„ูŠู„ ุญูˆู„ GPG. +ssh_helper = ู‡ู„ ุชุญุชุงุฌ ู…ุณุงุนุฏุฉุŸ ุงุทู„ุน ุนู„ู‰ ุงู„ุฏู„ูŠู„ ู„ู€ ุฅู†ุดุงุก ู…ูุงุชูŠุญ SSH ุงู„ุฎุงุตุฉ ุจูƒ ุฃูˆ ู„ุญู„ ุงู„ู…ุดูƒู„ุงุช ุงู„ุดุงุฆุนุฉ ุงู„ุชูŠ ู‚ุฏ ุชูˆุงุฌู‡ู‡ุง ุนู†ุฏ ุงุณุชุฎุฏุงู… SSH. +add_new_principal = ุฅุถุงูุฉ ู‡ูˆูŠุฉ ุฑุฆูŠุณูŠุฉ +keep_pronouns_private.description = ุณูŠุคุฏูŠ ุฐู„ูƒ ุฅู„ู‰ ุฅุฎูุงุก ุถู…ุงุฆุฑูƒ ุนู† ุงู„ุฒูˆุงุฑ ุงู„ุฐูŠู† ู„ู… ูŠู‚ูˆู…ูˆุง ุจุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„. +language.description = ุณูŠุชู… ุญูุธ ู‡ุฐู‡ ุงู„ู„ุบุฉ ููŠ ุญุณุงุจูƒ ูˆุงุณุชุฎุฏุงู…ู‡ุง ูƒู„ุบุฉ ุงูุชุฑุงุถูŠุฉ ุจุนุฏ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„. +storage_overview = ู†ุธุฑุฉ ุนุงู…ุฉ ุนู„ู‰ ุงู„ุชุฎุฒูŠู† +hints = ุชู„ู…ูŠุญุงุช +language.title = ุงู„ู„ุบุฉ ุงู„ุงูุชุฑุงุถูŠุฉ +update_hints_success = ุชู… ุชุญุฏูŠุซ ุงู„ุชู„ู…ูŠุญุงุช. +language.localization_project = "ุณุงุนุฏู†ุง ููŠ ุชุฑุฌู…ุฉ Forgejo ุฅู„ู‰ ู„ุบุชูƒ! ุงู„ู…ุฒูŠุฏ ู…ู† ุงู„ู…ุนู„ูˆู…ุงุช. +change_username_redirect_prompt.with_cooldown.one = ุณูŠุตุจุญ ุงุณู… ุงู„ู…ุณุชุฎุฏู… ุงู„ู‚ุฏูŠู… ู…ุชุงุญู‹ุง ู„ู„ุฌู…ูŠุน ุจุนุฏ ูุชุฑุฉ ุชุจุงุทุค ุชุจู„ุบ %[1]d ูŠูˆู…ู‹ุง. ู„ุง ูŠุฒุงู„ ุจุฅู…ูƒุงู†ูƒ ุงุณุชุนุงุฏุฉ ุงุณู… ุงู„ู…ุณุชุฎุฏู… ุงู„ู‚ุฏูŠู… ุฎู„ุงู„ ูุชุฑุฉ ุงู„ุชู‡ุฏุฆุฉ. +additional_repo_units_hint = ุงู‚ุชุฑุงุญ ุชูุนูŠู„ ูˆุญุฏุงุช ุงู„ู…ุณุชูˆุฏุนุงุช ุงู„ุฅุถุงููŠุฉ +additional_repo_units_hint_description = ุงุนุฑุถ ุชู„ู…ูŠุญ "ุชูุนูŠู„ ุงู„ู…ุฒูŠุฏ" ู„ู„ู…ุณุชูˆุฏุนุงุช ุงู„ุชูŠ ู„ู… ูŠุชู… ุชูุนูŠู„ ุฌู…ูŠุน ุงู„ูˆุญุฏุงุช ุงู„ู…ุชุงุญุฉ ุจู‡ุง. +change_password = ุบูŠู‘ุฑ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ +keep_pronouns_private = ุฅุธู‡ุงุฑ ุงู„ุถู…ุงุฆุฑ ู„ู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ุฐูŠู† ุชู…ุช ู…ุตุงุฏู‚ุชู‡ู… ูู‚ุท +change_username_redirect_prompt.with_cooldown.few = ุณูŠุตุจุญ ุงุณู… ุงู„ู…ุณุชุฎุฏู… ุงู„ู‚ุฏูŠู… ู…ุชุงุญู‹ุง ู„ู„ุฌู…ูŠุน ุจุนุฏ ูุชุฑุฉ ุชุจุงุทุค ุชุจู„ุบ ูŠูˆู…ู‹ุง. ู„ุง ูŠุฒุงู„ ุจุฅู…ูƒุงู†ูƒ ุงุณุชุนุงุฏุฉ ุงุณู… ุงู„ู…ุณุชุฎุฏู… ุงู„ู‚ุฏูŠู… ุฎู„ุงู„ ูุชุฑุฉ ุงู„ุชู‡ุฏุฆุฉ. +no_activity = ู„ุง ูŠูˆุฌุฏ ู†ุดุงุท ุญุฏูŠุซ +generate_token_success = ุชู… ุฅู†ุดุงุก ุงู„ุฑู…ุฒ ุงู„ูุฑูŠุฏ ุงู„ุฌุฏูŠุฏ ุงู„ุฎุงุต ุจูƒ. ุงู†ุณุฎู‡ ุงู„ุขู† ู„ุฃู†ู‡ ู„ู† ูŠุธู‡ุฑ ู…ุฑุฉ ุฃุฎุฑู‰. +manage_access_token = ุฑู…ูˆุฒ ุงู„ูˆุตูˆู„ ุงู„ูุฑูŠุฏุฉ +token_name = ุงุณู… ุงู„ุฑู…ุฒ ุงู„ูุฑูŠุฏ +generate_token_name_duplicate = ุงุณู… ุงู„ุชุทุจูŠู‚ %s ู…ูุณุชุฎุฏู… ู…ุณุจู‚ู‹ุง. ูŠูุฑุฌู‰ ุงุณุชุฎุฏุงู… ุงุณู… ุฌุฏูŠุฏ. +regenerate_token = ุฅุนุงุฏุฉ ุงู„ุชูˆู„ูŠุฏ +access_token_regeneration = ุฅุนุงุฏุฉ ุชูˆู„ูŠุฏ ุฑู…ุฒ ูˆุตูˆู„ ูุฑูŠุฏ +permission_write = ู‚ุฑุงุกุฉ ูˆูƒุชุงุจุฉ +delete_token_success = ุชู… ุญุฐู ุงู„ุฑู…ุฒ ุงู„ูุฑูŠุฏ. ู„ู… ูŠุนุฏ ุจุฅู…ูƒุงู† ุงู„ุชุทุจูŠู‚ุงุช ุงู„ุชูŠ ุชุณุชุฎุฏู…ู‡ ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ. +gpg_key_matched_identities = ุงู„ู‡ูˆูŠุงุช ุงู„ู…ุชุทุงุจู‚ุฉ: +tokens_desc = ุชู…ู†ุญ ู‡ุฐู‡ ุงู„ุฑู…ูˆุฒ ุงู„ูุฑูŠุฏุฉ ุฅู…ูƒุงู†ูŠุฉ ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ ุจุงุณุชุฎุฏุงู… ูˆุงุฌู‡ุฉ ุจุฑู…ุฌุฉ ุชุทุจูŠู‚ุงุช Forgejo. +generate_token = ุชูˆู„ูŠุฏ ุฑู…ุฒ ูุฑูŠุฏ +access_token_deletion = ุญุฐู ุฑู…ุฒ ุงู„ูˆุตูˆู„ ุงู„ูุฑูŠุฏ +generate_new_token = ุชูˆู„ูŠุฏ ุฑู…ุฒ ุฌุฏูŠุฏ +access_token_deletion_desc = ุญุฐู ุงู„ุฑู…ุฒ ุงู„ูุฑูŠุฏ ุณูŠุณุญุจ ุตู„ุงุญูŠุฉ ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ ู…ู† ุงู„ุชุทุจูŠู‚ุงุช ุงู„ุชูŠ ุชุณุชุฎุฏู…ู‡. ู„ุง ูŠู…ูƒู† ุงู„ุชุฑุงุฌุน ุนู† ู‡ุฐุง ุงู„ุฅุฌุฑุงุก. ุชุฑูŠุฏ ุงู„ู…ุชุงุจุนุฉุŸ +access_token_desc = ุชู‚ุชุตุฑ ุตู„ุงุญูŠุงุช ุงู„ุฑู…ุฒ ุงู„ู…ุญุฏุฏุฉ ุนู„ู‰ ู…ุณุงุฑุงุช ูˆุงุฌู‡ุฉ ุงู„ุจุฑู…ุฌุฉ (API) ุงู„ู…ู‚ุงุจู„ุฉ ูู‚ุท. ุงุทู„ุน ุนู„ู‰ ุงู„ูˆุซุงุฆู‚ ู„ู…ุฒูŠุฏ ู…ู† ุงู„ู…ุนู„ูˆู…ุงุช. +oauth2_application_remove_description = ุณุชุคุฏูŠ ุฅุฒุงู„ุฉ ุชุทุจูŠู‚ OAuth2 ุฅู„ู‰ ู…ู†ุนู‡ ู…ู† ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุญุณุงุจุงุช ุงู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ู…ุตุฑุญ ู„ู‡ู… ุนู„ู‰ ู‡ุฐุง ุงู„ู…ุซูŠู„. ุงู„ู…ุชุงุจุนุฉุŸ +ssh_principal_deletion = ุฅุฒุงู„ุฉ ุงู„ู‡ูˆูŠุฉ ุงู„ุฑุฆูŠุณูŠุฉ ู„ุดู‡ุงุฏุฉ SSH +at_least_one_permission = ูŠุฌุจ ุนู„ูŠูƒ ุชุญุฏูŠุฏ ุตู„ุงุญูŠุฉ ูˆุงุญุฏุฉ ุนู„ู‰ ุงู„ุฃู‚ู„ ู„ุฅู†ุดุงุก ุฑู…ุฒ ูุฑูŠุฏ +subkeys = ุงู„ู…ูุงุชูŠุญ ุงู„ูุฑุนูŠุฉ +ssh_principal_deletion_desc = ุฅุฒุงู„ุฉ ู‡ูˆูŠุฉ ุฑุฆูŠุณูŠุฉ ู„ุดู‡ุงุฏุฉ SSH ุณุชุณุญุจ ุตู„ุงุญูŠุฉ ูˆุตูˆู„ู‡ุง ุฅู„ู‰ ุญุณุงุจูƒ. ุชุฑูŠุฏ ุงู„ู…ุชุงุจุนุฉุŸ +principal_state_desc = ุงุณุชุฎุฏู…ุช ู‡ุฐู‡ ุงู„ู‡ูˆูŠุฉ ููŠ ุขุฎุฑ 7 ุฃูŠุงู… +ssh_signonly = SSH ู…ุนุทู‘ู„ ุญุงู„ูŠู‹ุงุŒ ู„ุฐุง ุชูุณุชุฎุฏู… ู‡ุฐู‡ ุงู„ู…ูุงุชูŠุญ ูู‚ุท ู„ู„ุชุญู‚ู‚ ู…ู† ุชูˆู‚ูŠุน ุงู„ุฅูŠุฏุงุน. +ssh_externally_managed = ูŠุชู… ุฅุฏุงุฑุฉ ู…ูุชุงุญ SSH ู‡ุฐุง ุฎุงุฑุฌูŠู‹ุง ู„ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู… +access_token_regeneration_desc = ุณูŠุคุฏูŠ ุฅุนุงุฏุฉ ุฅู†ุดุงุก ุฑู…ุฒ ูุฑูŠุฏ ุฅู„ู‰ ุฅุจุทุงู„ ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ ู„ู„ุชุทุจูŠู‚ุงุช ุงู„ุชูŠ ุชุณุชุฎุฏู…ู‡. ู„ุง ูŠู…ูƒู† ุงู„ุชุฑุงุฌุน ุนู† ุฐู„ูƒ. ุงู„ู…ุชุงุจุนุฉุŸ +regenerate_token_success = ุชู… ุฅุนุงุฏุฉ ุฅู†ุดุงุก ุงู„ุฑู…ุฒ ุงู„ุบุฑูŠุฏ. ู„ู… ูŠุนุฏ ุจุฅู…ูƒุงู† ุงู„ุชุทุจูŠู‚ุงุช ุงู„ุชูŠ ุชุณุชุฎุฏู…ู‡ ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ ูˆูŠุฌุจ ุชุญุฏูŠุซู‡ุง ุจุงู„ุฑู…ุฒ ุงู„ุฌุฏูŠุฏ. +repo_and_org_access = ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุงู„ู…ุณุชูˆุฏุน ูˆุงู„ู…ู†ุธู…ุฉ +add_principal_success = ุชู…ุช ุฅุถุงูุฉ ุงู„ู‡ูˆูŠุฉ ุงู„ุฑุฆูŠุณูŠุฉ ู„ุดู‡ุงุฏุฉ "SSH "%s. +ssh_principal_deletion_success = ุชู… ุฅุฒุงู„ุฉ ุงู„ู‡ูˆูŠุฉ. +oauth2_applications_desc = ุชู…ูƒู‘ู† ุชุทุจูŠู‚ุงุช OAuth2 ุชุทุจูŠู‚ุงุช ุงู„ุทุฑู ุงู„ุซุงู„ุซ ู…ู† ู…ุตุงุฏู‚ุฉ ุงู„ู…ุณุชุฎุฏู…ูŠู† ุจุฃู…ุงู† ููŠ ู…ุซูŠู„ Forgejo ู‡ุฐุง. +oauth2_client_secret = ุณุฑ ุงู„ุนู…ูŠู„ +oauth2_regenerate_secret = ุชุฌุฏูŠุฏ ุงู„ุณุฑ +oauth2_regenerate_secret_hint = ูู‚ุฏุช ุณุฑูƒุŸ +oauth2_client_id = ู…ุนุฑู ุงู„ุนู…ูŠู„ +permission_no_access = ู„ุง ูˆุตูˆู„ +remove_oauth2_application_desc = ุณุชุคุฏูŠ ุฅุฒุงู„ุฉ ุชุทุจูŠู‚ OAuth2 ุฅู„ู‰ ุฅุจุทุงู„ ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุฌู…ูŠุน ุฑู…ูˆุฒ ุงู„ูˆุตูˆู„ ุงู„ูุฑูŠุฏุฉ ุงู„ู…ูˆู‚ุนุฉ. ุงู„ู…ุชุงุจุนุฉุŸ +oauth2_confidential_client = ุงู„ุนู…ูŠู„ ุงู„ุณุฑูŠ. ุญุฏุฏ ู„ู„ุชุทุจูŠู‚ุงุช ุงู„ุชูŠ ุชุญุงูุธ ุนู„ู‰ ุงู„ุณุฑูŠุฉุŒ ู…ุซู„ ุชุทุจูŠู‚ุงุช ุงู„ูˆูŠุจ. ู„ุง ุชุญุฏุฏ ู„ู„ุชุทุจูŠู‚ุงุช ุงู„ุฃุตู„ูŠุฉ ุจู…ุง ููŠ ุฐู„ูƒ ุชุทุจูŠู‚ุงุช ุณุทุญ ุงู„ู…ูƒุชุจ ูˆุชุทุจูŠู‚ุงุช ุงู„ุฃุฌู‡ุฒุฉ ุงู„ู…ุญู…ูˆู„ุฉ. +oauth2_client_secret_hint = ู„ู† ูŠุธู‡ุฑ ุงู„ุณุฑ ู…ุฑุฉ ุฃุฎุฑู‰ ุจุนุฏ ู…ุบุงุฏุฑุฉ ู‡ุฐู‡ ุงู„ุตูุญุฉ ุฃูˆ ุชุญุฏูŠุซู‡ุง. ูŠุฑุฌู‰ ุงู„ุชุฃูƒุฏ ู…ู† ุฃู†ูƒ ู‚ู…ุช ุจุญูุธู‡. +oauth2_application_create_description = โ€ชุชู…ู†ุญ ุชุทุจูŠู‚ุงุช OAuth2 ุชุทุจูŠู‚ุงุช ุงู„ุทุฑู ุงู„ุซุงู„ุซ ุญู‚ ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุญุณุงุจุงุช ุงู„ู…ุณุชุฎุฏู…ูŠู† ุนู„ู‰ ู‡ุฐุง ุงู„ู…ุซูŠู„. +oauth2_application_locked = ูŠู‚ูˆู… Forgejo ุจุงู„ุชุณุฌูŠู„ ุงู„ู…ุณุจู‚ ู„ุจุนุถ ุชุทุจูŠู‚ุงุช OAuth2 ุนู†ุฏ ุจุฏุก ุงู„ุชุดุบูŠู„ ุฅุฐุง ุชู… ุชู…ูƒูŠู†ู‡ุง ููŠ ุงู„ุชูƒูˆูŠู†. ู„ู…ู†ุน ุงู„ุณู„ูˆูƒ ุบูŠุฑ ุงู„ู…ุชูˆู‚ุนุŒ ู„ุง ูŠู…ูƒู† ุชุญุฑูŠุฑู‡ุง ุฃูˆ ุฅุฒุงู„ุชู‡ุง. ูŠุฑุฌู‰ ุงู„ุฑุฌูˆุน ุฅู„ู‰ ูˆุซุงุฆู‚ OAuth2 ู„ู…ุฒูŠุฏ ู…ู† ุงู„ู…ุนู„ูˆู…ุงุช. +twofa_recovery_tip = ุฅุฐุง ูู‚ุฏุช ุฌู‡ุงุฒูƒุŒ ุณุชุชู…ูƒู† ู…ู† ุงุณุชุฎุฏุงู… ู…ูุชุงุญ ุงู„ุงุณุชุฑุฏุงุฏ ู„ู„ุงุณุชุฎุฏุงู… ู…ุฑุฉ ูˆุงุญุฏุฉ ู„ุงุณุชุนุงุฏุฉ ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ. +twofa_not_enrolled = ุญุณุงุจูƒ ุบูŠุฑ ู…ุณุฌู‘ู„ ุญุงู„ูŠุงู‹ ููŠ ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ. +twofa_scratch_token_regenerate = ุฅุนุงุฏุฉ ุฅู†ุดุงุก ู…ูุชุงุญ ุงู„ุงุณุชุฑุฏุงุฏ ู„ู„ุงุณุชุฎุฏุงู… ู…ุฑุฉ ูˆุงุญุฏุฉ +regenerate_scratch_token_desc = ุฅุฐุง ูู‚ุฏุช ู…ูุชุงุญ ุงู„ุงุณุชุฑุฏุงุฏ ุงู„ุฎุงุต ุจูƒ ููŠ ุบูŠุฑ ู…ุญู„ู‡ ุฃูˆ ุงุณุชุฎุฏู…ุชู‡ ุจุงู„ูุนู„ ู„ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ุŒ ูŠู…ูƒู†ูƒ ุฅุนุงุฏุฉ ุชุนูŠูŠู†ู‡ ู‡ู†ุง. +twofa_failed_get_secret = ุฅุฎูุงู‚ ููŠ ุงู„ุญุตูˆู„ ุนู„ู‰ ุณุฑ. +manage_account_links_desc = ู‡ุฐู‡ ุงู„ุญุณุงุจุงุช ุงู„ุฎุงุฑุฌูŠุฉ ู…ุฑุชุจุทุฉ ุจุญุณุงุจูƒ ููŠ Forgejo. +revoke_oauth2_grant_description = ุณูŠุคุฏูŠ ุฅุจุทุงู„ ุงู„ูˆุตูˆู„ ู„ู‡ุฐุง ุงู„ุชุทุจูŠู‚ ุงู„ุชุงุจุน ู„ุฌู‡ุฉ ุฎุงุฑุฌูŠุฉ ุฅู„ู‰ ู…ู†ุน ู‡ุฐุง ุงู„ุชุทุจูŠู‚ ู…ู† ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุจูŠุงู†ุงุชูƒ. ุฃู†ุช ู…ุชุฃูƒุฏุŸ +revoke_oauth2_grant_success = ุชู… ุณุญุจ ุตู„ุงุญูŠุฉ ุงู„ูˆุตูˆู„ ุจู†ุฌุงุญ. +webauthn_alternative_tip = ู‚ุฏ ุชุฑุบุจ ููŠ ุชูƒูˆูŠู† ุฃุณู„ูˆุจ ู…ุตุงุฏู‚ุฉ ุฅุถุงููŠ. +webauthn_register_key = ุฅุถุงูุฉ ู…ูุชุงุญ ุชุดููŠุฑ +webauthn_nickname = ุงู„ุงุณู… ุงู„ู…ุณุชุนุงุฑ +manage_account_links = ุงู„ุญุณุงุจุงุช ุงู„ู…ุฑุชุจุทุฉ +revoke_oauth2_grant = ุณุญุจ ุตู„ุงุญูŠุฉ ุงู„ูˆุตูˆู„ +twofa_enroll = ุงู„ุชุณุฌูŠู„ ููŠ ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ +twofa_is_enrolled = ุญุณุงุจูƒ ู…ุณุฌู‘ู„ ุญุงู„ูŠู‹ุง ููŠ ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ. +twofa_scratch_token_regenerated = ู…ูุชุงุญ ุงู„ุงุณุชุฑุฏุงุฏ ู„ู„ุงุณุชุฎุฏุงู… ู…ุฑุฉ ูˆุงุญุฏุฉ ู‡ูˆ %s ุงู„ุขู†. ู‚ู… ุจุชุฎุฒูŠู†ู‡ ููŠ ู…ูƒุงู† ุขู…ู†ุŒ ูู„ู† ูŠุชู… ุนุฑุถู‡ ู…ุฌุฏุฏุงู‹. +twofa_enrolled = ุชู… ุชุณุฌูŠู„ ุญุณุงุจูƒ ุจู†ุฌุงุญ. ู‚ู… ุจุชุฎุฒูŠู† ู…ูุชุงุญ ุงู„ุงุณุชุฑุฏุงุฏ ู„ู„ุงุณุชุฎุฏุงู… ู„ู…ุฑุฉ ูˆุงุญุฏุฉ (%s) ููŠ ู…ูƒุงู† ุขู…ู†ุŒ ูู„ู† ูŠุชู… ุนุฑุถู‡ ู…ุฌุฏุฏุงู‹. +webauthn_desc = ู…ูุงุชูŠุญ ุงู„ุฃู…ุงู† ู‡ูŠ ุฃุฌู‡ุฒุฉ ูุนู„ูŠุฉ ุชุญูˆูŠ ุนู„ู‰ ู…ูุงุชูŠุญ ุชุดููŠุฑ. ูŠู…ูƒู† ุงุณุชุฎุฏุงู…ู‡ุง ู„ู„ุชุญู‚ู‚ ุจุฎุทูˆุชูŠู†. ูŠุฌุจ ุฃู† ุชุฏุนู… ู…ูุงุชูŠุญ ุงู„ุฃู…ุงู† ู…ุนูŠุงุฑ WebAuthn Authenticator. +remove_account_link_desc = ุณุชุคุฏูŠ ุฅุฒุงู„ุฉ ุญุณุงุจ ู…ุฑุชุจุท ุฅู„ู‰ ุฅู„ุบุงุก ูˆุตูˆู„ู‡ ุฅู„ู‰ ุญุณุงุจ Forgejo ุงู„ุฎุงุต ุจูƒ. ุงู„ู…ุชุงุจุนุฉุŸ +email_notifications.onmention = ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ูู‚ุท ุนู†ุฏ ุงู„ุฅุดุงุฑุฉ +email_notifications.andyourown = ูˆุงู„ุฅุดุนุงุฑุงุช ุงู„ุฎุงุตุฉ ุจูƒ +visibility = ุฑุคูŠุฉ ุงู„ู…ุณุชุฎุฏู… +visibility.public_tooltip = ู…ุฑุฆูŠ ู„ู„ุฌู…ูŠุน +visibility.private_tooltip = ู…ุฑุฆูŠ ูู‚ุท ู„ุฃุนุถุงุก ุงู„ู…ุคุณุณุงุช ุงู„ุชูŠ ุงู†ุถู…ู…ุช ุฅู„ูŠู‡ุง +visibility.public = ุนุงู… +delete_account_desc = ู‡ู„ ุฃู†ุช ู…ุชุฃูƒุฏ ู…ู† ุฑุบุจุชูƒ ููŠ ุญุฐู ุญุณุงุจ ุงู„ู…ุณุชุฎุฏู… ู‡ุฐุง ู†ู‡ุงุฆูŠู‹ุงุŸ +user_block_yourself = ู„ุง ูŠู…ูƒู†ูƒ ุญุธุฑ ู†ูุณูƒ. +delete_with_all_comments = ุญุณุงุจูƒ ุฃุตุบุฑ ู…ู† %s. ู„ุชุฌู†ุจ ุงู„ุชุนู„ูŠู‚ุงุช ุงู„ูˆู‡ู…ูŠุฉุŒ ุณูŠุชู… ุญุฐู ุฌู…ูŠุน ุชุนู„ูŠู‚ุงุช ุงู„ู…ุดูƒู„ุฉ/ุงู„ู…ุณุคูˆู„ูŠุฉ ุงู„ุดุฎุตูŠุฉ ู…ุนู‡ุง. +visibility.limited_tooltip = ู…ุฑุฆูŠุฉ ูู‚ุท ู„ู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ุฐูŠู† ู‚ุงู…ูˆุง ุจุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ +visibility.limited = ู…ุญุฏูˆุฏ +visibility.private = ุฎุงุต +quota.applies_to_org = ุชู†ุทุจู‚ ู‚ูˆุงุนุฏ ุงู„ุญุตุต ุงู„ุชุงู„ูŠุฉ ุนู„ู‰ ู‡ุฐู‡ ุงู„ู…ู†ุธู…ุฉ +quota.rule.no_limit = ุบูŠุฑ ู…ุญุฏูˆุฏ +quota.sizes.all = ุงู„ูƒู„ +quota.sizes.repos.all = ุงู„ู…ุณุชูˆุฏุนุงุช +quota.sizes.repos.public = ู…ุณุชูˆุฏุนุงุช ุนุงู…ุฉ +quota.sizes.repos.private = ู…ุณุชูˆุฏุนุงุช ุฎุงุตุฉ +quota.sizes.git.all = ู…ุญุชูˆู‰ Git +quota.sizes.git.lfs = Git LFS +quota.sizes.assets.all = ุงู„ุฃุตูˆู„ +quota.sizes.assets.attachments.all = ุงู„ู…ุฑูู‚ุงุช +quota.sizes.assets.attachments.issues = ุฅุตุฏุงุฑ ุงู„ู…ุฑูู‚ุงุช +quota.sizes.assets.attachments.releases = ุชุญุฑูŠุฑ ุงู„ู…ุฑูู‚ุงุช +quota.sizes.assets.artifacts = ุงู„ุชุญู ุงู„ูู†ูŠุฉ +quota.sizes.assets.packages.all = ุงู„ุญุฒู… +quota.rule.exceeded.helper = ู„ู‚ุฏ ุชุฌุงูˆุฒ ุงู„ุญุฌู… ุงู„ุฅุฌู…ุงู„ูŠ ู„ู„ูƒุงุฆู†ุงุช ู„ู‡ุฐู‡ ุงู„ู‚ุงุนุฏุฉ ุงู„ุญุตุฉ ุงู„ู†ุณุจูŠุฉ. +quota.rule.exceeded = ุชู… ุชุฌุงูˆุฒู‡ +quota.applies_to_user = ุชู†ุทุจู‚ ู‚ูˆุงุนุฏ ุงู„ุญุตุต ุงู„ุชุงู„ูŠุฉ ุนู„ู‰ ุญุณุงุจูƒ +quota.sizes.wiki = ุงู„ู…ูˆุณูˆุนุฉ [org] follow_blocked_user = ู„ุง ูŠู…ูƒู†ูƒ ุฅุชุจุงุน ู‡ุฐู‡ ุงู„ู…ู†ุธู…ุฉ ู„ุฃู† ู‡ุฐู‡ ุงู„ู…ู†ุธู…ุฉ ุญุธุฑุชูƒ. @@ -618,10 +709,8 @@ settings.add_collaborator_blocked_our = ู„ุง ูŠู…ูƒู† ุฅุถุงูุฉ ุงู„ู…ุดุชุฑูƒ commits.browse_further = ุชุตูุญ ุฃูƒุซุฑ settings.ignore_stale_approvals = ุชุฌุงู‡ู„ ุงู„ุทู„ุจุงุช ุงู„ุฑุงูƒุฏุฉ rss.must_be_on_branch = ูŠุฌุจ ุฃู† ุชูƒูˆู† ุนู„ู‰ ูุฑุน ู„ุชุญุตู„ ุนู„ู‰ ู…ูˆุฌุฒ RSS. -clone_in_vscodium = ุฅุณุชู†ุณุฎ ููŠ VSCodium admin.enabled_flags = ุงู„ุนู„ุงู…ุงุช ุงู„ู…ูุนู„ุฉ ู„ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน: settings.new_owner_blocked_doer = ุงู„ู…ุงู„ูƒ ุงู„ุฌุฏูŠุฏ ุญุธุฑูƒ. -issues.comment.blocked_by_user = ู„ุง ูŠู…ูƒู†ูƒ ุฃู† ุชุฑุณู„ ุชุนู„ูŠู‚ุงู‹ ุนู„ู‰ ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ ู„ุฃู†ูƒ ู…ุญุธูˆุฑ ู…ู† ู‚ุจู„ ู…ุงู„ูƒ ุงู„ู…ุณุชูˆุฏุน ุฃูˆ ู…ุฑุณู„ ุงู„ู…ุณุฃู„ุฉ. pulls.nothing_to_compare_have_tag = ุงู„ูุฑุน/ุงู„ูˆุณู… ุงู„ู…ุฎุชุงุฑูŠู† ู…ุชุณุงูˆูŠูŠู†. admin.update_flags = ุชุญุฏูŠุซ ุงู„ุนู„ุงู…ุงุช editor.invalid_commit_mail = ุงู„ุจุฑูŠุฏ ุบูŠุฑ ุตุงู„ุญ ู„ุตู†ุน ุฅูŠุฏุงุน. @@ -631,24 +720,23 @@ settings.add_collaborator_blocked_them = ู„ุง ูŠู…ูƒู† ุฃู† ุฅุถุงูุฉ ุงู„ู…ุด issues.blocked_by_user = ู„ุง ูŠู…ูƒู†ูƒ ุฃู† ุชุฑุณู„ ู…ุณุฃู„ุฉ ููŠ ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน ู„ุฃู†ูƒ ู…ุญุธูˆุฑ ู…ู† ู‚ุจู„ ู…ุงู„ูƒ ุงู„ู…ุณุชูˆุฏุน. mirror_sync = ู…ุชุฒุงู…ู† settings.archive.mirrors_unavailable = ุงู„ู…ุฑุงูŠุง ู„ูŠุณุช ู…ุชุงุญุฉ ุฅุฐุง ุชู… ุฃุฑุดูุฉ ุงู„ู…ุณุชูˆุฏุน. -migrate.forgejo.description = ุชุฑุญูŠู„ ุงู„ู…ุนู„ูˆู…ุงุช ู…ู† ูƒูˆุฏุจูŠุฑุฌ ุฃูˆ ุฎูˆุงุฏู… ููˆุฑุฌูŠูˆ ุงู„ุฃุฎุฑู‰. pulls.blocked_by_user = ู„ุง ูŠู…ูƒู†ูƒ ุฃู† ุชุฑุณู„ ุทู„ุจ ุณุญุจ ููŠ ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน ู„ุฃู†ูƒ ู…ุญุธูˆุฑ ู…ู† ู‚ุจู„ ู…ุงู„ูƒ ุงู„ู…ุณุชูˆุฏุน. migrate.migrating_milestones = ุชุฑุญูŠู„ ุงู„ุฃู‡ุฏุงู migrate_items_milestones = ุฃู‡ุฏุงู repo_size = ุญุฌู… ุงู„ู…ุณุชูˆุฏุน -object_format = ุตูŠุบุฉ ุงู„ูƒุงุฆู†ุงุช +object_format = ุชู†ุณูŠู‚ ุงู„ูƒุงุฆู†ุงุช use_template = ุงุณุชุฎุฏู… ู‡ุฐุง ุงู„ู‚ุงู„ุจ migrate_items_merge_requests = ุทู„ุจุงุช ุงู„ุฏู…ุฌ repo_name = ุงุณู… ุงู„ู…ุณุชูˆุฏุน template = ุงู„ู‚ุงู„ุจ projects.modify = ุนุฏู‘ู„ ุงู„ู…ุดุฑูˆุน -tree_path_not_found_commit = ุงู„ู…ุณุงุฑ %[1]s ุบูŠุฑ ู…ูˆุฌูˆุฏ ููŠ ุงู„ุฅูŠุฏุงุน %[2]s +tree_path_not_found.commit = ุงู„ู…ุณุงุฑ %[1]s ุบูŠุฑ ู…ูˆุฌูˆุฏ ููŠ ุงู„ุฅูŠุฏุงุน %[2]s repo_lang = ุงู„ู„ุบุฉ -fork_repo = ุงุดุชู‚ ุงู„ู…ุณุชูˆุฏุน +fork_repo = ุงุดุชู‚ุงู‚ ุงู„ู…ุณุชูˆุฏุน fork_no_valid_owners = ู„ุง ูŠู…ูƒู† ุงุดุชู‚ุงู‚ ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน ู„ุนุฏู… ูˆุฌูˆุฏ ู…ุงู„ูƒ ุตุงู„ุญ. license = ุงู„ุชุฑุฎูŠุต fork_branch = ุงู„ูุฑุน ุงู„ุฐูŠ ุณูŠูุณุชู†ุณุฎ ุฅู„ู‰ ุงู„ุงุดุชู‚ุงู‚ -template_helper = ุงุฌุนู„ ุงู„ู…ุณุชูˆุฏุน ู‚ุงู„ุจุง +template_helper = ุงุฌุนู„ ุงู„ู…ุณุชูˆุฏุน ู‚ุงู„ุจุงู‹ owner = ุงู„ู…ุงู„ูƒ projects.deletion_success = ุชู… ุญุฐู ุงู„ู…ุดุฑูˆุน. projects.deletion = ุงุญุฐู ุงู„ู…ุดุฑูˆุน @@ -658,23 +746,23 @@ projects.edit = ุนุฏู‘ู„ ุงู„ู…ุดุฑูˆุน template.avatar = ุงู„ุตูˆุฑุฉ ุงู„ุฑู…ุฒูŠุฉ migrate_items_wiki = ุงู„ู…ูˆุณูˆุนุฉ repo_desc = ุงู„ูˆุตู -template_select = ุงุฎุชุฑ ู‚ุงู„ุจุง. +template_select = ุงุฎุชุฑ ู‚ุงู„ุจุง repo_name_helper = ุงู„ุฃุณู…ุงุก ุงู„ุญุณู†ุฉ ู„ู„ู…ุณุชูˆุฏุนุงุช ุชุณุชุฎุฏู… ูƒู„ู…ุงุช ู…ูุชุงุญูŠุฉ ู‚ุตูŠุฑุฉ ูˆุณู‡ู„ุฉ ุงู„ุชุฐูƒุฑ ูˆูุฑูŠุฏุฉ. -default_branch = ุงู„ูุฑุน ุงู„ู…ุจุฏุฆูŠ +default_branch = ุงู„ูุฑุน ุงู„ุงูุชุฑุงุถูŠ all_branches = ูƒู„ ุงู„ูุฑูˆุน -migrate_items_issues = ุงู„ู…ุณุงุฆู„ +migrate_items_issues = ุงู„ุจู„ุงุบุงุช projects.deletion_desc = ุญุฐู ู…ุดุฑูˆุน ูŠุญุฐู ูƒู„ ุงู„ู…ุณุงุฆู„ ุงู„ู…ุฑุชุจุทุฉ ุจู‡. ุฃุชุฑูŠุฏ ุงู„ุงุณุชู…ุฑุงุฑุŸ -repo_desc_helper = ุฃุฏุฎู„ ูˆุตูุง ู‚ุตูŠุฑุง (ุงุฎุชูŠุงุฑูŠุง) -create_repo = ุฃู†ุดุฆ ู…ุณุชูˆุฏุนุง +repo_desc_helper = ุฃุฏุฎู„ ูˆุตูุงู‹ ู‚ุตูŠุฑุงู‹ (ุงุฎุชูŠุงุฑูŠ) +create_repo = ุฅู†ุดุงุก ู…ุณุชูˆุฏุน migrate_items_releases = ุงู„ุฅุตุฏุงุฑุงุช already_forked = ู„ู‚ุฏ ุงุดุชู‚ู‚ุช %s ุจุงู„ูุนู„ license_helper = ุงุฎุชุฑ ู…ู„ู ุชุฑุฎูŠุต. -tree_path_not_found_tag = ุงู„ู…ุณุงุฑ %[1]s ุบูŠุฑ ู…ูˆุฌูˆุฏ ููŠ ุงู„ูˆุณู… %[2]s +tree_path_not_found.tag = ุงู„ู…ุณุงุฑ %[1]s ุบูŠุฑ ู…ูˆุฌูˆุฏ ููŠ ุงู„ูˆุณู… %[2]s object_format_helper = ุตูŠุบุฉ ูƒุงุฆู†ุงุช ุงู„ู…ุณุชูˆุฏุน. ู„ุง ูŠู…ูƒู† ุชุบูŠูŠุฑู‡ุง ุจุนุฏ ุฐู„ูƒ. SHA1 ู‡ูŠ ุงู„ุฃูƒุซุฑ ุชูˆุงูู‚ูŠุฉ. forks = ุงู„ุงุดุชู‚ุงู‚ุงุช migrate_items_pullrequests = ุทู„ุจุงุช ุงู„ุณุญุจ fork_to_different_account = ุงุดุชู‚ ุฅู„ู‰ ุญุณุงุจ ู…ุฎุชู„ู -tree_path_not_found_branch = ุงู„ู…ุณุงุฑ %[1]s ุบูŠุฑ ู…ูˆุฌูˆุฏ ููŠ ุงู„ูุฑุน %[2]s +tree_path_not_found.branch = ุงู„ู…ุณุงุฑ %[1]s ุบูŠุฑ ู…ูˆุฌูˆุฏ ููŠ ุงู„ูุฑุน %[2]s projects.edit_success = ุญุฏู‘ูุซ ุงู„ู…ุดุฑูˆุน "%s". projects.create_success = ุชู… ุฅู†ุดุงุก ุงู„ู…ุดุฑูˆุน "%s". find_file.no_matching = ู„ุง ูŠูˆุฌุฏ ู…ู„ู ู…ุทุงุจู‚ @@ -686,7 +774,7 @@ issues.remove_milestone_at = `ุฃุฒุงู„ ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ ู…ู† ุงู„ู‡ุฏู issues.filter_assginee_no_assignee = ุจู„ุง ู…ูƒู„ู issues.new.no_milestone = ุจู„ุง ู‡ุฏู issues.new.projects = ุงู„ู…ุดุฑูˆุนุงุช -delete_preexisting_label = ุงุญุฐู +delete_preexisting_label = ุญุฐู issues.context.edit = ุนุฏู‘ู„ branch.rename = ุบูŠู‘ุฑ ุงุณู… ุงู„ูุฑุน "%s" issue_labels = ุชุตู†ูŠูุงุช ุงู„ู…ุณุงุฆู„ @@ -695,7 +783,6 @@ issues.remove_project_at = `ุฃุฒุงู„ ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ ู…ู† ุงู„ู…ุดุฑูˆุน < issues.unlock.notice_1 = - ูŠุณุชุทูŠุน ุฃูŠ ู…ุณุชุฎุฏู… ุนู†ุฏุฆุฐู ุฃู† ูŠุนู„ู‘ู‚ ุนู„ู‰ ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ ู…ู† ุฌุฏูŠุฏ. issues.remove_assignee_at = `ุฃู„ุบู‰ ุชูƒู„ูŠูู‡ %s %s` branch.warning_rename_default_branch = ุฅู†ูƒ ุชุบูŠู‘ุฑ ุงุณู… ุงู„ูุฑุน ุงู„ู…ุจุฏุฆูŠ. -trust_model_helper_default = ุงู„ู…ุจุฏุฆูŠ: ุงุฎุชุฑ ู†ู…ูˆุฐุฌ ุงู„ุซู‚ุฉ ุงู„ู…ุจุฏุฆูŠ ู„ู‡ุฐุง ุงู„ู…ูˆู‚ุน tag.create_tag = ุฃู†ุดุฆ ุงู„ูˆุณู… %s release.title_empty = ู„ุง ูŠู…ูƒู† ุชุฑูƒ ุงู„ุนู†ูˆุงู† ูุงุฑุบุง. tag.create_tag_operation = ุฃู†ุดุฆ ูˆุณู…ู‹ุง @@ -782,9 +869,8 @@ milestones.deletion_desc = ุญุฐู ู‡ุฏู ูŠุญุฐูู‡ ู…ู† ูƒู„ ุงู„ู…ุณุงุฆู„ ุง issues.desc = ู†ุธู‘ู… ุฅุจู„ุงุบุงุช ุงู„ุนู„ู„ุŒ ูˆุงู„ู…ู‡ุงู…ุŒ ูˆุงู„ุฃู‡ุฏุงู. issues.choose.ignore_invalid_templates = ุฃูู‡ู…ูู„ุช ุงู„ู‚ูˆุงู„ุจ ุงู„ุชุงู„ูุฉ branch.renamed = ุบููŠู‘ุฑ ุงุณู… ุงู„ูุฑุน %s ุฅู„ู‰ %s. -delete_preexisting = ุงุญุฐู ุงู„ู…ู„ูุงุช ุงู„ู…ูˆุฌูˆุฏุฉ ุณุงุจู‚ุง +delete_preexisting = ุญุฐู ุงู„ู…ู„ูุงุช ุงู„ู…ูˆุฌูˆุฏุฉ ู…ุณุจู‚ุงู‹ branch.included_desc = ู‡ุฐุง ุงู„ูุฑุน ุฌุฒุก ู…ู† ุงู„ูุฑุน ุงู„ู…ุจุฏุฆูŠ -trust_model_helper_collaborator_committer = ู…ุดุชุฑูƒ+ู…ูˆุฏุน: ุซู‚ ุจุชูˆู‚ูŠุนุงุช ุงู„ู…ุดุชุฑูƒูŠู† ุงู„ุชูŠ ุชุทุงุจู‚ ุงู„ู…ูˆุฏุน issues.reopened_at = `ุฃุนุงุฏ ูุชุญ ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ %s` issues.action_milestone = ู‡ุฏู issues.new.assignees = ุงู„ู…ูƒู„ู‘ูŽููˆู† @@ -793,7 +879,6 @@ milestones.filter_sort.most_complete = ุงู„ุฃูƒุซุฑ ุงูƒุชู…ุงู„ุง issues.filter_labels = ุชุตููŠุฉ ุงู„ุชุตู†ูŠูุงุช issues.label.filter_sort.alphabetically = ุฃุจุฌุฏูŠุง settings.confirm_wiki_delete = ุงุญุฐู ุจูŠุงู†ุงุช ุงู„ู…ูˆุณูˆุนุฉ -branch.confirm_rename_branch = ุบูŠู‘ุฑ ุงุณู… ุงู„ูุฑุน issues.action_assignee_no_select = ุจู„ุง ู…ูƒู„ู release.publish = ุงู†ุดุฑ ุงู„ุฅุตุฏุงุฑ issues.lock_duplicate = ู„ุง ูŠู…ูƒู† ุฅู‚ูุงู„ ู…ุณุฃู„ุฉ ู…ู‚ูู„ุฉ. @@ -805,7 +890,6 @@ milestones.filter_sort.least_complete = ุงู„ุฃู‚ู„ ุงูƒุชู…ุงู„ุง branch.create_branch = ุฃู†ุดุฆ ุงู„ูุฑุน %s issues.remove_self_assignment = `ุฃู„ุบู‰ ุชูƒู„ูŠู ู†ูุณู‡ %s` issues.label_edit = ุนุฏู‘ู„ -release.download_count = ุงู„ุชู†ุฒูŠู„ุงุช: %s wiki.welcome = ุฃู‡ู„ุง ุจูƒ ููŠ ุงู„ู…ูˆุณูˆุนุฉ. find_file.go_to_file = ุงู†ุชู‚ู„ ุฅู„ู‰ ู…ู„ู issues.cancel = ุฃู„ุบู @@ -856,7 +940,7 @@ issues.reopen_comment_issue = ุนู„ู‘ู‚ ูˆุฃุนุฏ ูุชุญู‡ุง issues.dependency.add = ุฃุถู ุงุนุชู…ุงุฏูŠุฉโ€ฆ issues.label_deletion_desc = ุญุฐู ุชุตู†ูŠู ูŠุญุฐูู‡ ู…ู† ูƒู„ ุงู„ู…ุณุงุฆู„ุŒ ุฃุชุฑูŠุฏ ุงู„ุงุณุชู…ุฑุงุฑุŸ labels = ุงู„ุชุตู†ูŠูุงุช -delete_preexisting_content = ุงุญุฐู ุงู„ู…ู„ูุงุช ููŠ %s +delete_preexisting_content = ุญุฐู ุงู„ู…ู„ูุงุช ููŠ %s milestones.deletion = ุงุญุฐู ุงู„ู‡ุฏู issues.comment_pull_merged_at = ุฏู…ุฌ ุงู„ุฅูŠุฏุงุน %[1]s ุฅู„ู‰ %[2]s %[3]s issues.new.closed_milestone = ุงู„ุฃู‡ุฏุงู ุงู„ุชุงู…ุฉ @@ -864,7 +948,6 @@ issues.new.milestone = ุงู„ุฃู‡ุฏุงู branch.confirm_create_branch = ุฃู†ุดุฆ ูุฑุนู‹ุง issues.close = ู…ุณุฃู„ุฉ ุชุงู…ุฉ issues.label_modify = ุนุฏู‘ู„ ุงู„ุชุตู†ูŠู -issues.author_helper = ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู… ู‡ูˆ ู…ู†ุดุฆ ุงู„ู…ุณุฃู„ุฉ. issues.delete_branch_at = `ุญูุฐูู ุงู„ูุฑุน %s %s` issues.new.open_projects = ุงู„ู…ุดุฑูˆุนุงุช ุงู„ุญุงู„ูŠุฉ issues.new_label = ุชุตู†ูŠู ุฌุฏูŠุฏ @@ -881,7 +964,6 @@ issues.filter_assginee_no_select = ูƒู„ ุงู„ู…ูƒู„ููŠู† issues.new.no_projects = ุจู„ุง ู…ุดุฑูˆุน issues.new.labels = ุงู„ุชุตู†ูŠูุงุช issues.role.member = ุนุถูˆ -trust_model_helper_collaborator = ู…ุดุชุฑูƒ: ุซู‚ ุจุชูˆู‚ูŠุนุงุช ุงู„ู…ุดุชุฑููƒูŠู† issues.label_archived_filter = ุฃุธู‡ุฑ ุงู„ุชุตู†ูŠูุงุช ุงู„ู…ุคุฑุดูุฉ issues.filter_milestone = ู‡ุฏู issues.remove_label = ุฃุฒุงู„ ุงู„ุชุตู†ูŠู %s %s @@ -898,16 +980,14 @@ issues.dependency.issue_no_dependencies = ุจู„ุง ุงุนุชู…ุงุฏูŠุฉ. issues.filter_sort.mostcomment = ุงู„ุฃูƒุซุฑ ุชุนู„ูŠู‚ุงุช issues.label.filter_sort.reverse_by_size = ุงู„ุฃูƒุจุฑ ุญุฌู…ุง release.message = ุตูู ู‡ุฐุง ุงู„ุฅุตุฏุงุฑ -editor.cancel_lower = ุฃู„ุบู +editor.cancel_lower = ุฅู„ุบุงุก issues.label.filter_sort.reverse_alphabetically = ุฃุจุฌุฏูŠุง ู…ุนูƒูˆุณุง -trust_model_helper = ุงุฎุชุฑ ู†ู…ูˆุฐุฌ ุงู„ุซู‚ุฉ ู„ู„ุชุญู‚ู‚ ู…ู† ุงู„ุชูˆู‚ูŠุนุงุช. ุงู„ุฎูŠุงุฑุงุช ุงู„ู…ุชุงุญุฉ ู‡ูŠ: issues.unlock_error = ู„ุง ูŠู…ูƒู† ููƒ ู‚ูู„ ู…ุณุฃู„ุฉ ุบูŠุฑ ู…ู‚ูู„ุฉ. milestones.filter_sort.most_issues = ุงู„ุฃูƒุซุฑ ู…ุณุงุฆู„ issues.add_project_at = `ุฃุถุงู ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ ุฅู„ู‰ ุงู„ู…ุดุฑูˆุน %s %s` issues.context.reference_issue = ุฃุดุฑ ุฅู„ูŠู‡ุง ููŠ ู…ุณุฃู„ุฉ ุฌุฏูŠุฏุฉ issues.context.quote_reply = ุงู‚ุชุจุณ ููŠ ุฑุฏ issues.role.owner_helper = ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู… ู‡ูˆ ู…ุงู„ูƒ ุงู„ู…ุณุชูˆุฏุน. -trust_model_helper_committer = ู…ูˆุฏุน: ุซู‚ ุจุงู„ุชูˆู‚ูŠุนุงุช ุงู„ู…ูˆุงูู‚ุฉ ู„ู„ู…ูˆุฏุนูŠู† issues.filter_project_none = ุจู„ุง ู…ุดุฑูˆุน issues.lock_no_reason = "ุฃู‚ูู„ู‡ุง ูˆู‚ูŠู‘ุฏ ุงู„ุชุญุงูˆุฑ ู„ู„ู…ุดุชุฑูƒูŠู† ููŠ ุงู„ู…ุณุชูˆุฏุน %s" issue_labels_helper = ุงุฎุชุฑ ุจุงู‚ุฉ ุชุตู†ูŠูุงุช ู„ู„ู…ุณุงุฆู„. @@ -932,7 +1012,7 @@ issues.label_archive = ุฃุฑุดู ุงู„ุชุตู†ูŠู issues.choose.blank_about = ุฃู†ุดุฆ ู…ุณุฃู„ุฉ ู…ู† ุงู„ู‚ุงู„ุจ ุงู„ู…ุจุฏุฆูŠ. issues.filter_poster = ู…ู†ุดุฆ release.delete_release = ุงุญุฐู ุงู„ุฅุตุฏุงุฑ -editor.cancel = ุฃู„ุบู +editor.cancel = ุฅู„ุบุงุก issues.change_title_at = `ุบูŠู‘ุฑ ุงู„ุนู†ูˆุงู† ู…ู† %s ุฅู„ู‰ %s %s` branch.new_branch_from = ุฃู†ุดุฆ ูุฑุนู‹ุง ุฌุฏูŠุฏู‹ุง ู…ู† "%s" issues.due_date_form_edit = "ุนุฏู‘ู„" @@ -944,7 +1024,7 @@ issues.filter_assignee = ู…ูƒู„ู issues.open_title = ุญุงู„ูŠุฉ download_file = ู†ุฒู‘ู„ ุงู„ู…ู„ู issues.attachment.download = `ุงู†ู‚ุฑ ู„ุชู†ุฒูŠู„ "%s"` -download_archive = ู†ุฒู‘ู„ ุงู„ู…ุณุชูˆุฏุน +download_archive = ุชู†ุฒูŠู„ ุงู„ู…ุณุชูˆุฏุน download_tar = ู†ุฒู‘ู„ TAR.GZ download_zip = ู†ุฒู‘ู„ ZIP releases.desc = ุชุชุจุน ุฅุตุฏุงุฑุงุช ุงู„ู…ุดุฑูˆุน ูˆุชู†ุฒูŠู„ุงุชู‡. @@ -957,15 +1037,15 @@ projects.desc = ุฃุฏุฑ ุงู„ู…ุณุงุฆู„ ูˆุทู„ุจุงุช ุงู„ุฏู…ุฌ ููŠ ู„ูˆุญุงุช ู… ambiguous_runes_header = `ูŠุญุชูˆูŠ ู‡ุฐุง ุงู„ู…ู„ู ุนู„ู‰ ู…ุญุงุฑู ูŠูˆู†ูŠูƒูˆุฏ ุบุงู…ุถุฉ` executable_file = ู…ู„ู ุชู†ููŠุฐูŠ settings.webhook.response = ุงู„ุงุณุชุฌุงุจุฉ -editor.delete = ุงุญุฐู %s -file_view_raw = ุฃุธู‡ุฑ ุงู„ู…ุฌุฑุฏ +editor.delete = ุญุฐู %s +file_view_raw = ุฃุธู‡ุฑ ุงู„ุฎุงู… settings.add_webhook = ุฃุถู ุฎุทุงู ูˆูŠุจ settings.slack_channel = ู‚ู†ุงุฉ commits = ุฅูŠุฏุงุนุงุช commit = ุฅูŠุฏุงุน -editor.upload_file = ุงุฑูุน ู…ู„ูุง +editor.upload_file = ุฑูุน ู…ู„ู settings.webhook.headers = ุงู„ุชุฑูˆูŠุณุงุช -org_labels_desc_manage = ุฃุฏุฑู‡ุง +org_labels_desc_manage = ุฅุฏุงุฑุฉ editor.patch = ุทุจู‘ู‚ ุงู„ุฑู‚ุนุฉ editor.must_have_write_access = ูŠุฌุจ ุฃู† ูŠูƒูˆู† ู„ุฏูŠูƒ ุฅู…ูƒุงู†ูŠุฉ ุงู„ูƒุชุงุจุฉ ุญุชู‰ ุชุนุฏู‘ู„ ู‡ุฐุง ุงู„ู…ู„ู ุฃูˆ ุชู‚ุชุฑุญ ุชุนุฏูŠู„ุงุช. template.webhooks = ุฎุทุงุทูŠู ุงู„ูˆูŠุจ @@ -989,12 +1069,12 @@ settings.webhook.payload = ุงู„ู…ุญุชูˆู‰ invisible_runes_header = `ูŠุญุชูˆูŠ ู‡ุฐุง ุงู„ู…ู„ู ุนู„ู‰ ู…ุญุงุฑู ูŠูˆู†ูŠูƒูˆุฏ ุบูŠุฑ ู…ุฑุฆูŠุฉ` editor.filename_help = ุฃุถู ู…ุฌู„ุฏุง ุจูƒุชุงุจุฉ ุงุณู…ู‡ ุซู… ุดุฑุทุฉ ู…ุงุฆู„ุฉ ('/'). ุงุญุฐู ู…ุฌู„ุฏุง ุจุถุบุท ุฒุฑ Backspace ุฃูˆู„ ุดูŠุก ููŠ ุฎุงู†ุฉ ุงู„ุงุณู…. editor.commit_changes = ุฃูˆุฏุน ุงู„ุชุนุฏูŠู„ุงุช -editor.add_file = ุฃุถู ู…ู„ูุง +editor.add_file = ุฅุถุงูุฉ ู…ู„ู settings.githook_name = ุงุณู… ุงู„ุฎุทุงู editor.fork_before_edit = ูŠุฌุจ ุฃู† ุชุดุชู‚ ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน ุญุชู‰ ุชุนุฏู‘ู„ ู‡ุฐุง ุงู„ู…ู„ู ุฃูˆ ุชู‚ุชุฑุญ ุชุนุฏูŠู„ุงุช. projects.description_placeholder = ุงู„ูˆุตู tag = ูˆุณู… -file_raw = ู…ุฌุฑุฏ +file_raw = ุฎุงู… projects.create = ุฃู†ุดุฆ ู…ุดุฑูˆุนุง settings.update_webhook = ุญุฏู‘ุซ ุฎุทุงู ุงู„ูˆูŠุจ editor.push_rejected_no_message = ู„ู‚ุฏ ุฑูุถ ุงู„ุฎุงุฏูˆู… ุงู„ุชุนุฏูŠู„ ุจุบูŠุฑ ุฑุณุงู„ุฉ. ู†ุฑุฌูˆ ู…ุฑุงุฌุนุฉ ุฎุทุงุทูŠู ุฌุช. @@ -1013,7 +1093,7 @@ editor.no_changes_to_show = ู„ุง ุชูˆุฌุฏ ุชุนุฏูŠู„ุงุช ู„ุนุฑุถู‡ุง. settings.webhook.test_delivery = ุงุฎุชุจุงุฑ ุงู„ุชูˆุตูŠู„ commit_graph.hide_pr_refs = ุฃุฎูู ุทู„ุจุงุช ุงู„ุฏู…ุฌ editor.new_file = ู…ู„ู ุฌุฏูŠุฏ -file_view_source = ุฃุธู‡ุฑ ุงู„ู…ุตุฏุฑ +file_view_source = ุนุฑุถ ุงู„ู…ุตุฏุฑ settings.webhook_deletion_success = ุฃุฒูŠู„ ุฎุทุงู ุงู„ูˆูŠุจ. projects.title = ุงู„ุนู†ูˆุงู† settings.slack_domain = ุงู„ู†ุทุงู‚ @@ -1042,11 +1122,11 @@ settings.add_hook_success = ุฃุถูŠู ุฎุทุงู ุงู„ูˆูŠุจ. commit.revert-header = ุฅุฑุฌุงุน: %s editor.file_already_exists = ูŠูˆุฌุฏ ูุนู„ุง ููŠ ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน ู…ู„ู ุจุงุณู… "%s". settings.web_hook_name_matrix = ู…ุชุฑูƒุณ -editor.filename_cannot_be_empty = ู„ุง ูŠู…ูƒู† ุชุฑูƒ ุงุณู… ุงู„ู…ู„ู ูุงุฑุบุง. +editor.filename_cannot_be_empty = ู„ุง ูŠู…ูƒู† ุชุฑูƒ ุงุณู… ุงู„ู…ู„ู ูุงุฑุบุงู‹. editor.add_tmpl = ุฃุถู '<%s>' editor.new_branch_name_desc = ุงุณู… ุงู„ูุฑุน ุงู„ุฌุฏูŠุฏโ€ฆ release = ุฅุตุฏุงุฑ -editor.delete_this_file = ุงุญุฐู ุงู„ู…ู„ู +editor.delete_this_file = ุญุฐู ุงู„ู…ู„ู editor.or = ุฃูˆ editor.push_rejected_summary = ุฑุณุงู„ุฉ ุงู„ุฑูุถ ุงู„ูƒุงู…ู„ุฉ: settings.webhook_deletion = ุฃุฒู„ ุฎุทุงู ุงู„ูˆูŠุจ @@ -1067,7 +1147,6 @@ visibility_helper_forced = ูŠูุฑุถ ู…ุฏูŠุฑ ู…ูˆู‚ุนูƒ ุฃู† ุชูƒูˆู† ุงู„ู…ุณุช wiki.page_content = ู…ุญุชูˆู‰ ุงู„ุตูุญุฉ settings.collaborator_deletion_desc = ุฅุฒุงู„ุฉ ู…ุดุชุฑูƒ ุณุชุจุทู„ ูˆุตูˆู„ู‡ ุฅู„ู‰ ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน. ุฃุชุฑูŠุฏ ุงู„ุงุณุชู…ุฑุงุฑุŸ settings.remove_collaborator_success = ุฃูุฒูŠู„ ุงู„ู…ุดุชุฑูƒ. -settings.search_user_placeholder = ุงุจุญุซ ุนู† ู…ุณุชุฎุฏู…โ€ฆ settings.mirror_settings.pushed_repository = ู…ุณุชูˆุฏุน ู…ุฏููˆุน settings.delete_collaborator = ุฃุฒู„ settings.add_collaborator_success = ุฃูุถูŠู ุงู„ู…ุดุชุฑูƒ. @@ -1216,8 +1295,6 @@ milestones.close = ุชุงู… milestones.new_subheader = ุชุณุงุนุฏูƒ ุงู„ุฃู‡ุฏุงู ููŠ ุชู†ุธูŠู… ุงู„ู…ุณุงุฆู„ ูˆุชุชุจุน ุณูŠุฑู‡ุง. milestones.completeness = %d%% ู…ูƒุชู…ู„ milestones.create = ุฃู†ุดุฆ ู‡ุฏูุง -search.match.tooltip = ู„ุง ุชุฃุช ุฅู„ุง ุจุงู„ู†ุชุงุฆุฌ ุงู„ุชูŠ ุชุทุงุจู‚ ูƒู„ู…ุฉ ุงู„ุจุญุซ ุชู…ุงู…ุง -search.results = ู†ุชุงุฆุฌ ุงู„ุจุญุซ ุนู† "%s" ููŠ %s settings.collaboration = ุงู„ู…ุดุชุฑูƒูˆู† settings.collaboration.admin = ู…ุฏูŠุฑ settings.collaboration.write = ุชุญุฑูŠุฑ @@ -1266,7 +1343,6 @@ settings.transfer_perform = ุฃุชู… ู†ู‚ู„ ุงู„ู…ู„ูƒูŠุฉ settings.transfer_succeed = ุชู… ู†ู‚ู„ ู…ู„ูƒูŠุฉ ุงู„ู…ุณุชูˆุฏุน. pulls.auto_merge_newly_scheduled_comment = `ุฃู…ุฑ ุจุฌุฏูˆู„ุฉ ู‡ุฐุง ุงู„ุทู„ุจ ู„ู„ุฏู…ุฌ ุขู„ูŠุง ุนู†ุฏ ู†ุฌุงุญ ุฌู…ูŠุน ุงู„ูุญูˆุต %[1]s pulls.auto_merge_canceled_schedule_comment = `ุฃู„ุบู‰ ุงู„ุฏู…ุฌ ุงู„ุขู„ูŠ ู„ู‡ุฐุง ุงู„ุทู„ุจ ุนู†ุฏ ู†ุฌุงุญ ุฌู…ูŠุน ุงู„ูุญูˆุต %[1]s -ext_issues.desc = ุฑุงุจุท ู…ุชุชุจุน ุงู„ู…ุณุงุฆู„ ุงู„ุฎุงุฑุฌูŠ. projects.edit_subheader = ุชุณุงุนุฏ ุงู„ู…ุดุฑูˆุนุงุช ููŠ ุชู†ุธูŠู… ุงู„ู…ุณุงุฆู„ ูˆุชุชุจุน ุณูŠุฑู‡ุง. issues.tracker = ู…ุชุชุจุน ุงู„ูˆู‚ุช issues.start_tracking_short = ุดุบู‘ู„ ุงู„ู…ุคู‚ุช @@ -1298,10 +1374,6 @@ settings.trust_model.collaborator.long = ู…ุดุชุฑูƒ: ุซู‚ ุจุชูˆู‚ูŠุนุงุช ุงู„ wiki.file_revision = ู…ุฑุงุฌุนุฉ ุงู„ุตูุญุฉ wiki.wiki_page_revisions = ู…ุฑุงุฌุนุงุช ุตูุญุฉ ุงู„ู…ูˆุณูˆุนุฉ wiki.back_to_wiki = ุนุฏ ุฅู„ู‰ ุตูุญุฉ ุงู„ู…ูˆุณูˆุนุฉ -search.type.tooltip = ู†ูˆุน ุงู„ุจุญุซ -search.fuzzy = ุชู‚ุฑูŠุจูŠ -search.fuzzy.tooltip = ุงุฆุช ุจุงู„ู†ุชุงุฆุฌ ุงู„ู‚ุฑูŠุจุฉ ู…ู† ูƒู„ู…ุฉ ุงู„ุจุญุซ -search.match = ู…ุทุงุจู‚ settings = ุงู„ุฅุนุฏุงุฏุงุช settings.mirror_settings.push_mirror.remote_url = ุฑุงุจุท ู…ุณุชูˆุฏุน ุฌุช ุงู„ุจุนูŠุฏ settings.releases_desc = ูุนู‘ู„ ุงู„ุฅุตุฏุงุฑุงุช ููŠ ุงู„ู…ุณุชูˆุฏุน @@ -1311,12 +1383,10 @@ settings.trust_model = ู†ู…ูˆุฐุฌ ุงู„ุซู‚ุฉ ููŠ ุงู„ุชูˆู‚ูŠุนุงุช settings.trust_model.committer = ู…ูˆุฏูุน settings.trust_model.collaboratorcommitter = ู…ุดุชุฑูƒ+ู…ูˆุฏุน settings.trust_model.collaboratorcommitter.long = ู…ุดุชุฑูƒ+ู…ูˆุฏุน: ุซู‚ ุจุชูˆู‚ูŠุนุงุช ุงู„ู…ุดุชุฑูƒูŠู† ุงู„ุชูŠ ุชุทุงุจู‚ ุงู„ู…ูˆุฏุน -tagged_this = ูˆุณู… ู‡ุฐุง branches = ุงู„ูุฑูˆุน tags = ุงู„ูˆุณูˆู… issues = ุงู„ู…ุณุงุฆู„ pulls = ุทู„ุจุงุช ุงู„ุฏู…ุฌ -project_board = ุงู„ู…ุดุฑูˆุนุงุช packages = ุงู„ุญุฒู… actions = ุงู„ุฅุฌุฑุงุกุงุช released_this = ุฃุตุฏุฑ ู‡ุฐุง @@ -1333,14 +1403,10 @@ issues.ref_issue_from = `ุฃุดุงุฑ ุฅู„ู‰ ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ % issues.ref_pull_from = `ุฃุดุงุฑ ุฅู„ู‰ ู‡ุฐุง ุงู„ุทู„ุจ %[3]s %[1]s` issues.ref_closing_from = `ุฃุดุงุฑ ุฅู„ู‰ ุทู„ุจ ุฏู…ุฌ %[3]s ุณูŠุบู„ู‚ ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ %[1]s` issues.ref_reopening_from = `ุฃุดุงุฑ ุฅู„ู‰ ุทู„ุจ ุฏู…ุฌ %[3]s ุณูŠุนูŠุฏ ูุชุญ ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ %[1]s` -issues.ref_closed_from = `ุฃุบู„ู‚ ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ %[4]s %[2]s` -issues.ref_reopened_from = `ุฃุนุงุฏ ูุชุญ ู‡ุฐู‡ ุงู„ู…ุณุฃู„ุฉ %[4]s %[2]s` issues.reference_issue.body = ุงู„ู…ุญุชูˆู‰ -issues.reference_link = ู„ู„ุฅุดุงุฑุฉ: %s settings.actions_desc = ูุนู‘ู„ ุงู„ุฅุฌุฑุงุกุงุช ููŠ ุงู„ู…ุณุชูˆุฏุน pulls.push_rejected = ุชุนุฐุฑ ุงู„ุฏู…ุฌ: ุชู… ุฑูุถ ุงู„ุฏูุน. ุฑุงุฌุน ุฎุทุงุทูŠู ุฌุช ู„ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน. contributors.contribution_type.additions = ุงู„ุฅุถุงูุงุช -search = ุจุญุซ pulls.require_signed_wont_sign = ูŠุทู„ุจ ุงู„ูุฑุน ุฅูŠุฏุงุนุงุช ู…ูˆู‚ู‘ุนุฉุŒ ู„ูƒู† ู„ู† ูŠูƒูˆู† ู‡ุฐุง ุงู„ุฏู…ุฌ ู…ูˆู‚ู‘ุนู‹ุง pulls.update_branch = ุชุญุฏูŠุซ ุงู„ูุฑุน ุจุงู„ุฏู…ุฌ pulls.update_branch_rebase = ุชุญุฏูŠุซ ุงู„ูุฑุน ุจุฅุนุงุฏุฉ ุงู„ุชุฃุณูŠุณ @@ -1353,30 +1419,223 @@ pulls.fast_forward_only_merge_pull_request = ุชุณุฑูŠุน ูˆุญุณุจ pulls.merge_conflict = ุชุนุฐุฑ ุงู„ุฏู…ุฌ: ุญุฏุซ ู†ุฒุงุน ุฎู„ุงู„ ุงู„ุฏู…ุฌ. ู…ุณุงุนุฏุฉ: ุฌุฑุจ ุทุฑูŠู‚ุฉ ุฃุฎุฑู‰ pulls.rebase_conflict = ุชุนุฐุฑ ุงู„ุฏู…ุฌ: ุญุฏุซ ู†ุฒุงุน ุฎู„ุงู„ ุฅุนุงุฏุฉ ุชุฃุณูŠุณ ุงู„ุฅูŠุฏุงุน: %[1]s. ู…ุณุงุนุฏุฉ: ุฌุฑุจ ุทุฑูŠู‚ุฉ ุฃุฎุฑู‰ pulls.has_merged = ูุดู„: ู„ู‚ุฏ ุชู… ุฏู…ุฌ ู‡ุฐุง ุงู„ุทู„ุจุŒ ูู„ุง ูŠู…ูƒู†ูƒ ุฏู…ุฌู‡ ู…ุฌุฏุฏุง ุฃูˆ ุชุบูŠูŠุฑ ุงู„ูุฑุน ุงู„ู‡ุฏู. +new_repo_helper = ูŠุญุชูˆูŠ ุงู„ู…ุณุชูˆุฏุน ุนู„ู‰ ุฌู…ูŠุน ู…ู„ูุงุช ุงู„ู…ุดุฑูˆุนุŒ ุจู…ุง ููŠ ุฐู„ูƒ ุณุฌู„ ุงู„ุชุนุฏูŠู„ุงุช. ู‡ู„ ุชุณุชุถูŠู ูˆุงุญุฏู‹ุง ุจุงู„ูุนู„ ุนู„ู‰ ู…ู†ุตุฉ ุฃุฎุฑู‰ุŸ ู†ู‚ู„ ุงู„ู…ุณุชูˆุฏุน. +new_from_template = ุงุณุชุฎุฏู… ู‚ุงู„ุจุงู‹ +new_advanced = ุฅุนุฏุงุฏุงุช ู…ู‚ุชุฏู…ุฉ +new_advanced_expand = ุงู†ู‚ุฑ ู„ู„ุชูˆุณุนุฉ +new_from_template_description = ูŠู…ูƒู†ูƒ ุชุญุฏูŠุฏ ู‚ุงู„ุจ ู…ุณุชูˆุฏุน ู…ูˆุฌูˆุฏ ุนู„ู‰ ู‡ุฐุง ุงู„ู…ุซูŠู„ ูˆุชุทุจูŠู‚ ุฅุนุฏุงุฏุงุชู‡. +owner_helper = ู‚ุฏ ู„ุง ุชุธู‡ุฑ ุจุนุถ ู…ู†ุชุฏูŠุงุช ุงู„ู…ุฌู…ูˆุนุฉ ููŠ ุงู„ู‚ุงุฆู…ุฉ ุงู„ู…ู†ุณุฏู„ุฉ ุจุณุจุจ ุงู„ุญุฏ ุงู„ุฃู‚ุตู‰ ู„ุนุฏุฏ ุงู„ู…ุณุชูˆุฏุนุงุช. +fork_from = ุงุดุชู‚ ู…ู† +fork_visibility_helper = ู„ุง ูŠู…ูƒู† ุชุบูŠูŠุฑ ุธู‡ูˆุฑ ุงู„ู…ุณุชูˆุฏุน ุงู„ู…ุดุชู‘ู‚. +generate_repo = ุชูˆู„ูŠุฏ ู„ู…ุณุชูˆุฏุน +readme_helper_desc = ู‡ุฐุง ู‡ูˆ ุงู„ู…ูƒุงู† ุงู„ุฐูŠ ูŠู…ูƒู†ูƒ ููŠู‡ ูƒุชุงุจุฉ ูˆุตู ูƒุงู…ู„ ู„ู…ุดุฑูˆุนูƒ. +repo_gitignore_helper = ุญุฏุฏ ู‚ูˆุงู„ุจ .gitignore +auto_init = ุชู‡ูŠุฆุฉ ุงู„ู…ุณุชูˆุฏุน +default_branch_label = ุงูุชุฑุงุถูŠ +auto_init_description = ุงุจุฏุฃ ุณุฌู„ Git ุจู…ู„ู READMEุŒ ู…ุน ุฅู…ูƒุงู†ูŠุฉ ุฅุถุงูุฉ ู…ู„ูุงุช ุงู„ุฑุฎุตุฉ ูˆ.gitignore ุงุฎุชูŠุงุฑูŠุงู‹. +mirror_use_ssh.text = ุงุณุชุฎุฏู… ู…ุตุงุฏู‚ุฉ SSH +mirror_address = ุงุณุชู†ุณุงุฎ ุนุจุฑ URL +mirror_prune_desc = ุฅุฒุงู„ุฉ ู…ุฑุงุฌุน ุงู„ุชุชุจุน ุนู† ุจูุนุฏ ุงู„ู‚ุฏูŠู…ุฉ +mirror_interval_invalid = ุงู„ูุงุตู„ ุงู„ุฒู…ู†ูŠ ู„ู„ู…ุฑุขุฉ ุบูŠุฑ ุตุงู„ุญ. +readme_helper = ุญุฏุฏ ู‚ุงู„ุจ ู…ู„ู README +generate_from = ุงู„ุชูˆู„ูŠุฏ ู…ู† +mirror_use_ssh.not_available = ุงู„ู…ุตุงุฏู‚ุฉ ุนุจุฑ SSH ุบูŠุฑ ู…ุชุงุญุฉ. +open_with_editor = ุงูุชุญ ุจู€ %s +mirror_prune = ุชู†ู‚ูŠุฉ +visibility_fork_helper = (ุณูŠุคุซุฑ ุชุบูŠูŠุฑ ุฐู„ูƒ ุนู„ู‰ ุฑุคูŠุฉ ุฌู…ูŠุน ุงู„ู…ุดุชู‚ุงุช.) +clone_helper = ุชุญุชุงุฌ ู…ุณุงุนุฏุฉ ููŠ ุงู„ุงุณุชู†ุณุงุฎุŸ ุฒูุฑ ุงู„ู…ุณุงุนุฏุฉ. +mirror_public_key = ู…ูุชุงุญ SSH ุนุงู… +size_format = %[1]s: %[2]s, %[3]s: %[4]s +visibility_description = ูู‚ุท ุงู„ู…ุงู„ูƒ ุฃูˆ ุฃุนุถุงุก ุงู„ู…ุคุณุณุฉ ุฅุฐุง ูƒุงู† ู„ุฏูŠู‡ู… ุญู‚ูˆู‚ุŒ ุณูŠุชู…ูƒู†ูˆู† ู…ู† ุฑุคูŠุชู‡. +license_helper_desc = ุชุญุฏุฏ ุงู„ุฑุฎุตุฉ ู…ุง ูŠู…ูƒู† ู„ู„ุขุฎุฑูŠู† ูุนู„ู‡ ุฃูˆ ุนุฏู… ูุนู„ู‡ ุจุดูŠูุฑุฉ ุจุฑู…ุฌูŠุชูƒ. ู„ุณุช ู…ุชุฃูƒุฏู‹ุง ู…ู† ุงู„ุฑุฎุตุฉ ุงู„ู…ู†ุงุณุจุฉ ู„ู…ุดุฑูˆุนูƒุŸ ุทุงู„ุน ุงุฎุชูŠุงุฑ ุงู„ุฑุฎุตุฉ. +mirror_denied_combination = ู„ุง ูŠู…ูƒู† ุงุณุชุฎุฏุงู… ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ู…ุณุชู†ุฏุฉ ุฅู„ู‰ ุงู„ู…ูุชุงุญ ุงู„ุนุงู… ูˆูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ู…ุนุงู‹. +repo_gitignore_helper_desc = ุงุฎุชุฑ ุงู„ู…ู„ูุงุช ุงู„ุชูŠ ู„ุง ุชุฑูŠุฏ ุชุชุจุนู‡ุง ู…ู† ู‚ุงุฆู…ุฉ ุงู„ู‚ูˆุงู„ุจ ุงู„ุฎุงุตุฉ ุจุงู„ู„ุบุงุช ุงู„ุดุงุฆุนุฉ. ูŠุชู… ุชุถู…ูŠู† ุงู„ู‚ุทุน ุงู„ุฃุซุฑูŠุฉ ุงู„ู†ู…ูˆุฐุฌูŠุฉ ุงู„ุชูŠ ุชู… ุฅู†ุดุงุคู‡ุง ุจูˆุงุณุทุฉ ุฃุฏูˆุงุช ุงู„ุจู†ุงุก ุงู„ุฎุงุตุฉ ุจูƒู„ ู„ุบุฉ ููŠ .gitignore ุจุดูƒู„ ุงูุชุฑุงุถูŠ. +stars = ุงู„ู†ุฌูˆู… +default_branch_helper = ุงู„ูุฑุน ุงู„ุงูุชุฑุงุถูŠ ู‡ูˆ ุงู„ูุฑุน ุงู„ุฃุณุงุณูŠ ู„ุทู„ุจุงุช ุงู„ุณุญุจ ุŒูˆุนู…ู„ูŠุงุช ุฅู„ุฅูŠุฏุงุน. +mirror_use_ssh.helper = ุณูŠู‚ูˆู… Forgejo ุจุนูƒุณ ุงู„ู…ุณุชูˆุฏุน ุนุจุฑ Git ุนุจุฑ SSH ูˆุฅู†ุดุงุก ุฒูˆุฌ ู…ูุงุชูŠุญ ู„ูƒ ุนู†ุฏ ุชุญุฏูŠุฏ ู‡ุฐุง ุงู„ุฎูŠุงุฑ. ูŠุฌุจ ุนู„ูŠูƒ ุงู„ุชุฃูƒุฏ ู…ู† ุฃู† ุงู„ู…ูุชุงุญ ุงู„ุนุงู… ุงู„ุฐูŠ ุชู… ุฅู†ุดุงุคู‡ ู…ุฎูˆู„ ู„ู„ุฏูุน ุฅู„ู‰ ุงู„ู…ุณุชูˆุฏุน ุงู„ูˆุฌู‡ุฉ. ู„ุง ูŠู…ูƒู†ูƒ ุงุณุชุฎุฏุงู… ุงู„ุชุฎูˆูŠู„ ุงู„ู…ุณุชู†ุฏ ุฅู„ู‰ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุนู†ุฏ ุชุญุฏูŠุฏ ู‡ุฐุง ุงู„ุฎูŠุงุฑ. +desc.private = ุฎุงุต +readme = README +mirror_interval = ุงู„ูุงุตู„ ุงู„ุฒู…ู†ูŠ ู„ู„ู…ุฑุขุฉ (ูˆุญุฏุงุช ุงู„ูˆู‚ุช ุงู„ุตุญูŠุญุฉ ู‡ูŠ 'h' ุŒ'm' ุŒ's'). 0 ู„ุชุนุทูŠู„ ุงู„ู…ุฒุงู…ู†ุฉ ุงู„ุฏูˆุฑูŠุฉ. (ุงู„ุญุฏ ุงู„ุฃุฏู†ู‰: %s) +desc.template = ู‚ุงู„ุจ +desc.public = ุนุงู… +visibility = ุงู„ุฑุคูŠุฉ +migrate_options_mirror_helper = ุณูŠูƒูˆู† ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน ู…ุฑุขุฉ +adopt_preexisting_success = ุงู„ู…ู„ูุงุช ุงู„ู…ุนุชู…ุฏุฉ ูˆุงู„ู…ุณุชูˆุฏุน ุงู„ุฐูŠ ุชู… ุฅู†ุดุงุคู‡ ู…ู† %s +archive.title_date = ุชู…ุช ุฃุฑุดูุฉ ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน ุนู„ู‰ %s. ูŠู…ูƒู†ูƒ ุนุฑุถ ุงู„ู…ู„ูุงุช ูˆุงุณุชู†ุณุงุฎู‡ุŒ ู„ูƒู† ู„ุง ูŠู…ูƒู†ูƒ ุฅุฌุฑุงุก ุฃูŠ ุชุบูŠูŠุฑุงุช ุนู„ู‰ ุญุงู„ุชู‡ุŒ ู…ุซู„ ุฏูุน ูˆุฅู†ุดุงุก ุจู„ุงุบุงุช ุฃูˆ ุทู„ุจุงุช ุณุญุจ ุฃูˆ ุชุนู„ูŠู‚ุงุช ุฌุฏูŠุฏุฉ. +migrate_options_lfs_endpoint.label = ู†ู‚ุทุฉ ู†ู‡ุงูŠุฉ LFS +transfer.reject_desc = ุฅู„ุบุงุก ุงู„ู†ู‚ู„ ุฅู„ู‰ โ€%sโ€œ +archive.title = ุชู…ุช ุฃุฑุดูุฉ ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน. ูŠู…ูƒู†ูƒ ุนุฑุถ ุงู„ู…ู„ูุงุช ูˆุงุณุชู†ุณุงุฎู‡ุŒ ู„ูƒู† ู„ุง ูŠู…ูƒู†ูƒ ุฅุฌุฑุงุก ุฃูŠ ุชุบูŠูŠุฑุงุช ุนู„ู‰ ุญุงู„ุชู‡ุŒ ู…ุซู„ ุฏูุน ูˆุฅู†ุดุงุก ุจู„ุงุบุงุช ุฃูˆ ุทู„ุจุงุช ุณุญุจ ุฃูˆ ุชุนู„ูŠู‚ุงุช ุฌุฏูŠุฏุฉ. +stargazers = ุงู„ู…ู…ูŠู‘ูุฒูˆู† ุจู†ุฌู…ุฉ +form.reach_limit_of_creation_n = ู„ู‚ุฏ ูˆุตู„ ุงู„ู…ุงู„ูƒ ุจุงู„ูุนู„ ุฅู„ู‰ ุงู„ุญุฏ ุงู„ุฃู‚ุตู‰ ู„ู„ู…ุณุชูˆุฏุนุงุช %d. +mirror_sync_on_commit = ุงู„ู…ุฒุงู…ู†ุฉ ุนู†ุฏ ุฏูุน ุงู„ุฅูŠุฏุงุนุงุช +mirror_lfs_desc = ุชู†ุดูŠุท ุงู„ู†ุณุฎ ุงู„ู…ุชุทุงุจู‚ ู„ุจูŠุงู†ุงุช LFS. +author_search_tooltip = ุนุฑุถ ูƒุญุฏ ุฃู‚ุตู‰ 30 ู…ุณุชุฎุฏู…ู‹ุง +template.git_content = ู…ุญุชูˆู‰ Git (ุงู„ูุฑุน ุงู„ุงูุชุฑุงุถูŠ) +need_auth = ุงู„ู…ุตุงุฏู‚ุฉ +migrate_options_lfs_endpoint.description.local = ูŠุชู… ุฏุนู… ู…ุณุงุฑ ุงู„ุฎุงุฏู… ุงู„ู…ุญู„ูŠ ุฃูŠุถุงู‹. +template.one_item = ูŠุฌุจ ุนู„ู‰ ุงู„ุฃู‚ู„ ุชุญุฏูŠุฏ ุนู†ุตุฑ ู‚ุงู„ุจ ูˆุงุญุฏ +archive.pull.noreview = ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน ู…ุคุฑุดู. ู„ุง ูŠู…ูƒู†ูƒ ู…ุฑุงุฌุนุฉ ุทู„ุจุงุช ุงู„ุณุญุจ. +migrate.clone_address = ุชุฑุญูŠู„ / ุงุณุชู†ุณุงุฎ ู…ู† ุนู†ูˆุงู† URL +migrate.repo_desc_helper = ุงุชุฑูƒู‡ ูุงุฑุบุงู‹ ู„ุงุณุชูŠุฑุงุฏ ุงู„ูˆุตู ุงู„ู…ูˆุฌูˆุฏ +archive.nocomment = ุงู„ุชุนู„ูŠู‚ ุบูŠุฑ ู…ู…ูƒู† ู„ุฃู† ุงู„ู…ุณุชูˆุฏุน ุชู…ุช ุฃุฑุดูุชู‡. +mirror_address_protocol_invalid = ุนู†ูˆุงู† URL ุงู„ู…ู‚ุฏู… ุบูŠุฑ ุตุงู„ุญ. ูŠู…ูƒู† ุงุณุชุฎุฏุงู… ู…ูˆุงู‚ุน http(s):// ุฃูˆ git:// ูู‚ุท ู„ู„ู†ุณุฎ ุงู„ู…ุชุทุงุจู‚. +mirror_lfs_endpoint = ู†ู‚ุทุฉ ู†ู‡ุงูŠุฉ LFS +mirror_lfs_endpoint_desc = ุณุชุญุงูˆู„ ุงู„ู…ุฒุงู…ู†ุฉ ุงุณุชุฎุฏุงู… ุนู†ูˆุงู† url ุงู„ู…ุณุชู†ุณุฎ ุฅู„ู‰ ุชุญุฏูŠุฏ ุฎุงุฏู… LFS. ูŠู…ูƒู†ูƒ ุฃูŠุถู‹ุง ุชุญุฏูŠุฏ ู†ู‚ุทุฉ ู†ู‡ุงูŠุฉ ู…ุฎุตุตุฉ ุฅุฐุง ูƒุงู†ุช ุจูŠุงู†ุงุช LFS ุงู„ู…ุณุชูˆุฏุน ู…ุฎุฒู†ุฉ ููŠ ู…ูƒุงู† ุขุฎุฑ. +mirror_password_blank_placeholder = (ุจู„ุง ุชุนูŠูŠู†) +delete_preexisting_success = ุงู„ู…ู„ูุงุช ุงู„ู…ุญุฐูˆูุฉ ุบูŠุฑ ุงู„ู…ุนุชู…ุฏุฉ ููŠ %s +blame_prior = ุนุฑุถ ุงู„ู†ู‘ู‚ุฏ ู‚ุจู„ ู‡ุฐุง ุงู„ุชุบูŠูŠุฑ +blame.ignore_revs = ุฌุงุฑูŠ ุชุฌุงู‡ู„ ุงู„ู…ุฑุงุฌุนุงุช ููŠ .git-blame-ignore-revs. ุงู†ู‚ุฑ ู‡ู†ุง ู„ุชุฌุงูˆุฒ ูˆุนุฑุถ ูˆุงุฌู‡ุฉ blame ุงู„ุนุงุฏูŠุฉ. +desc.archived = ู…ุคุฑุดู +template.issue_labels = ูˆุณูˆู… ุงู„ุฅุจู„ุงุบุงุช +sync_fork.branch_behind_one = ู‡ุฐุง ุงู„ูุฑุน ู‡ูˆ %[1]d ุฅูŠุฏุงุน ุฎู„ู %[2]s +sync_fork.button = ู…ุฒุงู…ู†ุฉ +transfer.no_permission_to_reject = ู„ุง ุชู…ู„ูƒ ุงู„ุตู„ุงุญูŠุฉ ู„ุฑูุถ ู‡ุฐุง ุงู„ู†ู‚ู„. +form.string_too_long = ุงู„ุณู„ุณู„ุฉ ุงู„ู…ุญุฏุฏุฉ ุฃุทูˆู„ ู…ู† d ุญุฑูุงู‹. +migrate_options = ุฎูŠุงุฑุงุช ุงู„ุชุฑุญูŠู„ +migrate_options_lfs = ุชุฑุญูŠู„ ู…ู„ูุงุช LFS +migrate.clone_local_path = ุฃูˆ ู…ุณุงุฑ ุฎุงุฏู… ู…ุญู„ูŠ +mirror_password_help = ุชุบูŠูŠุฑ ุงุณู… ุงู„ู…ุณุชุฎุฏู… ู„ู…ุณุญ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุงู„ู…ุฎุฒู†ุฉ. +watchers = ุงู„ู…ุฑุงู‚ุจูˆู† +template.items = ุนู†ุงุตุฑ ุงู„ู‚ุงู„ุจ +template.topics = ุงู„ู…ูˆุงุถูŠุน +migrate_options_lfs_endpoint.placeholder = ุฅุฐุง ุชูุฑูƒุช ูุงุฑุบุฉุŒ ุณูŠุชู… ุงุดุชู‚ุงู‚ ู†ู‚ุทุฉ ุงู„ู†ู‡ุงูŠุฉ ู…ู† ุนู†ูˆุงู† URL ุงู„ู…ุณุชู†ุณุฎ +migrate.clone_address_desc = ุฑุงุจุท HTTP(S) ุฃูˆ Git ู„ุงุณุชู†ุณุงุฎ ู…ุณุชูˆุฏุน ู…ูˆุฌูˆุฏ +migrate.github_token_desc = ูŠู…ูƒู†ูƒ ูˆุถุน ุฑู…ุฒ ูุฑูŠุฏ ุฃูˆ ุฃูƒุซุฑ ู‡ู†ุง ู…ูุตูˆู„ ุจููˆุงุตู„ ู„ุฌุนู„ ุงู„ุชุฑุญูŠู„ ุฃุณุฑุน ู…ู† ุฎู„ุงู„ ุงู„ุชุญุงูŠู„ ุนู„ู‰ ุญุฏ ู…ุนุฏู„ GitHub API. ุชุญุฐูŠุฑ: ู‚ุฏ ูŠุคุฏูŠ ุฅุณุงุกุฉ ุงุณุชุฎุฏุงู… ู‡ุฐู‡ ุงู„ู…ูŠุฒุฉ ุฅู„ู‰ ุงู†ุชู‡ุงูƒ ุณูŠุงุณุฉ ู…ุฒูˆุฏ ุงู„ุฎุฏู…ุฉ ูˆู‚ุฏ ูŠุคุฏูŠ ุฅู„ู‰ ุญุธุฑ ุญุณุงุจูƒ (ุญุณุงุจุงุชูƒ). +transfer.no_permission_to_accept = ู„ุง ุชู…ู„ูƒ ุงู„ุตู„ุงุญูŠุฉ ู„ู‚ุจูˆู„ ู‡ุฐุง ุงู„ู†ู‚ู„. +transfer.reject = ุฑูุถ ุงู„ู†ู‚ู„ +transfer.accept_desc = ุงู„ู†ู‚ู„ ุฅู„ู‰ โ€%sโ€œ +desc.internal = ุฏุงุฎู„ูŠ +summary_card_alt = ุจุทุงู‚ุฉ ู…ู„ุฎุต ุงู„ู…ุณุชูˆุฏุน %s +transfer.accept = ู‚ุจูˆู„ ุงู„ู†ู‚ู„ +blame.ignore_revs.failed = ูุดู„ ุชุฌุงู‡ู„ ุงู„ู…ุฑุงุฌุนุงุช ููŠ .git-blame-ignore-revs. +form.name_pattern_not_allowed = ุงู„ู†ู…ุท โ€%sโ€œ ุบูŠุฑ ู…ุณู…ูˆุญ ุจู‡ ููŠ ุงุณู… ุงู„ู…ุณุชูˆุฏุน. +migrate_options_lfs_endpoint.description = ุณูŠุญุงูˆู„ ุงู„ุชุฑุญูŠู„ ุงุณุชุฎุฏุงู… ู…ุณุงุฑ Git ุงู„ุจุนูŠุฏ (remote) ู„ู€ ุชุญุฏูŠุฏ ุฎุงุฏู… LFS. ูŠู…ูƒู†ูƒ ุฃูŠุถู‹ุง ุชุญุฏูŠุฏ ู†ู‚ุทุฉ ู†ู‡ุงูŠุฉ ู…ุฎุตุตุฉ ุฅุฐุง ูƒุงู†ุช ุจูŠุงู†ุงุช LFS ู„ู„ู…ุณุชูˆุฏุน ู…ุฎุฒู†ุฉ ููŠ ู…ูƒุงู† ุขุฎุฑ. +migrate_items = ุนู†ุงุตุฑ ุงู„ุชุฑุญูŠู„ +adopt_preexisting_content = ุฅู†ุดุงุก ู…ุณุชูˆุฏุน ู…ู† %s +migrate_repo = ุชุฑุญูŠู„ ุงู„ู…ุณุชูˆุฏุน +mirror_password_placeholder = (ู„ู… ูŠุชู… ุชุนุฏูŠู„ู‡) +sync_fork.branch_behind_few = ู‡ุฐุง ุงู„ูุฑุน ู‡ูˆ %[1]d ุฅูŠุฏุงุนุงุช ุฎู„ู %[2]s +mirror_address_desc = ุถุน ุฃูŠ ุจูŠุงู†ุงุช ุงุนุชู…ุงุฏ ู…ุทู„ูˆุจุฉ ููŠ ู‚ุณู… ุงู„ู…ุตุงุฏู‚ุฉ. +mirror_last_synced = ุขุฎุฑ ู…ุฒุงู…ู†ุฉ +mirror_lfs = ุชุฎุฒูŠู† ุงู„ู…ู„ูุงุช ุงู„ูƒุจูŠุฑุฉ (LFS) +stars_remove_warning = ุณูŠุคุฏูŠ ุฐู„ูƒ ุฅู„ู‰ ุฅุฒุงู„ุฉ ุฌู…ูŠุน ุงู„ู†ุฌูˆู… ู…ู† ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน. +reactions_more = ูˆ %d ุฃูƒุซุฑ +template.invalid = ูŠุฌุจ ุชุญุฏูŠุฏ ู…ุณุชูˆุฏุน ุงู„ู‚ูˆุงู„ุจ +adopt_search = ุฃุฏุฎู„ ุงุณู… ุงู„ู…ุณุชุฎุฏู… ู„ู„ุจุญุซ ุนู† ู…ุณุชูˆุฏุนุงุช ุบูŠุฑ ู…ุนุชู…ุฏุฉ... (ุงุชุฑูƒู‡ ูุงุฑุบุงู‹ ู„ู„ุนุซูˆุฑ ุนู„ู‰ ุงู„ูƒู„) +adopt_preexisting = ุงุนุชู…ุงุฏ ุงู„ู…ู„ูุงุช ุงู„ู…ูˆุฌูˆุฏุฉ ู…ุณุจู‚ุงู‹ +language_other = ุฃูุฎุฑู‰ +adopt_preexisting_label = ุงุนุชู…ุงุฏ ุงู„ู…ู„ูุงุช +form.reach_limit_of_creation_1 = ู„ู‚ุฏ ูˆุตู„ ุงู„ู…ุงู„ูƒ ุจุงู„ูุนู„ ุฅู„ู‰ ุงู„ุญุฏ ุงู„ุฃู‚ุตู‰ ู„ู„ู…ุณุชูˆุฏุน %d. +form.name_reserved = ุชู… ุญุฌุฒ ุงุณู… ุงู„ู…ุณุชูˆุฏุน โ€%sโ€œ. +mirror_address_url_invalid = ุนู†ูˆุงู† URL ุงู„ู…ู‚ุฏู… ุบูŠุฑ ุตุงู„ุญ. ุชุฃูƒุฏ ู…ู† ุชุญุฑูŠุฑ ู…ูƒูˆู†ุงุช ุนู†ูˆุงู† URL ุจุดูƒู„ ุตุญูŠุญ. +migrate.migrate = ุงู„ุชุฑุญูŠู„ ู…ู† %s +migrate.migrating_topics = ุชุฑุญูŠู„ ุงู„ู…ูˆุงุถูŠุน +migrate.cancel_migrating_title = ุฅู„ุบุงุก ุงู„ุชุฑุญูŠู„ +generated_from = ุชู… ุชูˆู„ูŠุฏู‡ ู…ู† +star_guest_user = ู‚ู… ุจุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ู„ุฅุนุทุงุก ู†ุฌู…ุฉ ู„ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน. +subscribe.pull.guest.tooltip = ุณุฌู‘ู„ ุงู„ุฏุฎูˆู„ ู„ู„ุงุดุชุฑุงูƒ ููŠ ุทู„ุจ ุงู„ุณุญุจ ู‡ุฐุง. +unwatch = ุฅู„ุบุงุก ุงู„ู…ุดุงู‡ุฏุฉ +quick_guide = ุฏู„ูŠู„ ุณุฑูŠุน +empty_message = ู„ุง ูŠุญุชูˆูŠ ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน ุนู„ู‰ ุฃูŠ ู…ุญุชูˆู‰. +migrate.migrating_labels = ุชุฑุญูŠู„ ุงู„ูˆุณูˆู… +migrate.migrating_releases = ุชุฑุญูŠู„ ุงู„ุฅุตุฏุงุฑุงุช +watch_guest_user = ุณุฌู‘ู„ ุงู„ุฏุฎูˆู„ ู„ู…ุดุงู‡ุฏุฉ ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน. +star = ู†ุฌู…ุฉ +cite_this_repo = ุงู„ุงุณุชุดู‡ุงุฏ ุจู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน +no_desc = ู„ุง ูŠูˆุฌุฏ ูˆุตู +migrated_from_fake =ุชู… ุงู„ุชุฑุญูŠู„ ู…ู† %[1]s +clone_this_repo = ุงุณุชู†ุณุฎ ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน +fork_from_self = ู„ุง ูŠู…ูƒู†ูƒ ุงุดุชู‚ุงู‚ ู…ุณุชูˆุฏุน ุชู…ู„ูƒู‡. +fork_guest_user = ุณุฌู‘ู„ ุงู„ุฏุฎูˆู„ ู„ุงุดุชู‚ุงู‚ ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน. +subscribe.issue.guest.tooltip = ุณุฌู‘ู„ ุงู„ุฏุฎูˆู„ ู„ู„ุงุดุชุฑุงูƒ ููŠ ู‡ุฐุง ุงู„ุจู„ุงุบ. +more_operations = ุงู„ู…ุฒูŠุฏ ู…ู† ุงู„ุนู…ู„ูŠุงุช +push_exist_repo = ุฏูุน ู…ุณุชูˆุฏุน ู…ูˆุฌูˆุฏ ู…ู† ู…ูˆุฌู‘ู‡ ุงู„ุฃูˆุงู…ุฑ +broken_message = ู„ุง ูŠู…ูƒู† ู‚ุฑุงุกุฉ ุจูŠุงู†ุงุช Git ุงู„ุชูŠ ูŠุณุชู†ุฏ ุฅู„ูŠู‡ุง ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน. ุงุชุตู„ ุจู…ุณุคูˆู„ ู‡ุฐุง ุงู„ู…ุซูŠู„ ุฃูˆ ุงุญุฐู ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน. +watch = ุดุงู‡ุฏ +migrate.migrating_git = ุชุฑุญูŠู„ ุจูŠุงู†ุงุช Git +migrate.cancel_migrating_confirm = ุชุฑูŠุฏ ุฅู„ุบุงุก ุนู…ู„ูŠุฉ ุงู„ุชุฑุญูŠู„ ู‡ุฐู‡ุŸ +migrate.permission_denied_blocked = ู„ุง ูŠู…ูƒู†ูƒ ุงู„ุงุณุชูŠุฑุงุฏ ู…ู† ู…ุถูŠููŠู† ุบูŠุฑ ู…ุณู…ูˆุญ ุจู‡ู…ุŒ ูŠูุฑุฌู‰ ุงู„ุทู„ุจ ู…ู† ุงู„ู…ุณุคูˆู„ ุงู„ุชุญู‚ู‚ ู…ู† ุฅุนุฏุงุฏุงุช ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS. +migrate.migrating = ุงู„ุชุฑุญูŠู„ ู…ู† %s โ€ฆ +migrate.migrating_failed.error = ุฃุฎูู‚ ุงู„ุชุฑุญูŠู„: %s +migrate.invalid_local_path = ุงู„ู…ุณุงุฑ ุงู„ู…ุญู„ูŠ ุบูŠุฑ ุตุงู„ุญ. ูู‡ูˆ ุบูŠุฑ ู…ูˆุฌูˆุฏ ุฃูˆ ู„ูŠุณ ู…ุฌู„ุฏุงู‹. +migrate.permission_denied = ู„ุง ูŠูุณู…ุญ ู„ูƒ ุจุงุณุชูŠุฑุงุฏ ุงู„ู…ุณุชูˆุฏุนุงุช ุงู„ู…ุญู„ูŠุฉ. +migrate.failed = ุฃุฎูู‚ ุงู„ุชุฑุญูŠู„: %v +migrate.migrate_items_options = ุฑู…ุฒ ุงู„ูˆุตูˆู„ ุงู„ูุฑูŠุฏ ู…ุทู„ูˆุจ ู„ุชุฑุญูŠู„ ุงู„ุนู†ุงุตุฑ ุงู„ุฅุถุงููŠุฉ +migrated_from = ุชู… ุงู„ุชุฑุญูŠู„ ู…ู† %[2]s +mirror_from = ู…ุฑุขุฉ ู„ู€ +forked_from = ู…ุดุชู‚ู‘ ู…ู† +create_new_repo_command = ุฅู†ุดุงุก ู…ุณุชูˆุฏุน ุฌุฏูŠุฏ ุนู„ู‰ ู…ูˆุฌู‘ู‡ ุงู„ุฃูˆุงู…ุฑ +fork = ุงุดุชู‚ุงู‚ +migrate.migrating_failed_no_addr = ุฃุฎูู‚ ุงู„ุชุฑุญูŠู„. +unstar = ุฅุฒุงู„ุฉ ุงู„ู†ุฌู…ุฉ +migrate.migrating_issues = ุชุฑุญูŠู„ ุงู„ุจู„ุงุบุงุช +migrate.migrating_pulls = ุชุฑุญูŠู„ ุทู„ุจุงุช ุงู„ุณุญุจ +code = ุงู„ูƒูˆุฏ +migrate.invalid_lfs_endpoint = ู†ู‚ุทุฉ ู†ู‡ุงูŠุฉ LFS ุบูŠุฑ ุตุงู„ุญุฉ. +migrate.migrating_failed = ูุดู„ ุงู„ุชุฑุญูŠู„ ู…ู† %s. +branch = ูุฑุน +template.git_hooks_tooltip = ูŠุชุนุฐุฑ ุนู„ูŠูƒ ุญุงู„ูŠู‹ุง ุชุนุฏูŠู„ ุฃูˆ ุฅุฒุงู„ุฉ ุฎุทุงูุงุช Git ุจู…ุฌุฑุฏ ุฅุถุงูุชู‡ุง. ุญุฏุฏ ู‡ุฐุง ูู‚ุท ุฅุฐุง ูƒู†ุช ุชุซู‚ ุจู…ุณุชูˆุฏุน ุงู„ู‚ุงู„ุจ. +code.desc = ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุงู„ุดูŠูุฑุฉ ุงู„ู…ุตุฏุฑูŠุฉ ูˆุงู„ู…ู„ูุงุช ูˆุงู„ุงู„ุชุฒุงู…ุงุช ูˆุงู„ูุฑูˆุน. +tree = ุดุฌุฑุฉ +clear_ref = 'ู…ุณุญ ุงู„ู…ุฑุฌุน ุงู„ุญุงู„ูŠ' +project = ุงู„ู…ุดุงุฑูŠูŠุน +filter_branch_and_tag = ุชุตููŠุฉ ุงู„ูุฑุน ุฃูˆ ุงู„ุนู„ุงู…ุฉ +find_tag = ุงู„ุจุญุซ ุนู† ุนู„ุงู…ุฉ +commit_graph = ุงู„ุฑุณู… ุงู„ุจูŠุงู†ูŠ ู„ู„ุฅูŠุฏุงุน +editor.edit_this_file = ุนุฏู„ ุงู„ู…ู„ู +editor.signoff_desc = ุฃุถู ู…ู‚ุทูˆุฑุฉ ู…ูˆู‚ู‘ุนุฉ ู…ู† ู‚ูุจู„ ุงู„ู…ูุฌุฑู‘ุฏ ููŠ ู†ู‡ุงูŠุฉ ุฑุณุงู„ุฉ ุณุฌู„ ุงู„ุฏุฎูˆู„. +n_release_one = %s ุฅุตุฏุงุฑ +symbolic_link = ุฑุงุจุท ุฑู…ุฒูŠ +editor.cannot_edit_lfs_files = ู„ุง ูŠู…ูƒู† ุชุญุฑูŠุฑ ู…ู„ูุงุช LFS ููŠ ูˆุงุฌู‡ุฉ ุงู„ูˆูŠุจ. +editor.this_file_locked = ุงู„ู…ู„ู ู…ู‚ูู„ +n_commit_few = %s ุฅูŠุฏุงุนุงุช +editor.file_delete_success = ุชู… ุญุฐู ุงู„ู…ู„ู "%s". +editor.edit_file = ุนุฏู‘ู„ ุงู„ู…ู„ู +commit.contained_in_default_branch = ู‡ุฐุง ุงู„ุฅูŠุฏุงุน ุฌุฒุก ู…ู† ุงู„ูุฑุน ุงู„ุงูุชุฑุงุถูŠ +line = ุณุทุฑ +lines = ุฃุณุทุฑ +normal_view = ุนุฑุถ ุนุงุฏูŠ +n_branch_one = %s ูุฑุน +n_commit_one = %s ุฅูŠุฏุงุน +view_git_blame = ุนุฑุถ ู…ุณุคูˆู„ ุชุนุฏูŠู„ git +editor.add_tmpl.filename = ุงุณู… ุงู„ู…ู„ู +editor.name_your_file = ุงุณู… ู…ู„ููƒโ€ฆ +editor.propose_file_change = ุงู‚ุชุฑุงุญ ุชุบูŠูŠุฑ ุงู„ู…ู„ู +editor.new_branch_name = ุชุณู…ูŠุฉ ุงู„ูุฑุน ุงู„ุฌุฏูŠุฏ ู„ู‡ุฐุง ุงู„ุฅูŠุฏุงุน +from_comment = (ุชุนู„ูŠู‚) +no_eol.text = ู„ุง EOL +no_eol.tooltip = ู‡ุฐุง ุงู„ู…ู„ู ู„ุง ูŠุญุชูˆูŠ ุนู„ู‰ ู†ู‡ุงูŠุฉ ู„ุฎุท ุงู„ุดุฎุตูŠุฉ. +stored_lfs = ู…ุฎุฒู† ู…ุน Git LFS +commit.contained_in = ู‡ุฐุง ุงู„ุฅูŠุฏุงุน ู…ูˆุฌูˆุฏ ููŠ: +file_view_rendered = ุนุฑุถ ุงู„ู…ูุฎุฑุฌ ุงู„ู†ู‡ุงุฆูŠ +n_branch_few = %s ูุฑูˆุน +n_tag_one = %s ุนู„ุงู…ุฉ +n_tag_few = โ€ช%s ุนู„ุงู…ุงุช +n_release_few = %s ุฅุตุฏุงุฑุงุช +file.title = %s ุนู†ุฏ %s +vendored = ู…ุถู…ู† +file_follow = ู…ุชุงุจุนุฉ ุงู„ุฑูˆุงุจุท ุงู„ุฑู…ุฒูŠุฉ [mail] admin.new_user.text = ู…ู† ูุถู„ูƒ ุงุถุบุท ู‡ู†ุง ู„ุฅุฏุงุฑุฉ ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู… ู…ู† ู„ูˆุญุฉ ุงู„ุฅุฏุงุฑุฉ. admin.new_user.subject = ู…ุณุชุฎุฏู… ุฌุฏูŠุฏ: %s ุณุฌู„ ุญุงู„ุงู‹ admin.new_user.user_info = ู…ุนู„ูˆู…ุงุช ุงู„ู…ุณุชุฎุฏู… activate_account.text_1 = ุฃู‡ู„ุง ูŠุง %[1]sุŒ ุดูƒุฑุง ู„ูƒ ู„ู„ุชุณุฌูŠู„ ููŠ %[2]s! -register_notify = ุฃู‡ู„ุง ุจูƒ ููŠ ููˆุฑุฌูŠูˆ +register_notify = ุฃู‡ู„ุง ุจูƒ ููŠ %s activate_account = ู†ุฑุฌูˆ ุชูุนูŠู„ ุญุณุงุจูƒ -activate_account.title = ูŠุง %sุŒ ู†ุฑุฌูˆ ู…ู†ูƒ ุชูุนูŠู„ ุญุณุงุจูƒ issue.x_mentioned_you = ุฐูƒุฑูƒ @%s: -register_notify.title = ู…ุฑุญุจุง ุจูƒ ูŠุง %[1]s ููŠ %[2]s issue.in_tree_path = ููŠ %s: -register_notify.text_3 = ุฅุฐุง ุฃูู†ุดุฆ ู‡ุฐุง ุงู„ุญุณุงุจ ู„ูƒุŒ ูู†ุฑุฌูˆ ุฃูˆู„ุง ุถุจุท ูƒู„ู…ุฉ ู…ุฑูˆุฑูƒ. -register_notify.text_2 = ูŠู…ูƒู†ูƒ ุงู„ุขู† ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุจุงุณู… ุงู„ู…ุณุชุฎุฏู…: %s. -reset_password.title = ูŠุง %sุŒ ู„ู‚ุฏ ุทู„ุจุช ุงุณุชุนุงุฏุฉ ุญุณุงุจูƒ +register_notify.text_3 = ุฅุฐุง ู‚ุงู… ุดุฎุต ุขุฎุฑ ุจุฅู†ุดุงุก ู‡ุฐุง ุงู„ุญุณุงุจ ู„ูƒุŒ ูุณุชุญุชุงุฌ ุฃูˆู„ุงู‹ ุฅู„ู‰ ุถุจุท ูƒู„ู…ุฉ ู…ุฑูˆุฑูƒ. +register_notify.text_2 = ูŠู…ูƒู†ูƒ ุงู„ุขู† ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ ุจุงุณู… ุงู„ู…ุณุชุฎุฏู…: %s repo.transfer.to_you = ุฃู†ุช reset_password = ุงุณุชุนุงุฏุฉ ุญุณุงุจูƒ -repo.collaborator.added.subject = %s ุฃุถุงููƒ ุฅู„ู‰ %s +repo.collaborator.added.subject = %s ุฃุถุงููƒ ุฅู„ู‰ %s ูƒู…ุดุชุฑููƒ team_invite.subject = ู„ู‚ุฏ ุฏุนุงูƒ %[1]s ู„ู„ุงู†ุถู…ุงู… ุฅู„ู‰ ู…ู†ุธู…ุฉ %[2]s team_invite.text_2 = ู†ุฑุฌูˆ ุงู„ุถุบุท ุนู„ู‰ ุงู„ุฑุงุจุท ุงู„ุชุงู„ูŠ ู„ู„ุงู†ุถู…ุงู… ุฅู„ู‰ ุงู„ูุฑูŠู‚: team_invite.text_1 = ู„ู‚ุฏ ุฏุนุงูƒ %[1]s ู„ู„ุงู†ุถู…ุงู… ุฅู„ู‰ ูุฑูŠู‚ %[2]s ููŠ ู…ู†ุธู…ุฉ %[3]s. -repo.collaborator.added.text = ู„ู‚ุฏ ุฌูุนู„ุช ู…ุดุชุฑููƒุง ููŠ ู…ุณุชูˆุฏุน: +repo.collaborator.added.text = ู„ู‚ุฏ ุฌูุนู„ุช ู…ุดุชุฑููƒุง ุฅู„ู‰ ู…ุณุชูˆุฏุน: reply = ุฃูˆ ุฑุฏ ุนู„ู‰ ู‡ุฐุง ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ู…ุจุงุดุฑุฉ -link_not_working_do_paste = ู„ุง ุชุนู…ู„ุŸ ุญุงูˆู„ ุฃู† ุงู„ู†ุณุฎ ูˆุงู„ู„ุตู‚ ุฅู„ู‰ ู…ุชุตูุญูƒ. +link_not_working_do_paste = ู‡ู„ ุงู„ุฑุงุจุท ู„ุง ูŠุนู…ู„ุŸ ุฌุฑู‘ุจ ู†ุณุฎู‡ ูˆู„ุตู‚ู‡ ููŠ ุดุฑูŠุท ุนู†ูˆุงู† ุงู„ู…ุชุตูุญ ู„ุฏูŠูƒ. register_success = ู†ุฌุญ ุงู„ุชุณุฌูŠู„ view_it_on = ุงุนุฑุถู‡ ุนู„ู‰ %s activate_account.text_2 = ูŠุฑุฌู‰ ุงู„ู†ู‚ุฑ ุนู„ู‰ ุงู„ุฑุงุจุท ุงู„ุชุงู„ูŠ ู„ุชูุนูŠู„ ุญุณุงุจูƒ ููŠ %s: @@ -1387,7 +1646,6 @@ activate_email.text = ูŠุฑุฌู‰ ุงู„ุถุบุท ุนู„ู‰ ุงู„ุฑุงุจุท ุงู„ุขุชูŠ ู„ุชุฃ reset_password.text = ูŠุฑุฌู‰ ุงู„ุถุบุท ุนู„ู‰ ุงู„ุฑุงุจุท ุงู„ุขุชูŠ ู„ุงุณุชุนุงุฏุฉ ุงู„ุญุณุงุจ ููŠ ุฎู„ุงู„ %s: release.downloads = ุงู„ุชู†ุฒูŠู„ุงุช: activate_email = ุฃูƒุฏ ุนู†ูˆุงู† ุจุฑูŠุฏูƒ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ -activate_email.title = %sุŒ ูŠุฑุฌู‰ ุชุฃูƒูŠุฏ ุนู†ูˆุงู† ุจุฑูŠุฏูƒ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ release.note = ู…ู„ุงุญุธุฉ: issue.action.close = @%[1]s ุฃุบู„ู‚ #%[2]d. issue.action.merge = @%[1]s ุฏู…ุฌ #%[2]d ู…ุน %[3]s. @@ -1406,35 +1664,32 @@ issue.action.new = @%[1]s ุงู†ุดุฃ #%[2]d. issue_assigned.issue = @%[1]s ุนูŠู‘ู†ูƒ ุฅู„ู‰ ู…ุณุฃู„ุฉ %[2]s ููŠ ู…ุณุชูˆุฏุน %[3]s. issue.action.push_n = @%[1]s ุฏูุน %[3]d ุฅูŠุฏุงุนุงุช ุฅู„ู‰ %[2]s release.new.subject = ุฃูุตุฏุฑ %s ููŠ %s -repo.transfer.subject_to_you = %s ูŠูˆุฏ ู†ู‚ู„ ู…ู„ูƒูŠุฉ "%s" ุฅู„ูŠูƒ -repo.transfer.subject_to = %s ูŠูˆุฏ ู†ู‚ู„ ู…ู„ูƒูŠุฉ "%s" ุฅู„ู‰ %s +repo.transfer.subject_to_you = %s ูŠุฑูŠุฏ ู†ู‚ู„ ู…ู„ูƒูŠุฉ ู…ุณุชูˆุฏุน "%s" ุฅู„ูŠูƒ +repo.transfer.subject_to = %s ูŠุฑูŠุฏ ู†ู‚ู„ ู…ู„ูƒูŠุฉ ู…ุณุชูˆุฏุน "%s" ุฅู„ู‰ %s issue.action.ready_for_review = @%[1]s ุนู„ู‘ู… ู‡ุฐุง ุงู„ุทู„ุจ ู„ู„ุณุญุจ ูƒุฌุงู‡ุฒ ู„ู„ู…ุฑุงุฌุนุฉ. issue_assigned.pull = @%[1]s ุนูŠู‘ู†ูƒ ุฅู„ู‰ ุทู„ุจ ุณุญุจ %[2]s ููŠ ู…ุณุชูˆุฏุน %[3]s. issue.action.review_dismissed = @%[1]s ุฃุณุชุจุนุฏ ุขุฎุฑ ู…ุฑุงุฌุนุฉ ู…ู† %[2]s ู„ู‡ุฐุง ุงู„ุทู„ุจ ู„ู„ุณุญุจ. - password_change.subject = ุชู… ุชุบูŠูŠุฑ ูƒู„ู…ุฉ ู…ุฑูˆุฑูƒ -password_change.text_1 = ุชู… ุชุบูŠูŠุฑ ูƒู„ู…ุฉ ู…ุฑูˆุฑ ุญุณุงุจูƒ ู„ู„ุชูˆ. -primary_mail_change.subject = ุชู… ุชุบูŠูŠุฑ ุงู„ุจุฑูŠุฏ ุงู„ุฃุณุงุณูŠ ุงู„ุฎุงุต ุจูƒ -primary_mail_change.text_1 = ุชู… ุชุบูŠูŠุฑ ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ุงู„ุฃุณุงุณูŠ ู„ุญุณุงุจูƒ ุฅู„ู‰ %[1]s. ู‡ุฐุง ูŠุนู†ูŠ ุฃู† ุนู†ูˆุงู† ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ู‡ุฐุง ู„ู† ูŠุชู„ู‚ู‰ ุฅุดุนุงุฑุงุช ุงู„ุจุฑูŠุฏ ู„ุญุณุงุจูƒ ุจุนุฏ ุงู„ุขู†. totp_disabled.subject = ุชู… ุชุนุทูŠู„ TOTP totp_disabled.text_1 = ุชู… ุชุนุทูŠู„ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ู„ู…ุฑุฉ ูˆุงุญุฏุฉ ุงู„ู…ุณุชู†ุฏุฉ ุฅู„ู‰ ุงู„ูˆู‚ุช (TOTP) ุนู„ู‰ ุญุณุงุจูƒ ู„ู„ุชูˆ. -totp_disabled.no_2fa = ู„ู… ุชุนุฏ ู‡ู†ุงูƒ ุทุฑู‚ ุฃูุฎุฑู‰ ู„ู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ (2FA) ู‚ูŠุฏ ุงู„ุชู‡ูŠุฆุฉ ุนุฏ ุงู„ุขู† ุŒ ุฃูŠ ุฃู†ู‡ ู„ู… ูŠุนุฏ ู…ู† ุงู„ุถุฑูˆุฑูŠ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ ุจุงุณุชุฎุฏุงู… ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ (2FA). +totp_enrolled.text_1.has_webauthn = ู„ู‚ุฏ ู‚ู…ุช ู„ู„ุชูˆ ุจุชู…ูƒูŠู† TOTP ู„ุญุณุงุจูƒ. ู‡ุฐุง ูŠุนู†ูŠ ุฃู†ู‡ ุจุงู„ู†ุณุจุฉ ู„ุฌู…ูŠุน ุนู…ู„ูŠุงุช ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุงู„ู…ุณุชู‚ุจู„ูŠุฉ ุฅู„ู‰ ุญุณุงุจูƒุŒ ูŠู…ูƒู†ูƒ ุงุณุชุฎุฏุงู… TOTP ูƒุทุฑูŠู‚ุฉ ู„ู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ ุŒ ุฃูˆ ุงุณุชุฎุฏุงู… ุฃูŠ ู…ู† ู…ูุงุชูŠุญ ุงู„ุฃู…ุงู† ุงู„ุฎุงุตุฉ ุจูƒ. +totp_enrolled.subject = ู„ู‚ุฏ ู‚ู…ุช ุจุชุดูŠุท TOTP ูƒุทุฑูŠู‚ุฉ 2FA removed_security_key.subject = ุชู…ุช ุฅุฒุงู„ุฉ ู…ูุชุงุญ ุงู„ุฃู…ุงู† removed_security_key.text_1 = ุชู… ุฅุฒุงู„ุฉ ู…ูุชุงุญ ุงู„ุฃู…ุงู† โ€%[1] sโ€œ ู„ู„ุชูˆ ู…ู† ุญุณุงุจูƒ. -removed_security_key.no_2fa = ู„ู… ุชุนุฏ ู‡ู†ุงูƒ ุทุฑู‚ ุฃุฎุฑู‰ ู„ู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ (2FA) ู‚ูŠุฏ ุงู„ุชู‡ูŠุฆุฉ ุจุนุฏ ุงู„ุขู†ุŒ ุฃูŠ ู„ู… ูŠุนุฏ ู…ู† ุงู„ุถุฑูˆุฑูŠ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ ุจุงุณุชุฎุฏุงู… ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ (2FA). account_security_caution.text_1 = ุฅุฐุง ูƒุงู† ู‡ุฐุง ุฃู†ุชุŒ ููŠู…ูƒู†ูƒ ุชุฌุงู‡ู„ ู‡ุฐุง ุงู„ุจุฑูŠุฏ ุจุฃู…ุงู†. +totp_disabled.no_2fa = ู„ู… ุชุนุฏ ู‡ู†ุงูƒ ุทุฑู‚ ุฃูุฎุฑู‰ ู„ู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ (2FA) ู‚ูŠุฏ ุงู„ุชู‡ูŠุฆุฉ ุนุฏ ุงู„ุขู† ุŒ ุฃูŠ ุฃู†ู‡ ู„ู… ูŠุนุฏ ู…ู† ุงู„ุถุฑูˆุฑูŠ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ ุจุงุณุชุฎุฏุงู… ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ (2FA). +removed_security_key.no_2fa = ู„ู… ุชุนุฏ ู‡ู†ุงูƒ ุทุฑู‚ ุฃุฎุฑู‰ ู„ู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ (2FA) ู‚ูŠุฏ ุงู„ุชู‡ูŠุฆุฉ ุจุนุฏ ุงู„ุขู†ุŒ ุฃูŠ ู„ู… ูŠุนุฏ ู…ู† ุงู„ุถุฑูˆุฑูŠ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุฅู„ู‰ ุญุณุงุจูƒ ุจุงุณุชุฎุฏุงู… ุงู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ (2FA). +primary_mail_change.subject = ุชู… ุชุบูŠูŠุฑ ุงู„ุจุฑูŠุฏ ุงู„ุฃุณุงุณูŠ ุงู„ุฎุงุต ุจูƒ +password_change.text_1 = ุชู… ุชุบูŠูŠุฑ ูƒู„ู…ุฉ ู…ุฑูˆุฑ ุญุณุงุจูƒ ู„ู„ุชูˆ. +primary_mail_change.text_1 = ุชู… ุชุบูŠูŠุฑ ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ุงู„ุฃุณุงุณูŠ ู„ุญุณุงุจูƒ ุฅู„ู‰ %[1]s. ู‡ุฐุง ูŠุนู†ูŠ ุฃู† ุนู†ูˆุงู† ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ู‡ุฐุง ู„ู† ูŠุชู„ู‚ู‰ ุฅุดุนุงุฑุงุช ุงู„ุจุฑูŠุฏ ู„ุญุณุงุจูƒ ุจุนุฏ ุงู„ุขู†. account_security_caution.text_2 = ุฅุฐุง ู„ู… ุชูƒู† ุฃู†ุชุŒ ูู‡ุฐุง ูŠุนู†ูŠ ุฃู† ุญุณุงุจูƒ ู…ุฎุชุฑู‚. ูŠุฑุฌู‰ ุงู„ุงุชุตุงู„ ุจู…ุณุคูˆู„ูŠ ู‡ุฐุง ุงู„ู…ูˆู‚ุน. -totp_enrolled.subject = ู„ู‚ุฏ ู‚ู…ุช ุจุชุดูŠุท TOTP ูƒุทุฑูŠู‚ุฉ 2FA totp_enrolled.text_1.no_webauthn = ู„ู‚ุฏ ู‚ู…ุช ู„ู„ุชูˆ ุจุชู…ูƒูŠู† TOTP ู„ุญุณุงุจูƒ. ู‡ุฐุง ูŠุนู†ูŠ ุฃู†ู‡ ุจุงู„ู†ุณุจุฉ ู„ุฌู…ูŠุน ุนู…ู„ูŠุงุช ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุงู„ู…ุณุชู‚ุจู„ูŠุฉ ุฅู„ู‰ ุญุณุงุจูƒุŒ ูŠุฌุจ ุนู„ูŠูƒ ุงุณุชุฎุฏุงู… TOTP ูƒุทุฑูŠู‚ุฉ ู„ู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ. -totp_enrolled.text_1.has_webauthn = ู„ู‚ุฏ ู‚ู…ุช ู„ู„ุชูˆ ุจุชู…ูƒูŠู† TOTP ู„ุญุณุงุจูƒ. ู‡ุฐุง ูŠุนู†ูŠ ุฃู†ู‡ ุจุงู„ู†ุณุจุฉ ู„ุฌู…ูŠุน ุนู…ู„ูŠุงุช ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุงู„ู…ุณุชู‚ุจู„ูŠุฉ ุฅู„ู‰ ุญุณุงุจูƒุŒ ูŠู…ูƒู†ูƒ ุงุณุชุฎุฏุงู… TOTP ูƒุทุฑูŠู‚ุฉ ู„ู„ู…ุตุงุฏู‚ุฉ ุงู„ุซู†ุงุฆูŠุฉ ุŒ ุฃูˆ ุงุณุชุฎุฏุงู… ุฃูŠ ู…ู† ู…ูุงุชูŠุญ ุงู„ุฃู…ุงู† ุงู„ุฎุงุตุฉ ุจูƒ. [error] not_found = ุชุนุฐุฑ ุงู„ุนุซูˆุฑ ุนู„ู‰ ุงู„ู‡ุฏู. report_message = ุฅู† ูƒู†ุช ู…ุชูŠู‚ู‘ูู†ู‹ุง ุฃู† ู‡ุฐู‡ ุนู„ุฉ ููŠ ููˆุฑุฌูŠูˆุŒ ุฑุฌุงุกู‹ ุงุจุญุซ ููŠ ูƒูˆุฏุจูŠุฑุฌ ุฃูˆ ุงูุชุญ ู…ุณุฃู„ู‡ ุฌุฏูŠุฏุฉ ุฅุฐุง ู„ุฒู… ุงู„ุฃู…ุฑ. network_error = ุฎุทุฃ ููŠ ุงู„ุดุจูƒุฉ -invalid_csrf = ุทู„ุจ ุณูŠุฆ: ุฑู…ุฒ CSRF ุบูŠุฑ ุตุงู„ุญ occurred = ุญุฏุซ ุฎุทุฃ -missing_csrf = ุทู„ุจ ุณูŠุฆ: ู„ุง ูŠูˆุฌุฏ ุฑู…ุฒ CSRF server_internal = ุฎุทุฃ ุฏุงุฎู„ูŠ ููŠ ุงู„ุฎุงุฏู… [startpage] @@ -1475,21 +1730,19 @@ overview = ู†ุธุฑุฉ ุนุงู…ุฉ watched = ุงู„ู…ุณุชูˆุฏุนุงุช ุงู„ู…ุดุงู‡ุฏุฉ disabled_public_activity = ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู… ุนุทู‘ู„ ุงู„ุธู‡ูˆุฑ ุงู„ุนุงู… ู„ู„ู†ุดุงุท. show_on_map = ุงุนุฑุถ ู‡ุฐุง ุงู„ู…ูƒุงู† ุนู„ู‰ ุงู„ุฎุฑูŠุทุฉ -email_visibility.private = ุนู†ูˆุงู† ุจุฑูŠุฏูƒ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ุธุงู‡ุฑ ู„ูƒ ูˆู„ู„ู…ุฏูŠุฑูŠู† ูู‚ุท starred = ุงู„ู…ุณุชูˆุฏุนุงุช ุงู„ู…ู…ูŠู‘ุฒุฉ ุจู†ุฌู…ุฉ form.name_chars_not_allowed = ุงุณู… ุงู„ู…ุณุชุฎุฏู… "%s" ูŠุญุชูˆูŠ ุนู„ู‰ ุฑู…ูˆุฒ ุบูŠุฑ ุตุงู„ุญุฉ. form.name_pattern_not_allowed = ุงู„ู†ู…ุท "s%" ุบูŠุฑ ู…ุณู…ูˆุญ ุจู‡ ููŠ ุฅุณู… ุงู„ู…ุณุชุฎุฏู…. - followers.title.one = ู…ุชุงุจูุน -followers.title.few = ู…ุชุงุจุนูŠู† -following.title.one = ู…ุชุงุจุนุฉ -following.title.few = ู…ุชุงุจุนุฉ +public_activity.visibility_hint.admin_private = ู‡ุฐุง ุงู„ู†ุดุงุท ู…ุฑุฆูŠ ู„ูƒ ู„ุฃู†ูƒ ู…ุณุคูˆู„ุŒ ูˆู„ูƒู† ุงู„ู…ุณุชุฎุฏู… ูŠุฑูŠุฏ ุฃู† ูŠุธู„ ุฎุงุตุงู‹. +public_activity.visibility_hint.self_private = ู†ุดุงุทูƒ ู…ุฑุฆูŠ ู„ูƒ ูˆู„ุณูุนุงุฉ ุงู„ู…ุซูŠู„ ูู‚ุท. ุชุนุฏูŠู„ ุงู„ุฅุนุฏุงุฏุงุช. followers_one = %d ู…ุชุงุจูุน +following.title.one = ู…ุชุงุจุนุฉ +followers.title.few = ู…ุชุงุจุนูŠู† following_one = %d ูŠูุชุงุจุน +following.title.few = ู…ุชุงุจุนุฉ public_activity.visibility_hint.self_public = ู†ุดุงุทูƒ ู…ุฑุฆูŠ ู„ู„ุฌู…ูŠุนุŒ ุจุงุณุชุซู†ุงุก ุงู„ุชูุงุนู„ุงุช ููŠ ุงู„ู…ุณุงุญุงุช ุงู„ุฎุงุตุฉ. ุงุถุจุท ุงู„ุฅุนุฏุงุฏุงุช. public_activity.visibility_hint.admin_public = ู‡ุฐุง ุงู„ู†ุดุงุท ู…ุฑุฆูŠ ู„ู„ุฌู…ูŠุนุŒ ูˆู„ูƒู† ุจุตูุชูƒ ู…ุณุคูˆู„ุงู‹ ูŠู…ูƒู†ูƒ ุฃูŠุถู‹ุง ุฑุคูŠุฉ ุงู„ุชูุงุนู„ุงุช ููŠ ุงู„ู…ุณุงุญุงุช ุงู„ุฎุงุตุฉ. -public_activity.visibility_hint.self_private = ู†ุดุงุทูƒ ู…ุฑุฆูŠ ู„ูƒ ูˆู„ุณูุนุงุฉ ุงู„ู…ุซูŠู„ ูู‚ุท. ุชุนุฏูŠู„ ุงู„ุฅุนุฏุงุฏุงุช. -public_activity.visibility_hint.admin_private = ู‡ุฐุง ุงู„ู†ุดุงุท ู…ุฑุฆูŠ ู„ูƒ ู„ุฃู†ูƒ ู…ุณุคูˆู„ุŒ ูˆู„ูƒู† ุงู„ู…ุณุชุฎุฏู… ูŠุฑูŠุฏ ุฃู† ูŠุธู„ ุฎุงุตุงู‹. public_activity.visibility_hint.self_private_profile = ู†ุดุงุทูƒ ู…ุฑุฆูŠ ู„ูƒ ูˆู„ุณูุนุงุฉ ุงู„ู…ุซูŠู„ ูู‚ุท ู„ุฃู† ู…ู„ููƒ ุงู„ุดุฎุตูŠ ุฎุงุต. ุชุนุฏูŠู„ ุงู„ุฅุนุฏุงุฏุงุช. [auth] @@ -1501,26 +1754,21 @@ disable_register_mail = ุชู… ุชุนุทูŠู„ ุชุฃูƒูŠุฏ ุงู„ุจุฑูŠุฏ ู„ู„ุชุณุฌูŠู„. sign_up_successful = ุฃูŒู†ุดุฆ ุงู„ุญุณุงุจ ุจู†ุฌุงุญ. ู…ุฑุญุจุงู‹! forgot_password = ู†ุณูŠุช ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑุŸ allow_password_change = ุฃู„ุฒู… ุงู„ู…ุณุชุฎุฏู… ุจุชุบูŠูŠุฑ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ (ูŠูู†ุตุญ ุจู‡) -sign_up_now = ุชุญุชุงุฌ ุฅู„ู‰ ุญุณุงุจุŸ ุณุฌู„ ุงู„ุขู†. forgot_password_title = ู†ุณูŠุช ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ account_activated = ุชู… ุชูุนูŠู„ ุงู„ุญุณุงุจ -social_register_helper_msg = ู‡ู„ ู„ุฏูŠูƒ ุญุณุงุจ ุจุงู„ูุนู„ุŸ ุงุฑุจุทู‡ ุงู„ุขู†! create_new_account = ุณุฌู„ ุญุณุงุจ disable_register_prompt = ุงู„ุชุณุฌูŠู„ ู…ุบู„ู‚ ุญุงู„ูŠุงู‹. ูŠุฑุฌู‰ ุงู„ุงุชุตุงู„ ุจุงู„ู…ุฏูŠุฑ. active_your_account = ูุนู‘ู„ ุญุณุงุจูƒ -register_helper_msg = ู‡ู„ ู„ุฏูŠูƒ ุญุณุงุจ ุจุงู„ูุนู„ุŸ ุณุฌู„ ุงู„ุฏุฎูˆู„! manual_activation_only = ุชูˆุงุตู„ ู…ุน ู…ุฏูŠุฑ ู…ูˆู‚ุนูƒ ู„ุฅูƒู…ุงู„ ุงู„ุชูุนูŠู„. must_change_password = ุญุฏู‘ุซ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุงู„ุฎุงุตุฉ ุจูƒ send_reset_mail = ุฃุฑุณู„ ุจุฑูŠุฏ ุงู„ุงุณุชุนุงุฏุฉ resend_mail = ุงุถุบุท ู‡ู†ุง ู„ุฅุนุงุฏุฉ ุฅุฑุณุงู„ุฉ ุฑุณุงู„ุฉ ุชูุนูŠู„ ุญุณุงุจูƒ has_unconfirmed_mail = ุฃู‡ู„ุง ูŠุง %sุŒ ู„ุฏูŠูƒ ุนู†ูˆุงู† ุจุฑูŠุฏ ุฅู„ูƒุชุฑูˆู†ูŠ ุบูŠุฑ ู…ุคูƒู‘ูŽุฏ (%s). ุฅู† ู„ู… ุชุณุชู„ู… ุฑุณุงู„ุฉ ุชุฃูƒูŠุฏ ุฃูˆ ุชุฑูŠุฏ ุฅุฑุณุงู„ ูˆุงุญุฏุฉ ุฌุฏูŠุฏุฉุŒ ูู†ุฑุฌูˆ ุงู„ุถุบุท ุนู„ู‰ ุงู„ุฒุฑ ุงู„ุฐูŠ ุจุงู„ุฃุณูู„. -email_not_associate = ุนู†ูˆุงู† ุงู„ุจุฑูŠุฏ ู‡ุฐุง ุบูŠุฑ ู…ุฑุชุจุท ุจุฃูŠ ุญุณุงุจ. reset_password = ุงุณุชุนุงุฏุฉ ุงู„ุญุณุงุจ oauth_signin_tab = ุฃุฑุจุท ุจุญุณุงุจ ู…ูˆุฌูˆุฏ invalid_password = ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุงู„ุฎุงุตุฉ ุจูƒ ู„ุง ุชุทุงุจู‚ ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุงู„ุชูŠ ุงุณุชุฎุฏู…ุช ู„ุชุณุฌูŠู„ ุงู„ุญุณุงุจ. oauth_signin_title = ุณุฌู‘ู„ ุงู„ุฏุฎูˆู„ ู„ุชุฃุฐู† ู„ู„ุญุณุงุจ ุงู„ู…ุฑุจูˆุท reset_password_helper = ุฅุนุงุฏุฉ ุงู„ุญุณุงุจ -tab_openid = ุชุณุฌูŠู„ ุฏุฎูˆู„ ุจู€OpenID openid_connect_submit = ุงุชุตู„ oauth_signup_tab = ุณุฌู„ ุญุณุงุจ ุฌุฏูŠุฏ oauth.signin.error.temporarily_unavailable = ูุดู„ ุทู„ุจ ุงู„ุฅุฐู† ู„ุฃู† ุฎุงุฏู… ุงู„ุชูˆุซูŠู‚ ุบูŠุฑ ู…ุชุงุญ ู…ุคู‚ุชุง. ุญุงูˆู„ ู…ุฑุฉ ุฃุฎุฑู‰ ู„ุงุญู‚ุงู‹. @@ -1551,29 +1799,26 @@ authorize_application_description = ุฅุฐุง ู…ู†ุญุช ุญู‚ ุงู„ูˆุตูˆู„ุŒ ูุณูŠูƒ authorize_application_created_by = ุฃูู†ุดุฆ ู‡ุฐุง ุงู„ุชุทุจูŠู‚ ุจูˆุงุณุทุฉ %s. email_domain_blacklisted = ู„ุง ูŠู…ูƒู†ูƒ ุงู„ุชุณุฌูŠู„ ุจุงุณุชุฎุฏุงู… ุนู†ูˆุงู† ุจุฑูŠุฏูƒ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ. authorize_title = ู‡ู„ ุชุฑูŠุฏ ุฃู† ุชุฃุฐู† ู„ู€ "%s" ุจุงู„ูˆุตูˆู„ ุฅู„ู‰ ุญุณุงุจูƒุŸ -prohibit_login = ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ู…ู…ู†ูˆุน -prohibit_login_desc = ุญุณุงุจูƒ ู…ู…ู†ูˆุน ู…ู† ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ุŒ ูŠุฑุฌู‰ ุงู„ุชูˆุงุตู„ ู…ุน ู…ุฏูŠุฑ ุงู„ู…ูˆู‚ุน. +prohibit_login = ู‡ุฐุง ุงู„ุญุณุงุจ ู…ุนู„ู‚ +prohibit_login_desc = ุชู… ุชุนู„ูŠู‚ ุญุณุงุจูƒ ู…ู† ุงู„ุชูุงุนู„ ู…ุน ู‡ุฐู‡ ุงู„ู†ุณุฎุฉ. ุชูˆุงุตู„ ู…ุน ู…ุณุคูˆู„ ุงู„ู†ุณุฎุฉ ู„ุงุณุชุนุงุฏุฉ ุงู„ูˆุตูˆู„. disable_forgot_password_mail_admin = ุงุณุชุฑุฏุงุฏ ุงู„ุญุณุงุจ ู…ุชุงุญ ูู‚ุท ุนู†ุฏ ุฅุนุฏุงุฏ ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ. ูŠูุฑุฌู‰ ุฅุนุฏุงุฏ ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ู„ุชูุนูŠู„ ุงุณุชุฑุฏุงุฏ ุงู„ุญุณุงุจ. password_pwned_err = ุชุนุฐุฑ ุงู„ูˆุตูˆู„ ุฅู„ู‰ HaveIBeenPwned password_pwned = ุงู„ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุงู„ู…ูุฎุชุงุฑุฉ ู‡ูŠ ุนู„ู‰ ู‚ุงุฆู…ุฉ ูƒู„ู…ุงุช ู…ุฑูˆุฑ ู…ุณุฑูˆู‚ุฉ ุชู… ูƒุดูู‡ุง ููŠ ุชุณุฑูŠุจุงุช ุนุงู…ุฉ ู„ู„ุจูŠุงู†ุงุช. ูŠูุฑุฌู‰ ุงู„ู…ุญุงูˆู„ุฉ ู…ุฑุฉ ุฃุฎุฑู‰ ุจูƒู„ู…ุฉ ู…ุฑูˆุฑ ุฃุฎุฑู‰ุŒ ูˆุถุน ููŠ ุงุนุชุจุงุฑูƒ ุชุบูŠูŠุฑ ุชู„ูƒ ุงู„ูƒู„ู…ุฉ ููŠ ุงู„ุฃู…ุงูƒู† ุงู„ุฃุฎุฑู‰. authorization_failed = ูุดู„ ุงู„ุฅุฐู† authorize_redirect_notice = ุณุชุชู… ุฅุนุงุฏุฉ ุชูˆุฌูŠู‡ูƒ ุฅู„ู‰ %s ุฅุฐุง ุฃุฐู†ุช ู„ู„ุชุทุจูŠู‚. authorize_application = ุงุฆุฐู† ู„ู„ุชุทุจูŠู‚ -sspi_auth_failed = ูุดู„ุช ุนู…ู„ูŠุฉ ุงุณุชูŠุซุงู‚ SSPI openid_connect_desc = ู…ุณุงุฑ ุงู„ู€OpenID ุงู„ู…ุฎุชุงุฑ ู…ุฌู‡ูˆู„. ุงุฑุจุทู‡ ู…ุน ุญุณุงุจ ุฌุฏูŠุฏ ู‡ู†ุง. openid_signin_desc = ุฃุฏุฎู„ ู…ุณุงุฑ ุงู„ู€OpenID ุงู„ุฎุงุต ุจูƒ. ู…ุซู„ุงู‹: alice.openid.example.org ุฃูˆ https://openid.example.org/alice. openid_register_desc = ู…ุณุงุฑ ุงู„ู€OpenID ุงู„ู…ุฎุชุงุฑ ู…ุฌู‡ูˆู„. ุงุฑุจุทู‡ ู…ุน ุญุณุงุจ ุฌุฏูŠุฏ ู‡ู†ุง. remember_me = ุชุฐูƒุฑ ู‡ุฐุง ุงู„ุฌู‡ุงุฒ -remember_me.compromised = ุฑู…ุฒ ุงู„ุงุญุชูุงุธ ุจุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ู„ู… ูŠุนุฏ ุตุงู„ุญุงุŒ ู…ู…ุง ู‚ุฏ ูŠุนู†ูŠ ุงุฎุชุฑุงู‚ ุงู„ุญุณุงุจ. ู†ุฑุฌูˆ ู…ุฑุงุฌุนุฉ ุญุณุงุจูƒ ู„ุฑุคูŠุฉ ุฃูŠ ู†ุดุงุท ุบูŠุฑ ู…ุฃู„ูˆู. authorization_failed_desc = ูุดู„ ุงู„ุชููˆูŠุถ ู„ุฃู†ู†ุง ุงูƒุชุดูู†ุง ุทู„ุจู‹ุง ุบูŠุฑ ุตุงู„ุญ. ูŠุฑุฌู‰ ุงู„ุงุชุตุงู„ ุจู…ุดุฑู ุงู„ุชุทุจูŠู‚ ุงู„ุฐูŠ ุญุงูˆู„ุช ุชุฑุฎูŠุตู‡. - +sign_in_openid = ุงู„ู…ุชุงุจุนุฉ ุจุงุณุชุฎุฏุงู… OpenID hint_login = ู„ุฏูŠูƒ ุญุณุงุจ ุจุงู„ูุนู„ุŸ ุณุฌู‘ู„ ุงู„ุฏุฎูˆู„ ุงู„ุขู†! hint_register = ูŠู„ุฒู…ูƒ ุญุณุงุจ ุŸ ุณุฌู‘ูู„ ุงู„ุขู†. sign_up_button = ุณุฌู‘ูู„ ุงู„ุขู†. -unauthorized_credentials = ุจูŠุงู†ุงุช ุงู„ุงุนุชู…ุงุฏ ุบูŠุฑ ุตุญูŠุญุฉ ุฃูˆ ุงู†ุชู‡ุช ุตู„ุงุญูŠุชู‡ุง. ุฃุนุฏ ู…ุญุงูˆู„ุฉ ุชู†ููŠุฐ ุงู„ุฃู…ุฑ ุฃูˆ ุฑุงุฌุน %s ู„ู…ุฒูŠุฏ ู…ู† ุงู„ู…ุนู„ูˆู…ุงุช -use_onetime_code = ุงุณุชุฎุฏู… ุฑู…ุฒู‹ุง ู„ู…ุฑุฉ ูˆุงุญุฏุฉ back_to_sign_in = ุงู„ุนูˆุฏุฉ ุฅู„ู‰ ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ -sign_in_openid = ุงู„ู…ุชุงุจุนุฉ ุจุงุณุชุฎุฏุงู… OpenID +use_onetime_code = ุงุณุชุฎุฏู… ุฑู…ุฒู‹ุง ู„ู…ุฑุฉ ูˆุงุญุฏุฉ +unauthorized_credentials = ุจูŠุงู†ุงุช ุงู„ุงุนุชู…ุงุฏ ุบูŠุฑ ุตุญูŠุญุฉ ุฃูˆ ุงู†ุชู‡ุช ุตู„ุงุญูŠุชู‡ุง. ุฃุนุฏ ู…ุญุงูˆู„ุฉ ุชู†ููŠุฐ ุงู„ุฃู…ุฑ ุฃูˆ ุฑุงุฌุน %s ู„ู…ุฒูŠุฏ ู…ู† ุงู„ู…ุนู„ูˆู…ุงุช [packages] rpm.repository.multiple_groups = ู‡ุฐู‡ ุงู„ุญุฒู…ุฉ ู…ุชูˆูุฑุฉ ููŠ ู…ุฌู…ูˆุนุงุช ู…ุชุนุฏุฏุฉ. @@ -1617,7 +1862,6 @@ less = ุฃู‚ู„ number_of_contributions_in_the_last_12_months = %s ู…ุณุงู‡ู… ููŠ ุขุฎุฑ 12 ุดู‡ุฑ contributions_zero = ุจู„ุง ู…ุณุงู‡ู…ุงุช more = ุฃูƒุซุฑ - contributions_format = {contributions} ู…ุณุงู‡ู…ุฉ ููŠ {day} {month} {year} contributions_one = ุงู„ู…ุณุงู‡ู…ุฉ contributions_few = ุงู„ู…ุณุงู‡ู…ุงุช @@ -1692,7 +1936,6 @@ packages.repository = ุงู„ู…ุณุชูˆุฏุน orgs.teams = ุงู„ููุฑู‚ packages.size = ุงู„ุญุฌู… users.max_repo_creation = ุงู„ุนุฏุฏ ุงู„ุฃู‚ุตู‰ ู„ู„ู…ุณุชูˆุฏุนุงุช -repos.forks = ุงู„ุงุดุชู‚ุงู‚ุงุช orgs.new_orga = ู…ู†ุธู…ุฉ ุฌุฏูŠุฏุฉ users.allow_import_local = ูŠุณุชุทูŠุน ุงุณุชูŠุฑุงุฏ ู…ุณุชูˆุฏุนุงุช ู…ุญู„ูŠุฉ repos.name = ุงู„ุงุณู… @@ -1785,7 +2028,6 @@ email_invalid = ุนู†ูˆุงู† ุงู„ุจุฑูŠุฏ ุบูŠุฑ ุตุงู„ุญ. CommitSummary = ุฎู„ุงุตุฉ ุงู„ุฅูŠุฏุงุน team_not_exist = ุงู„ูุฑูŠู‚ ุบูŠุฑ ู…ูˆุฌูˆุฏ. TreeName = ู…ุณุงุฑ ุงู„ู…ู„ู -SSPIDefaultLanguage = ุงู„ู„ุบุฉ ุงู„ู…ุจุฏุฆูŠุฉ password_complexity = ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ู„ูŠุณุช ุจุงู„ุชุนู‚ูŠุฏ ุงู„ู…ุทู„ูˆุจ: password_not_match = ู„ุง ุชุชุทุงุจู‚ ูƒู„ู…ุชุง ุงู„ู…ุฑูˆุฑ. still_has_org = "ุญุณุงุจูƒ ุนุถูˆ ููŠ ู…ู†ุธู…ุฉ ุฃูˆ ุฃูƒุซุฑุ› ุบุงุฏุฑู‡ู… ุฃูˆู„ุง." @@ -1794,26 +2036,22 @@ repository_files_already_exist.delete = ุงู„ู…ู„ูุงุช ู…ูˆุฌูˆุฏุฉ ุจุงู„ูุนู„ repository_files_already_exist.adopt = ุงู„ู…ู„ูุงุช ู…ูˆุฌูˆุฏุฉ ุจุงู„ูุนู„ ู„ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน ูˆูŠู…ูƒู† ุงุนุชู…ุงุฏู‡ุง ูู‚ุท. repository_files_already_exist = ุงู„ู…ู„ูุงุช ู…ูˆุฌูˆุฏุฉ ุจุงู„ูุนู„ ู„ู‡ุฐุง ุงู„ู…ุณุชูˆุฏุน. ุงุชุตู„ ุจู…ุฏูŠุฑ ุงู„ู†ุธุงู…. TeamName = ุงุณู… ุงู„ูุฑูŠู‚ -username_has_not_been_changed = ู„ู… ูŠุชู… ุชุบูŠูŠุฑ ุงุณู… ุงู„ู…ุณุชุฎุฏู… username_change_not_local_user = ุงู„ู…ุณุชุฎุฏู…ูŠู† ุบูŠุฑ ุงู„ู…ุญู„ูŠูŠู† ุบูŠุฑ ู…ุณู…ูˆุญ ู„ู‡ู… ุจุชุบูŠูŠุฑ ุฃุณู…ุงุฆู‡ู…. captcha_incorrect = ุงู„ูƒุงุจุชุดุง ุฎุงุทุฆุฉ. AdminEmail = ุนู†ูˆุงู† ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ู„ู„ู…ุฏูŠุฑ team_no_units_error = ุงุณู…ุญ ุจุงู„ูˆุตูˆู„ ุฅู„ู‰ ู‚ุณู… ูˆุงุญุฏ ุนู„ู‰ ุงู„ุฃู‚ู„ ููŠ ุงู„ู…ุณุชูˆุฏุนุงุช. must_use_public_key = ุงู„ู…ูุชุงุญ ุงู„ุฐูŠ ู‚ุฏู…ุชู‡ ู‡ูˆ ู…ูุชุงุญ ุฎุงุต. ู…ู† ูุถู„ูƒ ู„ุง ุชุฑูุน ู…ูุชุงุญูƒ ุงู„ุฎุงุต ููŠ ุฃูŠ ู…ูƒุงู†. ุงุณุชุฎุฏู… ู…ูุชุงุญูƒ ุงู„ุนุงู… ุจุฏู„ุงู‹ ู…ู† ุฐู„ูƒ. -unable_verify_ssh_key = "ุชุนุฐุฑ ุงู„ุชุญู‚ู‚ ู…ู† ู…ูุชุงุญ ุงู„ู€SSHุŒ ุชุฃูƒุฏ ู…ู†ู‡ ู…ุฌุฏุฏุงู‹." +unable_verify_ssh_key = ุชุนุฐุฑ ุงู„ุชุญู‚ู‚ ู…ู† ู…ูุชุงุญ ุงู„ู€SSHุŒ ุชุฃูƒุฏ ู…ู†ู‡ ู…ุฌุฏุฏุงู‹. invalid_gpg_key = ูุดู„ ุชุญู‚ู‚ ู…ูุชุงุญ ุงู„ู€GPG: %s still_own_packages = "ุญุณุงุจูƒ ูŠู…ู„ูƒ ุญุฒู…ุฉ ูˆุงุญุฏุฉ ุฃูˆ ุงูƒุซุฑุŒ ุงุญุฐูู‡ู… ุฃูˆู„ุงู‹." -still_own_repo = "ุญุณุงุจูƒ ูŠู…ู„ูƒ ู…ุณุชูˆุฏุน ูˆุงุญุฏ ุฃูˆ ุงูƒุซุฑุŒ ุงุญุฐูู‡ู… ุฃูˆ ุญูˆู„ู‡ู… ุฃูˆู„ุงู‹." -SSHTitle = ุงุณู… ู…ูุชุงุญ SSH +still_own_repo = ุญุณุงุจูƒ ูŠู…ู„ูƒ ู…ุณุชูˆุฏุน ูˆุงุญุฏ ุฃูˆ ุงูƒุซุฑุŒ ุงุญุฐูู‡ู… ุฃูˆ ุญูˆู„ู‡ู… ุฃูˆู„ุงู‹. 2fa_auth_required = ุงู„ุฒูŠุงุฑุฉ ุงู„ุฎุงุฑุฌูŠุฉ ุงู„ุฒู…ุช ุงุณุชูŠุซุงู‚ ุซู†ุงุฆูŠ. target_branch_not_exist = ุงู„ูุฑุน ุงู„ู…ุณุชู‡ุฏู ู„ูŠุณ ู…ูˆุฌูˆุฏ. PayloadUrl = ุฑุงุจุท ุงู„ุญู…ูˆู„ุฉ org_still_own_packages = "ุงู„ู…ู†ุธู…ุฉ ุชุฒุงู„ ุชู…ู„ูƒ ุญุฒู…ุฉ ูˆุงุญุฏุฉ ุฃูˆ ุงูƒุซุฑุŒ ุงุญุฐูู‡ู… ุฃูˆู„ุงู‹." last_org_owner = ู„ุง ูŠู…ูƒู†ูƒ ุฅุฒุงู„ุฉ ุขุฎุฑ ู…ุณุชุฎุฏู… ู…ู† ูุฑูŠู‚ "ุงู„ู…ุงู„ูƒูŠู†". ูŠุฌุจ ุฃู† ูŠูƒูˆู† ู‡ู†ุงูƒ ุนู„ู‰ ุงู„ุฃู‚ู„ ู…ุงู„ูƒ ูˆุงุญุฏ ู„ู„ู…ู†ุธู…ุฉ. -HttpsUrl = ุฑุงุจุท HTTPS invalid_ssh_key = ูุดู„ ุชุญู‚ู‚ ู…ูุชุงุญ ุงู„ู€SSH: %s AuthName = ุงุณู… ุงู„ุฃุฐู† -SSPISeparatorReplacement = ุงู„ูุงุตู„ุฉ openid_been_used = ุนู†ูˆุงู† ุงู„ู€OpenID "%s" ู…ูุณุชุฎุฏู… ุจุงู„ูุนู„. git_ref_name_error = `ูŠุฌุจ ุฃู† ูŠูƒูˆู† ุงุณู…ู‹ุง ู…ุฑุฌุนูŠู‹ุง ุฌูŠุฏู‹ุง ู„ู€ Git.` include_error = ` ูŠุฌุจ ุฃู† ูŠุญุชูˆูŠ ุนู„ู‰ ุณู„ุณู„ุฉ ูุฑุนูŠุฉ "%s".` @@ -1822,18 +2060,17 @@ glob_pattern_error = `ุงู„ู†ู…ุท ุงู„ุดุงู…ู„ ุบูŠุฑ ุตุงู„ุญ: %s.` CommitChoice = ุฅุฎุชูŠุงุฑ ุงู„ุฅุฏุงุน regex_pattern_error = ` ู†ู…ุท ุงู„ุชุนุจูŠุฑ ุงู„ู†ู…ุทูŠ ุบูŠุฑ ุตุงู„ุญ: %s.` username_error = ` ูŠูู…ูƒู†ู‡ ุฃู† ูŠุญุชูˆูŠ ุนู„ู‰ ุญุฑูˆู ุฅู†ุฌู„ูŠุฒูŠุฉ ูˆุฃุฑู‚ุงู… ูˆุดุฑุทุฉ ("-") ูˆุดุฑุทุฉ ุณูู„ูŠุฉ ("_") ูˆ ู†ู‚ุทุฉ (".") ูู‚ุท. ูˆูŠู…ูƒู†ู‡ ุงู† ูŠุจุฏุฃ ูˆูŠู†ุชู‡ูŠ ุจุญุฑู ุงูˆ ุจุฑู‚ู….` - +Biography = ุงู„ู†ุจุฐุฉ +Website = ู…ูˆู‚ุน ุงู„ูˆูŠุจ +To = ุงุณู… ุงู„ูุฑุน +AccessToken = ุฑู…ุฒ ุงู„ูˆุตูˆู„ +repository_force_private = ูˆุถุน ุงู„ุฎุงุต ุงู„ุฅุฌุจุงุฑูŠ ู…ูุนู‘ู„: ู„ุง ูŠู…ูƒู† ุชุญูˆูŠู„ ุงู„ู…ุณุชูˆุฏุนุงุช ุงู„ุฎุงุตุฉ ุฅู„ู‰ ุนุงู…ุฉ. FullName = ุงู„ุงุณู… ุงู„ูƒุงู…ู„ Description = ุงู„ูˆุตู Pronouns = ุงู„ุถู…ุงุฆุฑ -Biography = ุงู„ู†ุจุฐุฉ -Website = ู…ูˆู‚ุน ุงู„ูˆูŠุจ -Location = ุงู„ู…ูˆู‚ุน -To = ุงุณู… ุงู„ูุฑุน -AccessToken = ุฑู…ุฒ ุงู„ูˆุตูˆู„ -invalid_group_team_map_error = ` ุงู„ุชุนูŠูŠู† ุบูŠุฑ ุตุงู„ุญ: %s ` username_claiming_cooldown = ู„ุง ูŠู…ูƒู† ุงู„ู…ุทุงู„ุจุฉ ุจุงุณู… ุงู„ู…ุณุชุฎุฏู…ุŒ ู„ุฃู† ูุชุฑุฉ ุชุจุงุทุคู‡ ู„ู… ุชู†ุชู‡ู ุจุนุฏ. ูŠู…ูƒู† ุงู„ู…ุทุงู„ุจุฉ ุจู‡ ุนู†ุฏ %[1]s. -repository_force_private = ูˆุถุน ุงู„ุฎุงุต ุงู„ุฅุฌุจุงุฑูŠ ู…ูุนู‘ู„: ู„ุง ูŠู…ูƒู† ุชุญูˆูŠู„ ุงู„ู…ุณุชูˆุฏุนุงุช ุงู„ุฎุงุตุฉ ุฅู„ู‰ ุนุงู…ุฉ. +Location = ุงู„ู…ูˆู‚ุน +invalid_group_team_map_error = ` ุงู„ุชุนูŠูŠู† ุบูŠุฑ ุตุงู„ุญ: %s ` visit_rate_limit = ุชู†ุงูˆู„ุช ุงู„ุฒูŠุงุฑุฉ ุนู† ุจูุนุฏ ุงู„ุญุฏ ู…ู† ู…ุนุฏู„ู‡ุง. email_domain_is_not_allowed = ู†ุทุงู‚ ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ู„ู„ู…ุณุชุฎุฏู… %s ูŠุชุนุงุฑุถ ู…ุน ู‚ุงุฆู…ุฉ ุงู„ู†ุทุงู‚ุงุช ุงู„ู…ุณู…ูˆุญุฉ ุŒ ุฃูˆ ุงู„ู…ู…ู†ูˆุนุฉ. ูŠุฑุฌู‰ ุงู„ุชุฃูƒุฏ ู…ู† ุฅุฏุฎุงู„ ุนู†ูˆุงู† ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ุจุดูƒู„ ุตุญูŠุญ. unset_password = ุงู„ู…ุณุชุฎุฏู… ุงู„ู…ุณุฌู„ ู„ู… ูŠู‚ู… ุจุชุนูŠูŠู† ูƒู„ู…ุฉ ู…ุฑูˆุฑ. @@ -1844,53 +2081,35 @@ required_prefix = ุงู„ู…ูุฏุฎู„ ูŠุฌุจ ุฃู† ูŠุจุฏุฃ ู…ุน "%s" [home] filter = ุชุตููŠุงุช ุฃุฎุฑู‰ show_archived = ู…ุคุฑุดู -search_repos = ุงู„ุนุซูˆุฑ ุนู„ู‰ ู…ุณุชูˆุฏุนโ€ฆ -my_orgs = ู…ูู†ุธู‘ู…ุงุชูŠ +my_orgs = ุงู„ู…ู†ุธู…ุงุช uname_holder = ุงุณู… ุงู„ู…ุณุชุฎุฏู… ุฃูˆ ุนู†ูˆุงู† ุงู„ุจุฑูŠุฏ ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ my_repos = ุงู„ู…ุณุชูˆุฏุนุงุช show_both_archived_unarchived = ูŠูุนุฑุถ ุงู„ู…ุคุฑุดู ูˆุบูŠุฑ ุงู„ู…ุคุฑุดู feed_of = ู…ูˆุฌุฒ "%s" issues.in_your_repos = ููŠ ู…ุณุชูˆุฏุนุงุชูƒ -switch_dashboard_context = ุชุบูŠูŠุฑ ุฅุทุงุฑ ู„ูˆุญุฉ ุงู„ุชุญูƒู… +switch_dashboard_context = ุจุฏู‘ู„ ุณูŠุงู‚ ู„ูˆุญุฉ ุงู„ุชุญูƒู… show_both_private_public = ูŠูุนุฑุถ ุงู„ุนุงู… ูˆุงู„ุฎุงุต filter_by_team_repositories = ุชุตููŠุฉ ุญุณุจ ู…ุณุชูˆุฏุนุงุช ุงู„ูุฑูŠู‚ show_only_private = ูŠูุนุฑุถ ุงู„ุฎุงุต ูู‚ุท show_private = ุฎุงุต -password_holder = ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ -show_more_repos = ุฅุธู‡ุงุฑ ุงู„ู…ุฒูŠุฏ ู…ู† ุงู„ู…ุณุชูˆุฏุนุงุชโ€ฆ show_only_public = ูŠูุนุฑุถ ุงู„ุนุงู… ูู‚ุท -collaborative_repos = ุงู„ู…ุณุชูˆุฏุนุงุช ุงู„ุชุนุงูˆู†ูŠุฉ show_only_unarchived = ูŠูุนุฑุถ ุบูŠุฑ ุงู„ู…ุคุฑุดู ูู‚ุท -my_mirrors = ู…ุฑุงูŠุงูŠ show_only_archived = ูŠูุนุฑุถ ุงู„ู…ุคุฑุดู ูู‚ุท view_home = ุนุฑุถ %s [explore] -search.match.tooltip = ุฅุดู…ู„ ูู‚ุท ุงู„ู†ุชุงุฆุฌ ุงู„ุชูŠ ุชุทุงุจู‚ ุงู„ุจุญุซ ูƒู„ูŠุงู‹ -search.type.tooltip = ู†ูˆุน ุงู„ุจุญุซ -code_search_results = ู†ุชุงุฆุฌ ุงู„ุจุญุซ ุนู† "%s" go_to = ุฅุฐู‡ุจ ุฅู„ู‰ repos = ุงู„ู…ุณุชูˆุฏุนุงุช users = ุงู„ู…ุณุชุฎุฏู…ูŠู† -code_search_unavailable = ุงู„ุจุญุซ ุงู„ุจุฑู…ุฌูŠ ุบูŠุฑ ู…ุชูˆูุฑ ุญุงู„ูŠุงู‹. ู…ู† ูุถู„ูƒ ุฑุงุณู„ ู…ุฏูŠุฑ ุงู„ู…ูˆู‚ุน. -search = ุงู„ุจุญุซ -user_no_results = ู„ุง ูŠูˆุฌุฏ ู…ุณุชุฎุฏู…ูˆู† ู…ุชุทุงุจู‚ูˆู†. -org_no_results = ู„ุง ุชูˆุฌุฏ ู…ู†ุธู…ุงุช ู…ุชุทุงุจู‚ุฉ. code = ู†ุต ุจุฑู…ุฌูŠ -search.match = ู…ุทุงุจู‚ -search.fuzzy.tooltip = ุฅุดู…ู„ ุงู„ู†ุชุงุฆุฌ ุงู„ุชูŠ ุชุทุงุจู‚ ุงู„ุจุญุซ ุชู‚ุฑูŠุจุงู‹ -search.fuzzy = ุชุทุงุจู‚ ุบุงู…ุถ organizations = ุงู„ู…ู†ุธู…ุงุช -repo_no_results = ู„ุง ุชูˆุฌุฏ ู…ุณุชูˆุฏุนุงุช ู…ุทุงุจู‚ุฉ. -code_no_results = ู„ู… ูŠุชู… ุงู„ุนุซูˆุฑ ุนู„ู‰ ุจุฑู…ุฌูŠุฉ ุชุทุงุจู‚ ุงู„ุจุญุซ. relevant_repositories_tooltip = ุชู… ุฃุฎูุงุก ุงู„ู…ุณุชูˆุฏุนุงุช ุงู„ุชูŠ ู‡ูŠ ู…ุดุชู‚ุงุช ูˆุฃูŠุถุงู‹ ุงู„ุชูŠ ู„ูŠุณ ู„ู‡ุง ู…ูˆุถูˆุนุŒ ูˆู„ุง ุฃูŠู‚ูˆู†ุฉุŒ ูˆู„ุง ูŠูˆุฌุฏ ูˆุตู. relevant_repositories = ูŠุชู… ุงุธู‡ุงุฑ ุงู„ู…ุณุชูˆุฏุนุงุช ุงู„ู…ุชุนู„ู‚ุฉ ูู‚ุท. ุฃุธู‡ุฑ ุงู„ู†ุชุงุฆุฌ ุบูŠุฑ ุงู„ู…ุตูุงุฉ. code_last_indexed_at = ููู‡ุฑุณ ุขุฎุฑ ู…ุฑุฉ %s - -stars_one = %d ู†ุฌู…ุฉ stars_few = %d ู†ุฌูˆู… forks_one = %d ู†ุณุฎุฉ forks_few = %d ู†ูุณูŽุฎ +stars_one = %d ู†ุฌู…ุฉ [actions] variables.none = ู„ุง ุชูˆุฌุฏ ู…ุชุบูŠุฑุงุช ุจุนุฏ. @@ -1924,7 +2143,6 @@ runners.labels = ุงู„ุชุตู†ูŠูุงุช runners.status.unspecified = ู…ุฌู‡ูˆู„ runs.commit = ุฅูŠุฏุงุน status.success = "ู†ุฌุญ" -runs.no_workflows.documentation =ู„ู…ุนุฑูุฉ ุงู„ู…ุฒูŠุฏ ุนู† ุฅุฌุฑุงุกุงุช ููˆุฑุฌูŠูˆุŒ ุจุฑุฌุงุก ุฑุคูŠุฉ ุงู„ุชูˆุซูŠู‚. runs.empty_commit_message = (ุฑุณุงู„ุฉ ุฅูŠุฏุงุน ูุงุฑุบุฉ) status.cancelled = "ู…ู„ุบูŠ" runs.status_no_select = ูƒู„ ุงู„ุญุงู„ุงุช @@ -2068,8 +2286,6 @@ search = ุงู„ุจุญุซโ€ฆ type_tooltip = ู†ูˆุน ุงู„ุจุญุซ fuzzy = ุฃุฌุนุฏ fuzzy_tooltip = ู‚ู… ุจุชุถู…ูŠู† ุงู„ู†ุชุงุฆุฌ ุงู„ุชูŠ ุชุชุทุงุจู‚ ุฃูŠุถู‹ุง ู…ุน ู…ุตุทู„ุญ ุงู„ุจุญุซ ุจุดูƒู„ ูˆุซูŠู‚ -match = ุชุชู†ุงุณุจ -match_tooltip = ู‚ู… ุจุชุถู…ูŠู† ุงู„ู†ุชุงุฆุฌ ุงู„ุชูŠ ุชุทุงุจู‚ ู…ุตุทู„ุญ ุงู„ุจุญุซ ุงู„ู…ุญุฏุฏ ูู‚ุท repo_kind = ุจุญุซ ููŠ ุงู„ู…ุณุชูˆุฏุนุงุชโ€ฆ user_kind = ุจุญุซ ุนู† ุงู„ู…ุณุชุฎุฏู…ูŠู†โ€ฆ team_kind = ุจุญุซ ุนู† ุงู„ูุฑู‚โ€ฆ @@ -2080,12 +2296,12 @@ no_results = ู„ุง ุชูˆุฌุฏ ู†ุชุงุฆุฌ ู…ุทุงุจู‚ุฉ. issue_kind = ุงู„ุจุญุซ ุถู…ู† ุงู„ุฃุนุทุงู„โ€ฆ pull_kind = ุงู„ุจุญุซ ุถู…ู† ุทู„ุจุงุช ุงู„ุณุญุจโ€ฆ keyword_search_unavailable = ุงู„ุจุญุซ ู…ู† ุฎู„ุงู„ ุงู„ูƒู„ู…ุงุช ุงู„ู…ูุชุงุญูŠุฉ ู„ูŠุณ ู…ุชูˆูุฑ ุญุงู„ูŠุงู‹. ุฑุฌุงุกุงู‹ ุชูˆุงุตู„ ู…ุน ู…ุดุฑู ุงู„ู…ูˆู‚ุน. +package_kind = ุงู„ุจุญุซ ุถู…ู† ุงู„ุญุฒู…โ€ฆ +regexp_tooltip = ุชุนุงู…ู„ ู…ุน ุนุจุงุฑุฉ ุงู„ุจุญุซ ุนู„ู‰ ุฃู†ู‡ุง ุชุนุจูŠุฑ ู†ู…ุทูŠ +commit_kind = ุงู„ุจุญุซ ุถู…ู† ุงู„ุฅูŠุฏุงุนุงุชโ€ฆ union = ู…ุทุงุจู‚ุฉ ุนุงู…ุฉ -union_tooltip = ุนุฑุถ ุงู„ู†ุชุงุฆุฌ ุงู„ุชูŠ ุชุทุงุจู‚ ุฃูŠ ู…ู† ุงู„ูƒู„ู…ุงุช ุงู„ู…ูุชุงุญูŠุฉ ุงู„ู…ูุตูˆู„ุฉ ุจู…ุณุงูุงุช +runner_kind = ุงู„ุจุญุซ ุถู…ู† ุงู„ู…ุดุบู‘ูู„ุงุชโ€ฆ exact = ู…ุทุงุจู‚ exact_tooltip = ุนุฑุถ ุงู„ู†ุชุงุฆุฌ ุงู„ุชูŠ ุชุทุงุจู‚ ู…ุตุทู„ุญ ุงู„ุจุญุซ ุจุงู„ุถุจุท ูู‚ุท regexp = RegExp -regexp_tooltip = ุชุนุงู…ู„ ู…ุน ุนุจุงุฑุฉ ุงู„ุจุญุซ ุนู„ู‰ ุฃู†ู‡ุง ุชุนุจูŠุฑ ู†ู…ุทูŠ -package_kind = ุงู„ุจุญุซ ุถู…ู† ุงู„ุญุฒู…โ€ฆ -commit_kind = ุงู„ุจุญุซ ุถู…ู† ุงู„ุฅูŠุฏุงุนุงุชโ€ฆ -runner_kind = ุงู„ุจุญุซ ุถู…ู† ุงู„ู…ุดุบู‘ูู„ุงุชโ€ฆ +union_tooltip = ุนุฑุถ ุงู„ู†ุชุงุฆุฌ ุงู„ุชูŠ ุชุทุงุจู‚ ุฃูŠ ู…ู† ุงู„ูƒู„ู…ุงุช ุงู„ู…ูุชุงุญูŠุฉ ุงู„ู…ูุตูˆู„ุฉ ุจู…ุณุงูุงุช diff --git a/options/locale/locale_be.ini b/options/locale/locale_be.ini index fe04dadc3e..25aff3019c 100644 --- a/options/locale/locale_be.ini +++ b/options/locale/locale_be.ini @@ -7,10 +7,20 @@ sign_in = ะฃะฒะฐะนัั†ั– sign_in_or = ะฐะฑะพ sign_out = ะ’ั‹ะนัั†ั– sign_up = ะ—ะฐั€ัะณั–ัั‚ั€ะฐะฒะฐั†ั†ะฐ -link_account = ะ—ะฒัะทะฐั†ัŒ ะฃะปั–ะบะพะฒั‹ ะทะฐะฟั–ั +link_account = ะ—ะฒัะทะฐั†ัŒ ัƒะปั–ะบะพะฒั‹ ะทะฐะฟั–ั register = ะ ัะณั–ัั‚ั€ะฐั†ั‹ั version = ะ’ะตั€ัั–ั powered_by = ะŸั€ะฐั†ัƒะต ะฝะฐ โ„…s page = ะกั‚ะฐั€ะพะฝะบะฐ home = ะ“ะฐะปะพัžะฝะฐั ะกั‚ะฐั€ะพะฝะบะฐ -sign_in_with_provider = ะฃะฒะฐะนัั†ั– ะท %s \ No newline at end of file +sign_in_with_provider = ะฃะฒะฐะนัั†ั– ะท %s +template = ะจะฐะฑะปะพะฝ +language = ะœะพะฒะฐ +notifications = ะะฟะฐะฒััˆั‡ัะฝะฝั– +create_new = ะกั‚ะฒะฐั€ั‹ั†ัŒโ€ฆ +user_profile_and_more = ะŸั€ะพั„ั–ะปัŒ ั– ะฝะฐะปะฐะดั‹โ€ฆ +signed_in_as = ะฃะฒะฐะนัˆะพัž ัะบ +enable_javascript = ะ“ัั‚ั‹ ะฒัะฑ-ัะฐะนั‚ ะฟะฐั‚ั€ะฐะฑัƒะต JavaScript. +toc = ะ—ะผะตัั‚ +licenses = ะ›ั–ั†ัะฝะทั–ั– +return_to_forgejo = ะ’ัั€ะฝัƒั†ั†ะฐ ะดะฐ Forgejo \ No newline at end of file diff --git a/options/locale/locale_bg.ini b/options/locale/locale_bg.ini index 35e33f4430..720c4fa1e5 100644 --- a/options/locale/locale_bg.ini +++ b/options/locale/locale_bg.ini @@ -40,7 +40,6 @@ new_mirror = ะะพะฒะพ ะพะณะปะตะดะฐะปะฝะพ re_type = ะŸะพั‚ะฒัŠั€ะดะตั‚ะต ะฟะฐั€ะพะปะฐั‚ะฐ copy = ะšะพะฟะธั€ะฐะฝะต enabled = ะ’ะบะปัŽั‡ะตะฝะพ -new_org = ะะพะฒะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธั milestones = ะ•ั‚ะฐะฟะธ rss_feed = RSS ะตะผะธัะธั never = ะะธะบะพะณะฐ @@ -54,12 +53,9 @@ add_all = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะฒัะธั‡ะบะพ new_project_column = ะะพะฒะฐ ะบะพะปะพะฝะฐ add = ะ”ะพะฑะฐะฒัะฝะต organization = ะžั€ะณะฐะฝะธะทะฐั†ะธั -new_migrate = ะะพะฒะฐ ะผะธะณั€ะฐั†ะธั save = ะ—ะฐะฟะฐะทะฒะฐะฝะต sign_in_with_provider = ะ’ะปะธะทะฐะฝะต ั %s ok = ะ”ะพะฑั€ะต -manage_org = ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธะธั‚ะต -new_repo = ะะพะฒะพ ั…ั€ะฐะฝะธะปะธั‰ะต register = ะ ะตะณะธัั‚ั€ะฐั†ะธั mirror = ะžะณะปะตะดะฐะปะฝะพ username = ะŸะพั‚ั€ะตะฑะธั‚ะตะปัะบะพ ะธะผะต @@ -74,7 +70,6 @@ issues = ะ—ะฐะดะฐั‡ะธ retry = ะŸะพะฒั‚ะพั€ะตะฝ ะพะฟะธั‚ remove = ะŸั€ะตะผะฐั…ะฒะฐะฝะต admin_panel = ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ ัะฐะนั‚ะฐ -account_settings = ะะฐัั‚ั€ะพะนะบะธ ะฝะฐ ะฐะบะฐัƒะฝั‚ะฐ powered_by = ะžััŠั‰ะตัั‚ะฒะตะฝะพ ะพั‚ %s pull_requests = ะ—ะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต collaborative = ะกัŠะฒะผะตัั‚ะฝะธ @@ -141,7 +136,6 @@ webauthn_sign_in = ะะฐั‚ะธัะฝะตั‚ะต ะฑัƒั‚ะพะฝะฐ ะฝะฐ ะฒะฐัˆะธั ะบะปัŽั‡ ะทะฐ webauthn_error = ะะตัƒัะฟะตัˆะฝะพ ะฟั€ะพั‡ะธั‚ะฐะฝะต ะฝะฐ ะฒะฐัˆะธั ะบะปัŽั‡ ะทะฐ ัะธะณัƒั€ะฝะพัั‚. webauthn_unsupported_browser = ะ’ะฐัˆะธัั‚ ะฑั€ะฐัƒะทัŠั€ ะฒ ะผะพะผะตะฝั‚ะฐ ะฝะต ะฟะพะดะดัŠั€ะถะฐ WebAuthn. webauthn_error_duplicated = ะšะปัŽั‡ัŠั‚ ะทะฐ ัะธะณัƒั€ะฝะพัั‚ ะฝะต ะต ั€ะฐะทั€ะตัˆะตะฝ ะทะฐ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ. ะœะพะปั, ัƒะฒะตั€ะตั‚ะต ัะต, ั‡ะต ะบะปัŽั‡ัŠั‚ ะฝะต ะต ะฒะตั‡ะต ั€ะตะณะธัั‚ั€ะธั€ะฐะฝ. - tracked_time_summary = ะžะฑะพะฑั‰ะตะฝะธะต ะฝะฐ ะฟั€ะพัะปะตะดะตะฝะพั‚ะพ ะฒั€ะตะผะต ะฒัŠะท ะพัะฝะพะฒะฐ ะฝะฐ ั„ะธะปั‚ั€ะธั‚ะต ะฒ ัะฟะธััŠะบะฐ ััŠั ะทะฐะดะฐั‡ะธ [settings] @@ -157,11 +151,9 @@ oauth2_application_edit = ะ ะตะดะฐะบั‚ะธั€ะฐะฝะต repos = ะฅั€ะฐะฝะธะปะธั‰ะฐ can_write_info = ะŸะธัะฐะฝะต delete = ะ˜ะทั‚ั€ะธะฒะฐะฝะต ะฝะฐ ะฐะบะฐัƒะฝั‚ะฐ -social = ะกะพั†ะธะฐะปะฝะธ ะฐะบะฐัƒะฝั‚ะธ twofa = ะ”ะฒัƒั„ะฐะบั‚ะพั€ะฝะพ ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะต (TOTP) update_theme = ะŸั€ะพะผัะฝะฐ ะฝะฐ ั‚ะตะผะฐั‚ะฐ can_read_info = ะงะตั‚ะตะฝะต -access_token_deletion_confirm_action = ะ˜ะทั‚ั€ะธะฒะฐะฝะต website = ะฃะตะฑัะฐะนั‚ cancel = ะžั‚ะบะฐะท delete_token = ะ˜ะทั‚ั€ะธะฒะฐะฝะต @@ -171,19 +163,16 @@ save_application = ะ—ะฐะฟะฐะทะฒะฐะฝะต privacy = ะŸะพะฒะตั€ะธั‚ะตะปะฝะพัั‚ avatar = ะŸั€ะพั„ะธะปะฝะฐ ัะฝะธะผะบะฐ add_key = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะบะปัŽั‡ -account_link = ะกะฒัŠั€ะทะฐะฝะธ ะฐะบะฐัƒะฝั‚ะธ delete_email = ะŸั€ะตะผะฐั…ะฒะฐะฝะต update_language = ะŸั€ะพะผัะฝะฐ ะฝะฐ ะตะทะธะบะฐ organization = ะžั€ะณะฐะฝะธะทะฐั†ะธะธ link_account = ะกะฒัŠั€ะทะฒะฐะฝะต ะฝะฐ ะฐะบะฐัƒะฝั‚ -add_new_gpg_key = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ GPG ะบะปัŽั‡ manage_gpg_keys = ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ GPG ะบะปัŽั‡ะพะฒะตั‚ะต manage_ssh_keys = ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ SSH ะบะปัŽั‡ะพะฒะตั‚ะต old_password = ะขะตะบัƒั‰ะฐ ะฟะฐั€ะพะปะฐ public_profile = ะŸัƒะฑะปะธั‡ะตะฝ ะฟั€ะพั„ะธะป full_name = ะŸัŠะปะฝะพ ะธะผะต security = ะกะธะณัƒั€ะฝะพัั‚ -add_new_key = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ SSH ะบะปัŽั‡ account = ะะบะฐัƒะฝั‚ update_avatar = ะžะฑะฝะพะฒัะฒะฐะฝะต ะฝะฐ ะฟั€ะพั„ะธะปะฝะฐั‚ะฐ ัะฝะธะผะบะฐ ssh_gpg_keys = SSH / GPG ะบะปัŽั‡ะพะฒะต @@ -195,7 +184,6 @@ biography_placeholder = ะ ะฐะทะบะฐะถะตั‚ะต ะฝะฐ ะดั€ัƒะณะธั‚ะต ะผะฐะปะบะพ ะทะฐ orgs = ะžั€ะณะฐะฝะธะทะฐั†ะธะธ continue = ะŸั€ะพะดัŠะปะถะฐะฒะฐะฝะต blocked_users = ะ‘ะปะพะบะธั€ะฐะฝะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะปะธ -emails = ะะดั€ะตัะธ ะฝะฐ ะตะป. ะฟะพั‰ะฐ update_profile = ะžะฑะฝะพะฒัะฒะฐะฝะต ะฝะฐ ะฟั€ะพั„ะธะปะฐ profile = ะŸั€ะพั„ะธะป change_password = ะŸั€ะพะผัะฝะฐ ะฝะฐ ะฟะฐั€ะพะปะฐั‚ะฐ @@ -230,7 +218,6 @@ comment_type_group_title = ะ—ะฐะณะปะฐะฒะธะต comment_type_group_label = ะ•ั‚ะธะบะตั‚ change_username_prompt = ะ‘ะตะปะตะถะบะฐ: ะŸั€ะพะผัะฝะฐั‚ะฐ ะฝะฐ ะฟะพั‚ั€ะตะฑะธั‚ะตะปัะบะพั‚ะพ ะฒะธ ะธะผะต ะฟั€ะพะผะตะฝั ััŠั‰ะพ URL ะฝะฐ ะฒะฐัˆะธั ะฐะบะฐัƒะฝั‚. update_language_not_found = ะ•ะทะธะบัŠั‚ โ€ž%sโ€œ ะฝะต ะต ะฝะฐะปะธั‡ะตะฝ. -keep_activity_private_popup = ะ’ะฐัˆะฐั‚ะฐ ะดะตะนะฝะพัั‚ ั‰ะต ะฑัŠะดะต ะฒะธะดะธะผะฐ ัะฐะผะพ ะทะฐ ะฒะฐั ะธ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ะธั‚ะต ะฝะฐ ัะฐะนั‚ะฐ uploaded_avatar_not_a_image = ะšะฐั‡ะตะฝะธัั‚ ั„ะฐะนะป ะฝะต ะต ะธะทะพะฑั€ะฐะถะตะฝะธะต. uploaded_avatar_is_too_big = ะ ะฐะทะผะตั€ัŠั‚ ะฝะฐ ะบะฐั‡ะตะฝะธั ั„ะฐะนะป (%d KiB) ะฝะฐะดะฒะธัˆะฐะฒะฐ ะผะฐะบัะธะผะฐะปะฝะธั ั€ะฐะทะผะตั€ (%d KiB). change_password_success = ะŸะฐั€ะพะปะฐั‚ะฐ ะฒะธ ะต ะพะฑะฝะพะฒะตะฝะฐ. ะžั‚ัะตะณะฐ ะฝะฐั‚ะฐั‚ัŠะบ ะธะทะฟะพะปะทะฒะฐะนั‚ะต ะฝะพะฒะฐั‚ะฐ ัะธ ะฟะฐั€ะพะปะฐ, ะทะฐ ะดะฐ ะฒะปะตะทะตั‚ะต. @@ -280,13 +267,10 @@ confirm_delete_account = ะŸะพั‚ะฒัŠั€ะถะดะฐะฒะฐะฝะต ะฝะฐ ะธะทั‚ั€ะธะฒะฐะฝะตั‚ะพ email_notifications.onmention = ะ•ะป. ะฟะธัะผะพ ัะฐะผะพ ะฟั€ะธ ัะฟะพะผะตะฝะฐะฒะฐะฝะต pronouns_unspecified = ะะตะฟะพัะพั‡ะตะฝะธ pronouns = ะœะตัั‚ะพะธะผะตะฝะธั -gpg_token_code = echo "%s" | gpg -a --default-key %s --detach-sig language.title = ะ•ะทะธะบ ะฟะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต language.localization_project = ะŸะพะผะพะณะฝะตั‚ะต ะฝะธ ะดะฐ ะฟั€ะตะฒะตะดะตะผ Forgejo ะฝะฐ ะฒะฐัˆะธั ะตะทะธะบ! ะะฐัƒั‡ะตั‚ะต ะฟะพะฒะตั‡ะต. language.description = ะขะพะทะธ ะตะทะธะบ ั‰ะต ะฑัŠะดะต ะทะฐะฟะฐะทะตะฝ ะฒัŠะฒ ะฒะฐัˆะธั ะฐะบะฐัƒะฝั‚ ะธ ั‰ะต ัะต ะธะทะฟะพะปะทะฒะฐ ะบะฐั‚ะพ ะตะทะธะบ ะฟะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต, ัะปะตะด ะบะฐั‚ะพ ะฒะปะตะทะตั‚ะต. -pronouns_custom = ะŸะตั€ัะพะฝะฐะปะธะทะธั€ะฐะฝะธ visibility.limited_tooltip = ะ’ะธะดะธะผ ัะฐะผะพ ะทะฐ ะฒะปะตะทะปะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะปะธ -pronouns_custom_label = ะŸะตั€ัะพะฝะฐะปะธะทะธั€ะฐะฝะธ ะผะตัั‚ะพะธะผะตะฝะธั comment_type_group_review_request = ะ˜ัะบะฐะฝะต ะทะฐ ั€ะตั†ะตะฝะทะธั ssh_key_been_used = ะขะพะทะธ SSH ะบะปัŽั‡ ะฒะตั‡ะต ะต ะดะพะฑะฐะฒะตะฝ ะบัŠะผ ััŠั€ะฒัŠั€ะฐ. create_oauth2_application = ะกัŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะฝะพะฒะพ OAuth2 ะฟั€ะธะปะพะถะตะฝะธะต @@ -373,7 +357,7 @@ add_email_confirmation_sent = ะ˜ะทะฟั€ะฐั‚ะตะฝะพ ะต ะตะป. ะฟะธัะผะพ ะทะฐ ะฟะพั‚ additional_repo_units_hint_description = ะŸะพะบะฐะทะฒะฐะฝะต ะฝะฐ ะฟะพะดัะบะฐะทะบะฐ โ€žะ’ะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ะฟะพะฒะตั‡ะตโ€œ ะทะฐ ั…ั€ะฐะฝะธะปะธั‰ะฐ, ะบะพะธั‚ะพ ะฝัะผะฐั‚ ะฒะบะปัŽั‡ะตะฝะธ ะฒัะธั‡ะบะธ ะฝะฐะปะธั‡ะฝะธ ะตะปะตะผะตะฝั‚ะธ. email_notifications.submit = ะ—ะฐะดะฐะฒะฐะฝะต ะฝะฐ ะฟั€ะตะดะฟะพั‡ะธั‚. ะทะฐ ะตะป. ะฟะพั‰ะฐ email_notifications.andyourown = ะ˜ ะฒะฐัˆะธั‚ะต ัะพะฑัั‚ะฒะตะฝะธ ะธะทะฒะตัั‚ะธั -email_deletion_desc = ะะดั€ะตััŠั‚ ะทะฐ ะตะป. ะฟะพั‰ะฐ ะธ ัะฒัŠั€ะทะฐะฝะฐั‚ะฐ ะธะฝั„ะพั€ะผะฐั†ะธั ั‰ะต ะฑัŠะดะฐั‚ ะฟั€ะตะผะฐั…ะฝะฐั‚ะธ ะพั‚ ะฒะฐัˆะธั ะฐะบะฐัƒะฝั‚. Git ะฟะพะดะฐะฒะฐะฝะธัั‚ะฐ ะพั‚ ั‚ะพะทะธ ะฐะดั€ะตั ะทะฐ ะตะป. ะฟะพั‰ะฐ ั‰ะต ะพัั‚ะฐะฝะฐั‚ ะฝะตะฟั€ะพะผะตะฝะตะฝะธ. ะŸั€ะพะดัŠะปะถะฐะฒะฐะฝะต? +email_deletion_desc = ะขะพะทะธ ะฐะดั€ะตั ะทะฐ ะตะป. ะฟะพั‰ะฐ ะธ ัะฒัŠั€ะทะฐะฝะฐั‚ะฐ ะธะฝั„ะพั€ะผะฐั†ะธั ั‰ะต ะฑัŠะดะฐั‚ ะฟั€ะตะผะฐั…ะฝะฐั‚ะธ ะพั‚ ะฒะฐัˆะธั ะฐะบะฐัƒะฝั‚. Git ะฟะพะดะฐะฒะฐะฝะธัั‚ะฐ ะพั‚ ั‚ะพะทะธ ะฐะดั€ะตั ะทะฐ ะตะป. ะฟะพั‰ะฐ ั‰ะต ะพัั‚ะฐะฝะฐั‚ ะฝะตะฟั€ะพะผะตะฝะตะฝะธ. ะŸั€ะพะดัŠะปะถะฐะฒะฐะฝะต? add_email_success = ะะพะฒะธัั‚ ะฐะดั€ะตั ะทะฐ ะตะป. ะฟะพั‰ะฐ ะต ะดะพะฑะฐะฒะตะฝ. remove_account_link = ะŸั€ะตะผะฐั…ะฒะฐะฝะต ะฝะฐ ัะฒัŠั€ะทะฐะฝ ะฐะบะฐัƒะฝั‚ webauthn_alternative_tip = ะœะพะถะต ะดะฐ ะธัะบะฐั‚ะต ะดะฐ ะบะพะฝั„ะธะณัƒั€ะธั€ะฐั‚ะต ะดะพะฟัŠะปะฝะธั‚ะตะปะตะฝ ะผะตั‚ะพะด ะทะฐ ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะต. @@ -382,20 +366,19 @@ hidden_comment_types = ะกะบั€ะธั‚ะธ ั‚ะธะฟะพะฒะต ะบะพะผะตะฝั‚ะฐั€ะธ comment_type_group_lock = ะกัŠัั‚ะพัะฝะธะต ะฝะฐ ะทะฐะบะปัŽั‡ะฒะฐะฝะต can_not_add_email_activations_pending = ะ˜ะผะฐ ั‡ะฐะบะฐั‰ะฐ ะฐะบั‚ะธะฒะฐั†ะธั, ะพะฟะธั‚ะฐะนั‚ะต ะพั‚ะฝะพะฒะพ ัะปะตะด ะฝัะบะพะปะบะพ ะผะธะฝัƒั‚ะธ, ะฐะบะพ ะธัะบะฐั‚ะต ะดะฐ ะดะพะฑะฐะฒะธั‚ะต ะฝะพะฒะฐ ะตะป. ะฟะพั‰ะฐ. storage_overview = ะŸั€ะตะณะปะตะด ะฝะฐ ััŠั…ั€ะฐะฝะตะฝะธะตั‚ะพ - webauthn = ะ”ะฒัƒั„ะฐะบั‚ะพั€ะฝะพ ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะต (ะšะปัŽั‡ะพะฒะต ะทะฐ ัะธะณัƒั€ะฝะพัั‚) -quota.sizes.all = ะ’ัะธั‡ะบะธ -quota.sizes.repos.all = ะฅั€ะฐะฝะธะปะธั‰ะฐ quota.sizes.repos.public = ะŸัƒะฑะปะธั‡ะฝะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ quota.sizes.repos.private = ะงะฐัั‚ะฝะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ quota.sizes.git.all = Git ััŠะดัŠั€ะถะฐะฝะธะต quota.sizes.git.lfs = Git LFS quota.sizes.assets.attachments.all = ะŸั€ะธะบะฐั‡ะตะฝะธ ั„ะฐะนะปะพะฒะต -quota.sizes.assets.attachments.issues = ะŸั€ะธะบะฐั‡ะตะฝะธ ั„ะฐะนะปะพะฒะต ะบัŠะผ ะทะฐะดะฐั‡ะธ quota.sizes.assets.attachments.releases = ะŸั€ะธะบะฐั‡ะตะฝะธ ั„ะฐะนะปะพะฒะต ะบัŠะผ ะธะทะดะฐะฝะธั quota.sizes.assets.artifacts = ะั€ั‚ะตั„ะฐะบั‚ะธ quota.sizes.assets.packages.all = ะŸะฐะบะตั‚ะธ quota.sizes.wiki = ะฃะธะบะธ +quota.sizes.all = ะ’ัะธั‡ะบะธ +quota.sizes.repos.all = ะฅั€ะฐะฝะธะปะธั‰ะฐ +quota.sizes.assets.attachments.issues = ะŸั€ะธะบะฐั‡ะตะฝะธ ั„ะฐะนะปะพะฒะต ะบัŠะผ ะทะฐะดะฐั‡ะธ [packages] container.labels.value = ะกั‚ะพะนะฝะพัั‚ @@ -454,113 +437,112 @@ details.documentation_site = ะฃะตะฑัะฐะนั‚ ะฝะฐ ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัั‚ะฐ arch.version.conflicts = ะ’ ะบะพะฝั„ะปะธะบั‚ alpine.repository.branches = ะšะปะพะฝะพะฒะต arch.pacman.repo.multi.item = ะšะพะฝั„ะธะณัƒั€ะฐั†ะธั ะทะฐ %s - -desc = ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ ะฟะฐะบะตั‚ะธั‚ะต ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. -alpine.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€, ะบะฐั‚ะพ ะดะพะฑะฐะฒะธั‚ะต URL ะฐะดั€ะตัะฐ ะฒัŠะฒ ะฒะฐัˆะธั ั„ะฐะนะป /etc/apk/repositories: -alpine.registry.key = ะ˜ะทั‚ะตะณะปะตั‚ะต ะฟัƒะฑะปะธั‡ะฝะธั RSA ะบะปัŽั‡ ะฝะฐ ั€ะตะณะธัั‚ัŠั€ะฐ ะฒ ะฟะฐะฟะบะฐั‚ะฐ /etc/apk/keys/, ะทะฐ ะดะฐ ะฟั€ะพะฒะตั€ะธั‚ะต ะฟะพะดะฟะธัะฐ ะฝะฐ ะธะฝะดะตะบัะฐ: -alpine.registry.info = ะ˜ะทะฑะตั€ะตั‚ะต $branch ะธ $repository ะพั‚ ัะฟะธััŠะบะฐ ะฟะพ-ะดะพะปัƒ. -alpine.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -arch.version.properties = ะกะฒะพะนัั‚ะฒะฐ ะฝะฐ ะฒะตั€ัะธัั‚ะฐ -arch.version.makedepends = ะ—ะฐะฒะธัะธะผะพัั‚ะธ ะทะฐ ะธะทะณั€ะฐะถะดะฐะฝะตั‚ะพ -arch.version.checkdepends = ะ—ะฐะฒะธัะธะผะพัั‚ะธ ะทะฐ ะฟั€ะพะฒะตั€ะบะฐั‚ะฐ -chef.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะฒัŠะฒ ะฒะฐัˆะธั ั„ะฐะนะป ~/.chef/config.rb: -chef.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -composer.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะฒัŠะฒ ะฒะฐัˆะธั ั„ะฐะนะป ~/.composer/config.json: -composer.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั Composer, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -composer.dependencies = ะ—ะฐะฒะธัะธะผะพัั‚ะธ -conan.details.repository = ะฅั€ะฐะฝะธะปะธั‰ะต -conan.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: -conan.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั Conan, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -conda.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะบะฐั‚ะพ Conda ั…ั€ะฐะฝะธะปะธั‰ะต ะฒัŠะฒ ะฒะฐัˆะธั ั„ะฐะนะป .condarc: -conda.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั Conda, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -container.pull = ะ˜ะทะดัŠั€ะฟะฐะนั‚ะต ะพะฑั€ะฐะทะฐ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: container.multi_arch = ะžะก / ะั€ั…ะธั‚ะตะบั‚ัƒั€ะฐ -container.layers = ะกะปะพะตะฒะต ะฝะฐ ะพะฑั€ะฐะทะฐ -cran.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะฒัŠะฒ ะฒะฐัˆะธั ั„ะฐะนะป Rprofile.site: -cran.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -debian.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: -debian.registry.info = ะ˜ะทะฑะตั€ะตั‚ะต $distribution ะธ $component ะพั‚ ัะฟะธััŠะบะฐ ะฟะพ-ะดะพะปัƒ. -debian.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -debian.repository = ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะทะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ -debian.repository.distributions = ะ”ะธัั‚ั€ะธะฑัƒั†ะธะธ -debian.repository.components = ะšะพะผะฟะพะฝะตะฝั‚ะธ -debian.repository.architectures = ะั€ั…ะธั‚ะตะบั‚ัƒั€ะธ -helm.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: -helm.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -maven.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะฒัŠะฒ ั„ะฐะนะปะฐ ะฝะฐ ะฒะฐัˆะธั ะฟั€ะพะตะบั‚ pom.xml: -maven.install = ะ—ะฐ ะดะฐ ะธะทะฟะพะปะทะฒะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะฒะบะปัŽั‡ะตั‚ะต ัะปะตะดะฝะพั‚ะพ ะฒ ะฑะปะพะบะฐ dependencies ะฒัŠะฒ ั„ะฐะนะปะฐ pom.xml: -maven.install2 = ะ˜ะทะฟัŠะปะฝะตั‚ะต ะฟั€ะตะท ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: -maven.download = ะ—ะฐ ะดะฐ ะธะทั‚ะตะณะปะธั‚ะต ะทะฐะฒะธัะธะผะพัั‚ั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ะฟั€ะตะท ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: -nuget.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: -nuget.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั NuGet, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -nuget.dependency.framework = ะฆะตะปะตะฒะฐ ะฟะปะฐั‚ั„ะพั€ะผะฐ -npm.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะฒัŠะฒ ั„ะฐะนะปะฐ ะฝะฐ ะฒะฐัˆะธั ะฟั€ะพะตะบั‚ .npmrc: -npm.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั npm, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -npm.install2 = ะธะปะธ ะณะพ ะดะพะฑะฐะฒะตั‚ะต ะฒัŠะฒ ั„ะฐะนะปะฐ package.json: -npm.dependencies.optional = ะžะฟั†ะธะพะฝะฐะปะฝะธ ะทะฐะฒะธัะธะผะพัั‚ะธ -npm.details.tag = ะœะฐั€ะบะตั€ -pub.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั Dart, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -pypi.requires = ะ˜ะทะธัะบะฒะฐ Python -pypi.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั pip, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -rpm.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: -rpm.distros.redhat = ะฝะฐ ะดะธัั‚ั€ะธะฑัƒั†ะธะธ, ะฑะฐะทะธั€ะฐะฝะธ ะฝะฐ RedHat -rpm.distros.suse = ะฝะฐ ะดะธัั‚ั€ะธะฑัƒั†ะธะธ, ะฑะฐะทะธั€ะฐะฝะธ ะฝะฐ SUSE -rpm.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: rpm.repository = ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะทะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ -rpm.repository.architectures = ะั€ั…ะธั‚ะตะบั‚ัƒั€ะธ -rpm.repository.multiple_groups = ะขะพะทะธ ะฟะฐะบะตั‚ ะต ะฝะฐะปะธั‡ะตะฝ ะฒ ะฝัะบะพะปะบะพ ะณั€ัƒะฟะธ. +container.pull = ะ˜ะทะดัŠั€ะฟะฐะนั‚ะต ะพะฑั€ะฐะทะฐ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: +helm.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: +debian.repository.distributions = ะ”ะธัั‚ั€ะธะฑัƒั†ะธะธ +npm.dependencies.optional = ะžะฟั†ะธะพะฝะฐะปะฝะธ ะทะฐะฒะธัะธะผะพัั‚ะธ +owner.settings.cargo.title = ะ˜ะฝะดะตะบั ะฝะฐ ั€ะตะณะธัั‚ัŠั€ะฐ ะฝะฐ Cargo +owner.settings.cleanuprules.keep.pattern.container = ะ’ะตั€ัะธัั‚ะฐ latest ะฒะธะฝะฐะณะธ ัะต ะทะฐะฟะฐะทะฒะฐ ะทะฐ Container ะฟะฐะบะตั‚ะธ. +owner.settings.cleanuprules.remove.pattern = ะŸั€ะตะผะฐั…ะฒะฐะฝะต ะฝะฐ ะฒะตั€ัะธะธ, ััŠะพั‚ะฒะตั‚ัั‚ะฒะฐั‰ะธ ะฝะฐ +rpm.distros.suse = ะฝะฐ ะดะธัั‚ั€ะธะฑัƒั†ะธะธ, ะฑะฐะทะธั€ะฐะฝะธ ะฝะฐ SUSE +owner.settings.cleanuprules.preview.overview = %d ะฟะฐะบะตั‚ะฐ ัะฐ ะฝะฐัั€ะพั‡ะตะฝะธ ะทะฐ ะฟั€ะตะผะฐั…ะฒะฐะฝะต. +owner.settings.cleanuprules.preview = ะŸั€ะตะณะปะตะด ะฝะฐ ะฟั€ะฐะฒะธะปะพ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต +arch.version.properties = ะกะฒะพะนัั‚ะฒะฐ ะฝะฐ ะฒะตั€ัะธัั‚ะฐ +conan.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: +conan.details.repository = ะฅั€ะฐะฝะธะปะธั‰ะต +composer.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั Composer, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +chef.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +chef.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะฒัŠะฒ ะฒะฐัˆะธั ั„ะฐะนะป ~/.chef/config.rb: +pub.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั Dart, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +npm.details.tag = ะœะฐั€ะบะตั€ +npm.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั npm, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +maven.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะฒัŠะฒ ั„ะฐะนะปะฐ ะฝะฐ ะฒะฐัˆะธั ะฟั€ะพะตะบั‚ pom.xml: +debian.repository.components = ะšะพะผะฟะพะฝะตะฝั‚ะธ +debian.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +cran.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +cran.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะฒัŠะฒ ะฒะฐัˆะธั ั„ะฐะนะป Rprofile.site: +rpm.distros.redhat = ะฝะฐ ะดะธัั‚ั€ะธะฑัƒั†ะธะธ, ะฑะฐะทะธั€ะฐะฝะธ ะฝะฐ RedHat alt.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: +rpm.repository.architectures = ะั€ั…ะธั‚ะตะบั‚ัƒั€ะธ alt.registry.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -alt.install = ะ˜ะฝัั‚ะฐะปะธั€ะฐะฝะต ะฝะฐ ะฟะฐะบะตั‚ alt.setup = ะ”ะพะฑะฐะฒะตั‚ะต ั…ั€ะฐะฝะธะปะธั‰ะต ะบัŠะผ ัะฟะธััŠะบะฐ ััŠั ัะฒัŠั€ะทะฐะฝะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ (ะธะทะฑะตั€ะตั‚ะต ะฝะตะพะฑั…ะพะดะธะผะฐั‚ะฐ ะฐั€ั…ะธั‚ะตะบั‚ัƒั€ะฐ ะฒะผะตัั‚ะพ โ€ž_arch_โ€œ): alt.repository = ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะทะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ -alt.repository.architectures = ะั€ั…ะธั‚ะตะบั‚ัƒั€ะธ +owner.settings.cargo.initialize.error = ะะตัƒัะฟะตัˆะฝะพ ะธะฝะธั†ะธะฐะปะธะทะธั€ะฐะฝะต ะฝะฐ ะธะฝะดะตะบัะฐ ะฝะฐ Cargo: %v +owner.settings.cargo.initialize = ะ˜ะฝะธั†ะธะฐะปะธะทะธั€ะฐะฝะต ะฝะฐ ะธะฝะดะตะบั +settings.delete.description = ะ˜ะทั‚ั€ะธะฒะฐะฝะตั‚ะพ ะฝะฐ ะฟะฐะบะตั‚ ะต ั‚ั€ะฐะนะฝะพ ะธ ะฝะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ะพั‚ะผะตะฝะตะฝะพ. alt.repository.multiple_groups = ะขะพะทะธ ะฟะฐะบะตั‚ ะต ะฝะฐะปะธั‡ะตะฝ ะฒ ะฝัะบะพะปะบะพ ะณั€ัƒะฟะธ. -swift.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: +alt.repository.architectures = ะั€ั…ะธั‚ะตะบั‚ัƒั€ะธ +owner.settings.chef.title = ะ ะตะณะธัั‚ัŠั€ ะฝะฐ Chef +owner.settings.cleanuprules.remove.days = ะŸั€ะตะผะฐั…ะฒะฐะฝะต ะฝะฐ ะฒะตั€ัะธะธ, ะฟะพ-ัั‚ะฐั€ะธ ะพั‚ +owner.settings.cleanuprules.keep.pattern = ะ—ะฐะฟะฐะทะฒะฐะฝะต ะฝะฐ ะฒะตั€ัะธะธ, ััŠะพั‚ะฒะตั‚ัั‚ะฒะฐั‰ะธ ะฝะฐ +owner.settings.cleanuprules.keep.count.n = %d ะฒะตั€ัะธะธ ะฝะฐ ะฟะฐะบะตั‚ +owner.settings.cleanuprules.keep.count.1 = 1 ะฒะตั€ัะธั ะฝะฐ ะฟะฐะบะตั‚ +owner.settings.cleanuprules.keep.count = ะ—ะฐะฟะฐะทะฒะฐะฝะต ะฝะฐ ะฝะฐะน-ะฝะพะฒะธั‚ะต +owner.settings.cleanuprules.enabled = ะ’ะบะปัŽั‡ะตะฝะพ +owner.settings.cleanuprules.preview.none = ะŸั€ะฐะฒะธะปะพั‚ะพ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต ะฝะต ััŠะฒะฟะฐะดะฐ ั ะฝะธั‚ะพ ะตะดะธะฝ ะฟะฐะบะตั‚. +owner.settings.cleanuprules.none = ะ’ัะต ะพั‰ะต ะฝัะผะฐ ะฟั€ะฐะฒะธะปะฐ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต. +owner.settings.cleanuprules.add = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะฟั€ะฐะฒะธะปะพ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต +owner.settings.cleanuprules.title = ะŸั€ะฐะฒะธะปะฐ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต +owner.settings.cargo.rebuild.success = ะ˜ะฝะดะตะบััŠั‚ ะฝะฐ Cargo ะฑะตัˆะต ัƒัะฟะตัˆะฝะพ ะฟั€ะตะธะทะณั€ะฐะดะตะฝ. +alpine.registry.key = ะ˜ะทั‚ะตะณะปะตั‚ะต ะฟัƒะฑะปะธั‡ะฝะธั RSA ะบะปัŽั‡ ะฝะฐ ั€ะตะณะธัั‚ัŠั€ะฐ ะฒ ะฟะฐะฟะบะฐั‚ะฐ /etc/apk/keys/, ะทะฐ ะดะฐ ะฟั€ะพะฒะตั€ะธั‚ะต ะฟะพะดะฟะธัะฐ ะฝะฐ ะธะฝะดะตะบัะฐ: +alpine.registry.info = ะ˜ะทะฑะตั€ะตั‚ะต $branch ะธ $repository ะพั‚ ัะฟะธััŠะบะฐ ะฟะพ-ะดะพะปัƒ. +arch.version.checkdepends = ะ—ะฐะฒะธัะธะผะพัั‚ะธ ะทะฐ ะฟั€ะพะฒะตั€ะบะฐั‚ะฐ +composer.dependencies = ะ—ะฐะฒะธัะธะผะพัั‚ะธ swift.install = ะ”ะพะฑะฐะฒะตั‚ะต ะฟะฐะบะตั‚ะฐ ะฒัŠะฒ ะฒะฐัˆะธั ั„ะฐะนะป Package.swift: +settings.link.error = ะะตัƒัะฟะตัˆะฝะพ ะพะฑะฝะพะฒัะฒะฐะฝะต ะฝะฐ ะฒั€ัŠะทะบะฐั‚ะฐ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. swift.install2 = ะธ ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: -vagrant.install = ะ—ะฐ ะดะฐ ะดะพะฑะฐะฒะธั‚ะต Vagrant box, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +rpm.repository.multiple_groups = ะขะพะทะธ ะฟะฐะบะตั‚ ะต ะฝะฐะปะธั‡ะตะฝ ะฒ ะฝัะบะพะปะบะพ ะณั€ัƒะฟะธ. +conda.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะบะฐั‚ะพ Conda ั…ั€ะฐะฝะธะปะธั‰ะต ะฒัŠะฒ ะฒะฐัˆะธั ั„ะฐะนะป .condarc: +conda.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั Conda, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +owner.settings.cargo.rebuild.error = ะะตัƒัะฟะตัˆะฝะพ ะฟั€ะตะธะทะณั€ะฐะถะดะฐะฝะต ะฝะฐ ะธะฝะดะตะบัะฐ ะฝะฐ Cargo: %v +owner.settings.cargo.rebuild = ะŸั€ะตะธะทะณั€ะฐะถะดะฐะฝะต ะฝะฐ ะธะฝะดะตะบั +settings.link.button = ะžะฑะฝะพะฒัะฒะฐะฝะต ะฝะฐ ะฒั€ัŠะทะบะฐั‚ะฐ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ +settings.link.select = ะ˜ะทะฑะตั€ะตั‚ะต ั…ั€ะฐะฝะธะปะธั‰ะต +debian.repository.architectures = ะั€ั…ะธั‚ะตะบั‚ัƒั€ะธ +rpm.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: +debian.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: +helm.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +swift.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: settings.link = ะกะฒัŠั€ะทะฒะฐะฝะต ะฝะฐ ั‚ะพะทะธ ะฟะฐะบะตั‚ ั ั…ั€ะฐะฝะธะปะธั‰ะต settings.link.description = ะะบะพ ัะฒัŠั€ะถะตั‚ะต ะฟะฐะบะตั‚ ั ั…ั€ะฐะฝะธะปะธั‰ะต, ะฟะฐะบะตั‚ัŠั‚ ัะต ะธะทะฑั€ะพัะฒะฐ ะฒ ัะฟะธััŠะบะฐ ั ะฟะฐะบะตั‚ะธ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. -settings.link.select = ะ˜ะทะฑะตั€ะตั‚ะต ั…ั€ะฐะฝะธะปะธั‰ะต -settings.link.button = ะžะฑะฝะพะฒัะฒะฐะฝะต ะฝะฐ ะฒั€ัŠะทะบะฐั‚ะฐ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ settings.link.success = ะ’ั€ัŠะทะบะฐั‚ะฐ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ะฑะตัˆะต ัƒัะฟะตัˆะฝะพ ะพะฑะฝะพะฒะตะฝะฐ. -settings.link.error = ะะตัƒัะฟะตัˆะฝะพ ะพะฑะฝะพะฒัะฒะฐะฝะต ะฝะฐ ะฒั€ัŠะทะบะฐั‚ะฐ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. -settings.delete.description = ะ˜ะทั‚ั€ะธะฒะฐะฝะตั‚ะพ ะฝะฐ ะฟะฐะบะตั‚ ะต ั‚ั€ะฐะนะฝะพ ะธ ะฝะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ะพั‚ะผะตะฝะตะฝะพ. -settings.delete.notice = ะะฐ ะฟัŠั‚ ัั‚ะต ะดะฐ ะธะทั‚ั€ะธะตั‚ะต %s (%s). ะขะฐะทะธ ะพะฟะตั€ะฐั†ะธั ะต ะฝะตะพะฑั€ะฐั‚ะธะผะฐ, ัะธะณัƒั€ะฝะธ ะปะธ ัั‚ะต? -owner.settings.cargo.title = ะ˜ะฝะดะตะบั ะฝะฐ ั€ะตะณะธัั‚ัŠั€ะฐ ะฝะฐ Cargo -owner.settings.cargo.initialize = ะ˜ะฝะธั†ะธะฐะปะธะทะธั€ะฐะฝะต ะฝะฐ ะธะฝะดะตะบั -owner.settings.cargo.initialize.description = ะะตะพะฑั…ะพะดะธะผะพ ะต ัะฟะตั†ะธะฐะปะฝะพ Git ั…ั€ะฐะฝะธะปะธั‰ะต ะทะฐ ะธะฝะดะตะบั, ะทะฐ ะดะฐ ัะต ะธะทะฟะพะปะทะฒะฐ ั€ะตะณะธัั‚ัŠั€ัŠั‚ ะฝะฐ Cargo. ะ˜ะทะฟะพะปะทะฒะฐะฝะตั‚ะพ ะฝะฐ ั‚ะฐะทะธ ะพะฟั†ะธั ั‰ะต (ะฟั€ะต)ััŠะทะดะฐะดะต ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ะธ ั‰ะต ะณะพ ะบะพะฝั„ะธะณัƒั€ะธั€ะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ. -owner.settings.cargo.initialize.error = ะะตัƒัะฟะตัˆะฝะพ ะธะฝะธั†ะธะฐะปะธะทะธั€ะฐะฝะต ะฝะฐ ะธะฝะดะตะบัะฐ ะฝะฐ Cargo: %v -owner.settings.cargo.initialize.success = ะ˜ะฝะดะตะบััŠั‚ ะฝะฐ Cargo ะฑะตัˆะต ัƒัะฟะตัˆะฝะพ ััŠะทะดะฐะดะตะฝ. -owner.settings.cargo.rebuild = ะŸั€ะตะธะทะณั€ะฐะถะดะฐะฝะต ะฝะฐ ะธะฝะดะตะบั -owner.settings.cargo.rebuild.description = ะŸั€ะตะธะทะณั€ะฐะถะดะฐะฝะตั‚ะพ ะผะพะถะต ะดะฐ ะฑัŠะดะต ะฟะพะปะตะทะฝะพ, ะฐะบะพ ะธะฝะดะตะบััŠั‚ ะฝะต ะต ัะธะฝั…ั€ะพะฝะธะทะธั€ะฐะฝ ััŠั ััŠั…ั€ะฐะฝะตะฝะธั‚ะต Cargo ะฟะฐะบะตั‚ะธ. -owner.settings.cargo.rebuild.error = ะะตัƒัะฟะตัˆะฝะพ ะฟั€ะตะธะทะณั€ะฐะถะดะฐะฝะต ะฝะฐ ะธะฝะดะตะบัะฐ ะฝะฐ Cargo: %v -owner.settings.cargo.rebuild.success = ะ˜ะฝะดะตะบััŠั‚ ะฝะฐ Cargo ะฑะตัˆะต ัƒัะฟะตัˆะฝะพ ะฟั€ะตะธะทะณั€ะฐะดะตะฝ. -owner.settings.cargo.rebuild.no_index = ะะต ะผะพะถะต ะดะฐ ัะต ะฟั€ะตะธะทะณั€ะฐะดะธ, ะฝัะผะฐ ะธะฝะธั†ะธะฐะปะธะทะธั€ะฐะฝ ะธะฝะดะตะบั. -owner.settings.cleanuprules.title = ะŸั€ะฐะฒะธะปะฐ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต -owner.settings.cleanuprules.add = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะฟั€ะฐะฒะธะปะพ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต -owner.settings.cleanuprules.edit = ะ ะตะดะฐะบั‚ะธั€ะฐะฝะต ะฝะฐ ะฟั€ะฐะฒะธะปะพั‚ะพ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต -owner.settings.cleanuprules.none = ะ’ัะต ะพั‰ะต ะฝัะผะฐ ะฟั€ะฐะฒะธะปะฐ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต. -owner.settings.cleanuprules.preview = ะŸั€ะตะณะปะตะด ะฝะฐ ะฟั€ะฐะฒะธะปะพ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต -owner.settings.cleanuprules.preview.overview = %d ะฟะฐะบะตั‚ะฐ ัะฐ ะฝะฐัั€ะพั‡ะตะฝะธ ะทะฐ ะฟั€ะตะผะฐั…ะฒะฐะฝะต. -owner.settings.cleanuprules.preview.none = ะŸั€ะฐะฒะธะปะพั‚ะพ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต ะฝะต ััŠะฒะฟะฐะดะฐ ั ะฝะธั‚ะพ ะตะดะธะฝ ะฟะฐะบะตั‚. -owner.settings.cleanuprules.enabled = ะ’ะบะปัŽั‡ะตะฝะพ owner.settings.cleanuprules.pattern_full_match = ะŸั€ะธะปะฐะณะฐะฝะต ะฝะฐ ัˆะฐะฑะปะพะฝะฐ ะบัŠะผ ะฟัŠะปะฝะพั‚ะพ ะธะผะต ะฝะฐ ะฟะฐะบะตั‚ะฐ owner.settings.cleanuprules.keep.title = ะ’ะตั€ัะธะธั‚ะต, ะบะพะธั‚ะพ ััŠะพั‚ะฒะตั‚ัั‚ะฒะฐั‚ ะฝะฐ ั‚ะตะทะธ ะฟั€ะฐะฒะธะปะฐ, ัะต ะทะฐะฟะฐะทะฒะฐั‚, ะดะพั€ะธ ะฐะบะพ ััŠะพั‚ะฒะตั‚ัั‚ะฒะฐั‚ ะฝะฐ ะฟั€ะฐะฒะธะปะพ ะทะฐ ะฟั€ะตะผะฐั…ะฒะฐะฝะต ะฟะพ-ะดะพะปัƒ. -owner.settings.cleanuprules.keep.count = ะ—ะฐะฟะฐะทะฒะฐะฝะต ะฝะฐ ะฝะฐะน-ะฝะพะฒะธั‚ะต -owner.settings.cleanuprules.keep.count.1 = 1 ะฒะตั€ัะธั ะฝะฐ ะฟะฐะบะตั‚ -owner.settings.cleanuprules.keep.count.n = %d ะฒะตั€ัะธะธ ะฝะฐ ะฟะฐะบะตั‚ -owner.settings.cleanuprules.keep.pattern = ะ—ะฐะฟะฐะทะฒะฐะฝะต ะฝะฐ ะฒะตั€ัะธะธ, ััŠะพั‚ะฒะตั‚ัั‚ะฒะฐั‰ะธ ะฝะฐ -owner.settings.cleanuprules.keep.pattern.container = ะ’ะตั€ัะธัั‚ะฐ latest ะฒะธะฝะฐะณะธ ัะต ะทะฐะฟะฐะทะฒะฐ ะทะฐ Container ะฟะฐะบะตั‚ะธ. -owner.settings.cleanuprules.remove.title = ะ’ะตั€ัะธะธั‚ะต, ะบะพะธั‚ะพ ััŠะพั‚ะฒะตั‚ัั‚ะฒะฐั‚ ะฝะฐ ั‚ะตะทะธ ะฟั€ะฐะฒะธะปะฐ, ัะต ะฟั€ะตะผะฐั…ะฒะฐั‚, ะพัะฒะตะฝ ะฐะบะพ ะฟั€ะฐะฒะธะปะพ ะฟะพ-ะณะพั€ะต ะฝะต ะบะฐะทะฒะฐ ะดะฐ ัะต ะทะฐะฟะฐะทัั‚. -owner.settings.cleanuprules.remove.days = ะŸั€ะตะผะฐั…ะฒะฐะฝะต ะฝะฐ ะฒะตั€ัะธะธ, ะฟะพ-ัั‚ะฐั€ะธ ะพั‚ -owner.settings.cleanuprules.remove.pattern = ะŸั€ะตะผะฐั…ะฒะฐะฝะต ะฝะฐ ะฒะตั€ัะธะธ, ััŠะพั‚ะฒะตั‚ัั‚ะฒะฐั‰ะธ ะฝะฐ -owner.settings.cleanuprules.success.update = ะŸั€ะฐะฒะธะปะพั‚ะพ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต ะต ะพะฑะฝะพะฒะตะฝะพ. -owner.settings.cleanuprules.success.delete = ะŸั€ะฐะฒะธะปะพั‚ะพ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต ะต ะธะทั‚ั€ะธั‚ะพ. -owner.settings.chef.title = ะ ะตะณะธัั‚ัŠั€ ะฝะฐ Chef +debian.repository = ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะทะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ +maven.install = ะ—ะฐ ะดะฐ ะธะทะฟะพะปะทะฒะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะฒะบะปัŽั‡ะตั‚ะต ัะปะตะดะฝะพั‚ะพ ะฒ ะฑะปะพะบะฐ dependencies ะฒัŠะฒ ั„ะฐะนะปะฐ pom.xml: +nuget.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั NuGet, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +alt.install = ะ˜ะฝัั‚ะฐะปะธั€ะฐะฝะต ะฝะฐ ะฟะฐะบะตั‚ +owner.settings.cleanuprules.edit = ะ ะตะดะฐะบั‚ะธั€ะฐะฝะต ะฝะฐ ะฟั€ะฐะฒะธะปะพั‚ะพ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต +rpm.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +pypi.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั pip, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +arch.version.makedepends = ะ—ะฐะฒะธัะธะผะพัั‚ะธ ะทะฐ ะธะทะณั€ะฐะถะดะฐะฝะตั‚ะพ +alpine.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +desc = ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ ะฟะฐะบะตั‚ะธั‚ะต ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. +owner.settings.cargo.rebuild.no_index = ะะต ะผะพะถะต ะดะฐ ัะต ะฟั€ะตะธะทะณั€ะฐะดะธ, ะฝัะผะฐ ะธะฝะธั†ะธะฐะปะธะทะธั€ะฐะฝ ะธะฝะดะตะบั. +owner.settings.cargo.rebuild.description = ะŸั€ะตะธะทะณั€ะฐะถะดะฐะฝะตั‚ะพ ะผะพะถะต ะดะฐ ะฑัŠะดะต ะฟะพะปะตะทะฝะพ, ะฐะบะพ ะธะฝะดะตะบััŠั‚ ะฝะต ะต ัะธะฝั…ั€ะพะฝะธะทะธั€ะฐะฝ ััŠั ััŠั…ั€ะฐะฝะตะฝะธั‚ะต Cargo ะฟะฐะบะตั‚ะธ. +owner.settings.cargo.initialize.description = ะะตะพะฑั…ะพะดะธะผะพ ะต ัะฟะตั†ะธะฐะปะฝะพ Git ั…ั€ะฐะฝะธะปะธั‰ะต ะทะฐ ะธะฝะดะตะบั, ะทะฐ ะดะฐ ัะต ะธะทะฟะพะปะทะฒะฐ ั€ะตะณะธัั‚ัŠั€ัŠั‚ ะฝะฐ Cargo. ะ˜ะทะฟะพะปะทะฒะฐะฝะตั‚ะพ ะฝะฐ ั‚ะฐะทะธ ะพะฟั†ะธั ั‰ะต (ะฟั€ะต)ััŠะทะดะฐะดะต ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ะธ ั‰ะต ะณะพ ะบะพะฝั„ะธะณัƒั€ะธั€ะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ. +pypi.requires = ะ˜ะทะธัะบะฒะฐ Python +debian.registry.info = ะ˜ะทะฑะตั€ะตั‚ะต $distribution ะธ $component ะพั‚ ัะฟะธััŠะบะฐ ะฟะพ-ะดะพะปัƒ. +alpine.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€, ะบะฐั‚ะพ ะดะพะฑะฐะฒะธั‚ะต URL ะฐะดั€ะตัะฐ ะฒัŠะฒ ะฒะฐัˆะธั ั„ะฐะนะป /etc/apk/repositories: +owner.settings.cargo.initialize.success = ะ˜ะฝะดะตะบััŠั‚ ะฝะฐ Cargo ะฑะตัˆะต ัƒัะฟะตัˆะฝะพ ััŠะทะดะฐะดะตะฝ. +npm.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะฒัŠะฒ ั„ะฐะนะปะฐ ะฝะฐ ะฒะฐัˆะธั ะฟั€ะพะตะบั‚ .npmrc: owner.settings.chef.keypair = ะ“ะตะฝะตั€ะธั€ะฐะฝะต ะฝะฐ ะดะฒะพะนะบะฐ ะบะปัŽั‡ะพะฒะต owner.settings.chef.keypair.description = ะ—ะฐัะฒะบะธั‚ะต, ะธะทะฟั€ะฐั‚ะตะฝะธ ะดะพ ั€ะตะณะธัั‚ัŠั€ะฐ ะฝะฐ Chef, ั‚ั€ัะฑะฒะฐ ะดะฐ ะฑัŠะดะฐั‚ ะบั€ะธะฟั‚ะพะณั€ะฐั„ัะบะธ ะฟะพะดะฟะธัะฐะฝะธ ะบะฐั‚ะพ ัั€ะตะดัั‚ะฒะพ ะทะฐ ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะต. ะŸั€ะธ ะณะตะฝะตั€ะธั€ะฐะฝะต ะฝะฐ ะดะฒะพะนะบะฐ ะบะปัŽั‡ะพะฒะต, ัะฐะผะพ ะฟัƒะฑะปะธั‡ะฝะธัั‚ ะบะปัŽั‡ ัะต ััŠั…ั€ะฐะฝัะฒะฐ ะฒัŠะฒ Forgejo. ะงะฐัั‚ะฝะธัั‚ ะบะปัŽั‡ ะฒะธ ัะต ะฟั€ะตะดะพัั‚ะฐะฒั, ะทะฐ ะดะฐ ัะต ะธะทะฟะพะปะทะฒะฐ ั knife. ะ“ะตะฝะตั€ะธั€ะฐะฝะตั‚ะพ ะฝะฐ ะฝะพะฒะฐ ะดะฒะพะนะบะฐ ะบะปัŽั‡ะพะฒะต ั‰ะต ะฟั€ะตะทะฐะฟะธัˆะต ะฟั€ะตะดะธัˆะฝะฐั‚ะฐ. +owner.settings.cleanuprules.remove.title = ะ’ะตั€ัะธะธั‚ะต, ะบะพะธั‚ะพ ััŠะพั‚ะฒะตั‚ัั‚ะฒะฐั‚ ะฝะฐ ั‚ะตะทะธ ะฟั€ะฐะฒะธะปะฐ, ัะต ะฟั€ะตะผะฐั…ะฒะฐั‚, ะพัะฒะตะฝ ะฐะบะพ ะฟั€ะฐะฒะธะปะพ ะฟะพ-ะณะพั€ะต ะฝะต ะบะฐะทะฒะฐ ะดะฐ ัะต ะทะฐะฟะฐะทัั‚. +nuget.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะพั‚ ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: +owner.settings.cleanuprules.success.update = ะŸั€ะฐะฒะธะปะพั‚ะพ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต ะต ะพะฑะฝะพะฒะตะฝะพ. +settings.delete.notice = ะะฐ ะฟัŠั‚ ัั‚ะต ะดะฐ ะธะทั‚ั€ะธะตั‚ะต %s (%s). ะขะฐะทะธ ะพะฟะตั€ะฐั†ะธั ะต ะฝะตะพะฑั€ะฐั‚ะธะผะฐ, ัะธะณัƒั€ะฝะธ ะปะธ ัั‚ะต? +npm.install2 = ะธะปะธ ะณะพ ะดะพะฑะฐะฒะตั‚ะต ะฒัŠะฒ ั„ะฐะนะปะฐ package.json: +owner.settings.cleanuprules.success.delete = ะŸั€ะฐะฒะธะปะพั‚ะพ ะทะฐ ะฟะพั‡ะธัั‚ะฒะฐะฝะต ะต ะธะทั‚ั€ะธั‚ะพ. +vagrant.install = ะ—ะฐ ะดะฐ ะดะพะฑะฐะฒะธั‚ะต Vagrant box, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +nuget.dependency.framework = ะฆะตะปะตะฒะฐ ะฟะปะฐั‚ั„ะพั€ะผะฐ +maven.install2 = ะ˜ะทะฟัŠะปะฝะตั‚ะต ะฟั€ะตะท ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: +maven.download = ะ—ะฐ ะดะฐ ะธะทั‚ะตะณะปะธั‚ะต ะทะฐะฒะธัะธะผะพัั‚ั‚ะฐ, ะธะทะฟัŠะปะฝะตั‚ะต ะฟั€ะตะท ะบะพะผะฐะฝะดะฝะธั ั€ะตะด: +container.layers = ะกะปะพะตะฒะต ะฝะฐ ะพะฑั€ะฐะทะฐ +conan.install = ะ—ะฐ ะดะฐ ะธะฝัั‚ะฐะปะธั€ะฐั‚ะต ะฟะฐะบะตั‚ะฐ ั Conan, ะธะทะฟัŠะปะฝะตั‚ะต ัะปะตะดะฝะฐั‚ะฐ ะบะพะผะฐะฝะดะฐ: +composer.registry = ะะฐัั‚ั€ะพะนั‚ะต ั‚ะพะทะธ ั€ะตะณะธัั‚ัŠั€ ะฒัŠะฒ ะฒะฐัˆะธั ั„ะฐะนะป ~/.composer/config.json: [tool] hours = %d ั‡ะฐัะฐ @@ -631,7 +613,6 @@ release.source_code = ะŸั€ะพะณั€ะฐะผะตะฝ ะบะพะด settings = ะะฐัั‚ั€ะพะนะบะธ forks = ะ ะฐะทะบะปะพะฝะตะฝะธั editor.or = ะธะปะธ -search = ะขัŠั€ัะตะฝะต issues.new_label_desc_placeholder = ะžะฟะธัะฐะฝะธะต watch_guest_user = ะ’ะปะตะทั‚ะต, ะทะฐ ะดะฐ ะฝะฐะฑะปัŽะดะฐะฒะฐั‚ะต ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต. migrate_items_milestones = ะ•ั‚ะฐะฟะธ @@ -674,7 +655,6 @@ pulls.made_using_agit = AGit issues.num_comments = %d ะบะพะผะตะฝั‚ะฐั€ะฐ issues.filter_sort.leastcomment = ะะฐะน-ะผะฐะปะบะพ ะบะพะผะตะฝั‚ะธั€ะฐะฝะธ issues.filter_sort.mostcomment = ะะฐะน-ะผะฝะพะณะพ ะบะพะผะตะฝั‚ะธั€ะฐะฝะธ -issues.keyword_search_unavailable = ะ’ ะผะพะผะตะฝั‚ะฐ ั‚ัŠั€ัะตะฝะตั‚ะพ ะฟะพ ะบะปัŽั‡ะพะฒะฐ ะดัƒะผะฐ ะฝะต ะต ะฝะฐะปะธั‡ะฝะพ. ะœะพะปั, ัะฒัŠั€ะถะตั‚ะต ัะต ั ะฒะฐัˆะธั ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ ะฝะฐ ัะฐะนั‚ะฐ. repo_desc_helper = ะ’ัŠะฒะตะดะตั‚ะต ะบั€ะฐั‚ะบะพ ะพะฟะธัะฐะฝะธะต (ะพะฟั†ะธะพะฝะฐะปะฝะพ) mirror_address = ะšะปะพะฝะธั€ะฐะฝะต ะพั‚ URL owner_helper = ะัะบะพะธ ะพั€ะณะฐะฝะธะทะฐั†ะธะธ ะผะพะถะต ะดะฐ ะฝะต ัะต ะฟะพะบะฐะทะฒะฐั‚ ะฒ ะฟะฐะดะฐั‰ะพั‚ะพ ะผะตะฝัŽ ะฟะพั€ะฐะดะธ ะพะณั€ะฐะฝะธั‡ะตะฝะธะต ะทะฐ ะผะฐะบัะธะผะฐะปะตะฝ ะฑั€ะพะน ั…ั€ะฐะฝะธะปะธั‰ะฐ. @@ -691,7 +671,6 @@ readme = README migrate.clone_address = ะœะธะณั€ะธั€ะฐะฝะต / ะšะปะพะฝะธั€ะฐะฝะต ะพั‚ URL migrated_from_fake = ะœะธะณั€ะธั€ะฐะฝะพ ะพั‚ %[1]s migrate.migrate = ะœะธะณั€ะธั€ะฐะฝะต ะพั‚ %s -settings.search_user_placeholder = ะŸะพั‚ัŠั€ัะตั‚ะต ะฟะพั‚ั€ะตะฑะธั‚ะตะปโ€ฆ issues.new_label = ะะพะฒ ะตั‚ะธะบะตั‚ issues.new_label_placeholder = ะ˜ะผะต ะฝะฐ ะตั‚ะธะบะตั‚ะฐ issues.label_count = %d ะตั‚ะธะบะตั‚ะฐ @@ -791,7 +770,6 @@ activity.title.issues_n = %d ะทะฐะดะฐั‡ะธ wiki.pages = ะกั‚ั€ะฐะฝะธั†ะธ activity.git_stats_author_1 = %d ะฐะฒั‚ะพั€ activity.git_stats_and_deletions = ะธ -project_board = ะŸั€ะพะตะบั‚ะธ wiki.save_page = ะ—ะฐะฟะฐะทะฒะฐะฝะต ะฝะฐ ัั‚ั€ะฐะฝะธั†ะฐั‚ะฐ activity.git_stats_author_n = %d ะฐะฒั‚ะพั€ะธ wiki.delete_page_button = ะ˜ะทั‚ั€ะธะฒะฐะฝะต ะฝะฐ ัั‚ั€ะฐะฝะธั†ะฐั‚ะฐ @@ -854,8 +832,6 @@ repo_size = ะ ะฐะทะผะตั€ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ settings.danger_zone = ะžะฟะฐัะฝะฐ ะทะพะฝะฐ issues.closed_by = ะพั‚ %[3]s ะฑะต ะทะฐั‚ะฒะพั€ะตะฝะฐ %[1]s issues.delete_comment_confirm = ะกะธะณัƒั€ะฝะธ ะปะธ ัั‚ะต, ั‡ะต ะธัะบะฐั‚ะต ะดะฐ ะธะทั‚ั€ะธะตั‚ะต ั‚ะพะทะธ ะบะพะผะตะฝั‚ะฐั€? -issues.author_helper = ะขะพะทะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะป ะต ะฐะฒั‚ะพั€ัŠั‚. -issues.ref_closed_from = `ะทะฐั‚ะฒะพั€ะธ ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ %[4]s %[2]s` milestones.due_date = ะšั€ะฐะตะฝ ัั€ะพะบ (ะพะฟั†ะธะพะฝะฐะปะฝะพ) settings.wiki_delete_notices_1 = - ะขะพะฒะฐ ั‰ะต ะธะทั‚ั€ะธะต ะฟะตั€ะผะฐะฝะตะฝั‚ะฝะพ ะธ ั‰ะต ะดะตะฐะบั‚ะธะฒะธั€ะฐ ัƒะธะบะธั‚ะพ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ %s. settings.wiki_deletion_success = ะ”ะฐะฝะฝะธั‚ะต ะฝะฐ ัƒะธะบะธั‚ะพ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ัะฐ ะธะทั‚ั€ะธั‚ะธ. @@ -901,14 +877,12 @@ pulls.reopened_at = `ะพั‚ะฒะพั€ะธ ะฝะฐะฝะพะฒะพ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธ issues.reopened_at = `ะพั‚ะฒะพั€ะธ ะฝะฐะฝะพะฒะพ ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ %s` projects.column.edit = ะ ะตะดะฐะบั‚ะธั€ะฐะฝะต ะฝะฐ ะบะพะปะพะฝะฐั‚ะฐ issues.close = ะ—ะฐั‚ะฒะฐั€ัะฝะต ะฝะฐ ะทะฐะดะฐั‡ะฐั‚ะฐ -issues.ref_reopened_from = `ะพั‚ะฒะพั€ะธ ะฝะฐะฝะพะฒะพ ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ %[4]s %[2]s` projects.deletion = ะ˜ะทั‚ั€ะธะฒะฐะฝะต ะฝะฐ ะฟั€ะพะตะบั‚ะฐ projects.edit_success = ะŸั€ะพะตะบั‚ัŠั‚ โ€ž%sโ€œ ะต ะพะฑะฝะพะฒะตะฝ. projects.deletion_success = ะŸั€ะพะตะบั‚ัŠั‚ ะต ะธะทั‚ั€ะธั‚. issues.create_comment = ะšะพะผะตะฝั‚ะธั€ะฐะฝะต unescape_control_characters = ะžั‚ะตะบั€ะฐะฝะธั€ะฐะฝะต editor.file_delete_success = ะคะฐะนะปัŠั‚ โ€ž%sโ€œ ะต ะธะทั‚ั€ะธั‚. -projects.type.uncategorized = ะะตะบะฐั‚ะตะณะพั€ะธะทะธั€ะฐะฝะพ projects.column.set_default = ะ—ะฐะดะฐะฒะฐะฝะต ะฟะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต projects.column.assigned_to = ะ’ัŠะทะปะพะถะตะฝะพ ะฝะฐ issues.reopen_comment_issue = ะžั‚ะฒะฐั€ัะฝะต ะฝะฐะฝะพะฒะพ ั ะบะพะผะตะฝั‚ะฐั€ @@ -993,14 +967,12 @@ editor.filename_cannot_be_empty = ะ˜ะผะตั‚ะพ ะฝะฐ ั„ะฐะนะปะฐ ะฝะต ะผะพะถะต ะดะฐ release.title_empty = ะ—ะฐะณะปะฐะฒะธะตั‚ะพ ะฝะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ะฟั€ะฐะทะฝะพ. settings.webhook.payload = ะกัŠะดัŠั€ะถะฐะฝะธะต settings.deploy_key_content = ะกัŠะดัŠั€ะถะฐะฝะธะต -clone_in_vsc = ะšะปะพะฝะธั€ะฐะฝะต ะฒัŠะฒ VS Code use_template = ะ˜ะทะฟะพะปะทะฒะฐะฝะต ะฝะฐ ั‚ะพะทะธ ัˆะฐะฑะปะพะฝ download_file = ะ˜ะทั‚ะตะณะปัะฝะต ะฝะฐ ั„ะฐะนะปะฐ editor.add_file = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ั„ะฐะนะป editor.edit_file = ะ ะตะดะฐะบั‚ะธั€ะฐะฝะต ะฝะฐ ั„ะฐะนะปะฐ editor.this_file_locked = ะคะฐะนะปัŠั‚ ะต ะทะฐะบะปัŽั‡ะตะฝ editor.delete_this_file = ะ˜ะทั‚ั€ะธะฒะฐะฝะต ะฝะฐ ั„ะฐะนะปะฐ -clone_in_vscodium = ะšะปะพะฝะธั€ะฐะฝะต ะฒัŠะฒ VSCodium download_zip = ะ˜ะทั‚ะตะณะปัะฝะต ะฝะฐ ZIP download_tar = ะ˜ะทั‚ะตะณะปัะฝะต ะฝะฐ TAR.GZ desc.public = ะŸัƒะฑะปะธั‡ะฝะพ @@ -1081,7 +1053,6 @@ issues.lock.reason = ะŸั€ะธั‡ะธะฝะฐ ะทะฐ ะทะฐะบะปัŽั‡ะฒะฐะฝะตั‚ะพ pulls.create = ะกัŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต issues.label.filter_sort.reverse_by_size = ะะฐะน-ะณะพะปัะผ ั€ะฐะทะผะตั€ issues.unlock = ะžั‚ะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ะพะฑััŠะถะดะฐะฝะตั‚ะพ -issues.due_date_form_add = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะบั€ะฐะตะฝ ัั€ะพะบ release.save_draft = ะ—ะฐะฟะฐะทะฒะฐะฝะต ะฝะฐ ั‡ะตั€ะฝะพะฒะฐ release.add_tag = ะกัŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะผะฐั€ะบะตั€ release.publish = ะŸัƒะฑะปะธะบัƒะฒะฐะฝะต ะฝะฐ ะธะทะดะฐะฝะธะต @@ -1134,9 +1105,7 @@ editor.fail_to_update_file_summary = ะกัŠะพะฑั‰ะตะฝะธะต ะทะฐ ะณั€ะตัˆะบะฐ: editor.fail_to_update_file = ะะตัƒัะฟะตัˆะฝะพ ะพะฑะฝะพะฒัะฒะฐะฝะต/ััŠะทะดะฐะฒะฐะฝะต ะฝะฐ ั„ะฐะนะป โ€ž%sโ€œ. editor.add_subdir = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะดะธั€ะตะบั‚ะพั€ะธัโ€ฆ commits.commits = ะŸะพะดะฐะฒะฐะฝะธั -commits.find = ะขัŠั€ัะตะฝะต commits.search_all = ะ’ัะธั‡ะบะธ ะบะปะพะฝะพะฒะต -commits.search = ะŸะพั‚ัŠั€ัะตั‚ะต ะฟะพะดะฐะฒะฐะฝะธัโ€ฆ commit.operations = ะžะฟะตั€ะฐั†ะธะธ issues.deleted_milestone = `(ะธะทั‚ั€ะธั‚)` issues.deleted_project = `(ะธะทั‚ั€ะธั‚)` @@ -1165,13 +1134,11 @@ activity.git_stats_push_to_all_branches = ะบัŠะผ ะฒัะธั‡ะบะธ ะบะปะพะฝะพะฒะต. release.deletion_tag_success = ะœะฐั€ะบะตั€ัŠั‚ ะต ะธะทั‚ั€ะธั‚. release.cancel = ะžั‚ะบะฐะท release.deletion = ะ˜ะทั‚ั€ะธะฒะฐะฝะต ะฝะฐ ะธะทะดะฐะฝะธะตั‚ะพ -release.download_count = ะ˜ะทั‚ะตะณะปัะฝะธั: %s release.tag_name_invalid = ะ˜ะผะตั‚ะพ ะฝะฐ ะผะฐั€ะบะตั€ะฐ ะฝะต ะต ะฒะฐะปะธะดะฝะพ. diff.stats_desc = %d ะฟั€ะพะผะตะฝะตะฝะธ ั„ะฐะนะปะฐ ั %d ะดะพะฑะฐะฒัะฝะธั ะธ %d ะธะทั‚ั€ะธะฒะฐะฝะธั release.tag_name_already_exist = ะ’ะตั‡ะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ ะธะทะดะฐะฝะธะต ั ั‚ะพะฒะฐ ะธะผะต ะฝะฐ ะผะฐั€ะบะตั€. branch.branch_already_exists = ะšะปะพะฝัŠั‚ โ€ž%sโ€œ ะฒะตั‡ะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ ะฒ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต. diff.download_patch = ะ˜ะทั‚ะตะณะปัะฝะต ะฝะฐ ั„ะฐะนะป-ะบั€ัŠะฟะบะฐ -diff.show_diff_stats = ะŸะพะบะฐะทะฒะฐะฝะต ะฝะฐ ัั‚ะฐั‚ะธัั‚ะธะบะฐ diff.commit = ะฟะพะดะฐะฒะฐะฝะต diff.download_diff = ะ˜ะทั‚ะตะณะปัะฝะต ะฝะฐ ั„ะฐะนะป-ั€ะฐะทะปะธะบะธ diff.whitespace_show_everything = ะŸะพะบะฐะทะฒะฐะฝะต ะฝะฐ ะฒัะธั‡ะบะธ ะฟั€ะพะผะตะฝะธ @@ -1186,12 +1153,11 @@ issues.push_commit_1 = ะดะพะฑะฐะฒะธ %d ะฟะพะดะฐะฒะฐะฝะต %s fork_visibility_helper = ะ’ะธะดะธะผะพัั‚ั‚ะฐ ะฝะฐ ั€ะฐะทะบะปะพะฝะตะฝะพ ั…ั€ะฐะฝะธะปะธั‰ะต ะฝะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ะฟั€ะพะผะตะฝะตะฝะฐ. language_other = ะ”ั€ัƒะณะธ stars_remove_warning = ะขะพะฒะฐ ั‰ะต ะฟั€ะตะผะฐั…ะฝะต ะฒัะธั‡ะบะธ ะทะฒะตะทะดะธ ะพั‚ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต. -tree_path_not_found_tag = ะŸัŠั‚ัั‚ %[1]s ะฝะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ ะฒ ะผะฐั€ะบะตั€ %[2]s -tree_path_not_found_commit = ะŸัŠั‚ัั‚ %[1]s ะฝะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ ะฒ ะฟะพะดะฐะฒะฐะฝะต %[2]s -tree_path_not_found_branch = ะŸัŠั‚ัั‚ %[1]s ะฝะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ ะฒ ะบะปะพะฝ %[2]s +tree_path_not_found.tag = ะŸัŠั‚ัั‚ %[1]s ะฝะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ ะฒ ะผะฐั€ะบะตั€ %[2]s +tree_path_not_found.commit = ะŸัŠั‚ัั‚ %[1]s ะฝะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ ะฒ ะฟะพะดะฐะฒะฐะฝะต %[2]s +tree_path_not_found.branch = ะŸัŠั‚ัั‚ %[1]s ะฝะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ ะฒ ะบะปะพะฝ %[2]s transfer.accept = ะŸั€ะธะตะผะฐะฝะต ะฝะฐ ะฟั€ะตั…ะฒัŠั€ะปัะฝะตั‚ะพ transfer.reject = ะžั‚ั…ะฒัŠั€ะปัะฝะต ะฝะฐ ะฟั€ะตั…ะฒัŠั€ะปัะฝะตั‚ะพ -archive.issue.nocomment = ะขะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต ะต ะฐั€ั…ะธะฒะธั€ะฐะฝะพ. ะะต ะผะพะถะตั‚ะต ะดะฐ ะบะพะผะตะฝั‚ะธั€ะฐั‚ะต ะฒ ะทะฐะดะฐั‡ะธั‚ะต. forked_from = ั€ะฐะทะบะปะพะฝะตะฝะพ ะพั‚ issues.delete_branch_at = `ะธะทั‚ั€ะธ ะบะปะพะฝะฐ %s %s` pulls.has_viewed_file = ะŸั€ะตะณะปะตะดะฐะฝะพ @@ -1227,7 +1193,6 @@ branch.delete_html = ะ˜ะทั‚ั€ะธะฒะฐะฝะต ะฝะฐ ะบะปะพะฝะฐ tag.create_success = ะœะฐั€ะบะตั€ัŠั‚ โ€ž%sโ€œ ะต ััŠะทะดะฐะดะตะฝ. branch.new_branch_from = ะกัŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะฝะพะฒ ะบะปะพะฝ ะพั‚ โ€ž%sโ€œ branch.new_branch = ะกัŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะฝะพะฒ ะบะปะพะฝ -branch.confirm_rename_branch = ะŸั€ะตะธะผะตะฝัƒะฒะฐะฝะต ะฝะฐ ะบะปะพะฝะฐ branch.create_from = ะพั‚ โ€ž%sโ€œ settings.add_team_duplicate = ะ•ะบะธะฟัŠั‚ ะฒะตั‡ะต ั€ะฐะทะฟะพะปะฐะณะฐ ั ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต settings.slack_domain = ะ”ะพะผะตะนะฝ @@ -1253,7 +1218,6 @@ branch.deletion_failed = ะะตัƒัะฟะตัˆะฝะพ ะธะทั‚ั€ะธะฒะฐะฝะต ะฝะฐ ะบะปะพะฝะฐ branch.rename_branch_to = ะŸั€ะตะธะผะตะฝัƒะฒะฐะฝะต ะพั‚ โ€ž%sโ€œ ะฝะฐ: settings.web_hook_name_msteams = Microsoft Teams settings.web_hook_name_dingtalk = DingTalk -issues.error_removing_due_date = ะะตัƒัะฟะตัˆะฝะพ ะฟั€ะตะผะฐั…ะฒะฐะฝะต ะฝะฐ ะบั€ะฐะนะฝะธั ัั€ะพะบ. branch.renamed = ะšะปะพะฝัŠั‚ %s ะต ะฟั€ะตะธะผะตะฝัƒะฒะฐะฝ ะฝะฐ %s. settings.teams = ะ•ะบะธะฟะธ settings.add_team = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะตะบะธะฟ @@ -1267,8 +1231,6 @@ branch.download = ะ˜ะทั‚ะตะณะปัะฝะต ะฝะฐ ะบะปะพะฝะฐ โ€ž%sโ€œ branch.rename = ะŸั€ะตะธะผะตะฝัƒะฒะฐะฝะต ะฝะฐ ะบะปะพะฝะฐ โ€ž%sโ€œ empty_message = ะ’ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต ะฝัะผะฐ ััŠะดัŠั€ะถะฐะฝะธะต. open_with_editor = ะžั‚ะฒะฐั€ัะฝะต ั %s -search.search_repo = ะขัŠั€ัะตะฝะต ะฒ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ -search.results = ะ ะตะทัƒะปั‚ะฐั‚ะธ ะพั‚ ั‚ัŠั€ัะตะฝะตั‚ะพ ะฝะฐ "%s" ะฒ %s object_format = ะคะพั€ะผะฐั‚ ะฝะฐ ะพะฑะตะบั‚ะธั‚ะต release.releases_for = ะ˜ะทะดะฐะฝะธั ะทะฐ %s release.tags_for = ะœะฐั€ะบะตั€ะธ ะทะฐ %s @@ -1276,12 +1238,10 @@ pulls.cmd_instruction_hint = ะ’ะธะถั‚ะต ะธะฝัั‚ั€ัƒะบั†ะธะธั‚ะต ะทะฐ ะบะพะผะฐะฝ pulls.showing_only_single_commit = ะŸะพะบะฐะทะฐะฝะธ ัะฐ ัะฐะผะพ ะฟั€ะพะผะตะฝะธั‚ะต ะฒ ะฟะพะดะฐะฒะฐะฝะต %[1]s issues.lock_no_reason = ะทะฐะบะปัŽั‡ะธ ะธ ะพะณั€ะฐะฝะธั‡ะธ ะพะฑััŠะถะดะฐะฝะตั‚ะพ ะดะพ ััŠั‚ั€ัƒะดะฝะธั†ะธ %s pulls.expand_files = ะ ะฐะทะณัŠะฒะฐะฝะต ะฝะฐ ะฒัะธั‡ะบะธ ั„ะฐะนะปะพะฒะต -pulls.title_desc_few = ะธัะบะฐ ะดะฐ ัะปะตะต %[1]d ะฟะพะดะฐะฒะฐะฝะธั ะพั‚ %[2]s ะฒ %[3]s issues.content_history.deleted = ะธะทั‚ั€ะธั‚ะพ activity.git_stats_exclude_merges = ะก ะธะทะบะปัŽั‡ะตะฝะธะต ะฝะฐ ัะปะธะฒะฐะฝะธัั‚ะฐ, activity.navbar.pulse = ะŸะพัะปะตะดะฝะฐ ะดะตะนะฝะพัั‚ activity.no_git_activity = ะะต ะต ะธะผะฐะปะพ ะฝะธะบะฐะบะฒะฐ ะดะตะนะฝะพัั‚ ั ะฟะพะดะฐะฒะฐะฝะธั ะฟั€ะตะท ั‚ะพะทะธ ะฟะตั€ะธะพะด. -pulls.merged_title_desc_few = ัะปั %[1]d ะฟะพะดะฐะฒะฐะฝะธั ะพั‚ %[2]s ะฒ %[3]s %[4]s diff.stats_desc_file = %d ะฟั€ะพะผะตะฝะธ: %d ะดะพะฑะฐะฒัะฝะธั ะธ %d ะธะทั‚ั€ะธะฒะฐะฝะธั issues.content_history.created = ััŠะทะดะฐะดะตะฝะพ pulls.status_checks_success = ะ’ัะธั‡ะบะธ ะฟั€ะพะฒะตั€ะบะธ ัะฐ ัƒัะฟะตัˆะฝะธ @@ -1296,9 +1256,7 @@ pulls.collapse_files = ะกะฒะธะฒะฐะฝะต ะฝะฐ ะฒัะธั‡ะบะธ ั„ะฐะนะปะพะฒะต pulls.show_all_commits = ะŸะพะบะฐะทะฒะฐะฝะต ะฝะฐ ะฒัะธั‡ะบะธ ะฟะพะดะฐะฒะฐะฝะธั diff.whitespace_button = ะŸั€ะฐะทะฝะธ ะทะฝะฐั†ะธ issues.content_history.edited = ั€ะตะดะฐะบั‚ะธั€ะฐะฝะพ -pulls.title_desc_one = ะธัะบะฐ ะดะฐ ัะปะตะต %[1]d ะฟะพะดะฐะฒะฐะฝะต ะพั‚ %[2]s ะฒ %[3]s pulls.showing_specified_commit_range = ะŸะพะบะฐะทะฐะฝะธ ัะฐ ัะฐะผะพ ะฟั€ะพะผะตะฝะธั‚ะต ะผะตะถะดัƒ %[1]s..%[2]s -pulls.merged_title_desc_one = ัะปั %[1]d ะฟะพะดะฐะฒะฐะฝะต ะพั‚ %[2]s ะฒ %[3]s %[4]s pulls.no_merge_access = ะะต ัั‚ะต ัƒะฟัŠะปะฝะพะผะพั‰ะตะฝะธ ะดะฐ ัะปะตะตั‚ะต ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต. activity.navbar.code_frequency = ะงะตัั‚ะพั‚ะฐ ะฝะฐ ะฟั€ะพะผะตะฝะธั‚ะต activity.git_stats_pushed_1 = ะต ะธะทั‚ะปะฐัะบะฐะป @@ -1339,7 +1297,6 @@ issues.dependency.issue_close_blocked = ะขั€ัะฑะฒะฐ ะดะฐ ะทะฐั‚ะฒะพั€ะธั‚ะต ะฒ issues.dependency.blocks_short = ะ‘ะปะพะบะธั€ะฐ issues.dependency.remove_header = ะŸั€ะตะผะฐั…ะฒะฐะฝะต ะฝะฐ ะทะฐะฒะธัะธะผะพัั‚ issues.dependency.issue_remove_text = ะขะพะฒะฐ ั‰ะต ะฟั€ะตะผะฐั…ะฝะต ะทะฐะฒะธัะธะผะพัั‚ั‚ะฐ ะพั‚ ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ. ะŸั€ะพะดัŠะปะถะฐะฒะฐะฝะต? -issues.reference_link = ะŸั€ะตะฟั€ะฐั‚ะบะฐ: %s pulls.closed = ะ—ะฐัะฒะบะฐั‚ะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะต ะทะฐั‚ะฒะพั€ะตะฝะฐ pulls.merged_success = ะ—ะฐัะฒะบะฐั‚ะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะต ัƒัะฟะตัˆะฝะพ ัะปัั‚ะฐ ะธ ะทะฐั‚ะฒะพั€ะตะฝะฐ branch.confirm_create_branch = ะกัŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะบะปะพะฝ @@ -1409,7 +1366,6 @@ issues.new.no_reviewers = ะัะผะฐ ั€ะตั†ะตะฝะทะตะฝั‚ะธ issues.filter_reviewers = ะคะธะปั‚ั€ะธั€ะฐะฝะต ะฝะฐ ั€ะตั†ะตะฝะทะตะฝั‚ issues.filter_type.reviewed_by_you = ะ ะตั†ะตะฝะทะธั€ะฐะฝะธ ะพั‚ ะฒะฐั issues.filter_type.review_requested = ะŸะพะธัะบะฐะฝะธ ั€ะตั†ะตะฝะทะธะธ -issues.review.review = ะ ะตั†ะตะฝะทะธั issues.review.comment = ั€ะตั†ะตะฝะทะธั€ะฐ %s branch.deleted_by = ะ˜ะทั‚ั€ะธั‚ ะพั‚ %s branch.restore = ะ’ัŠะทัั‚ะฐะฝะพะฒัะฒะฐะฝะต ะฝะฐ ะบะปะพะฝะฐ โ€ž%sโ€œ @@ -1593,20 +1549,16 @@ migrate.migrating_topics = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ ั‚ะตะผะธ projects.desc = ะฃะฟั€ะฐะฒะปัะฒะฐะนั‚ะต ะทะฐะดะฐั‡ะธ ะธ ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต ะฒ ะฟั€ะพะตะบั‚ะฝะธ ั‚ะฐะฑะปะฐ. issues.choose.invalid_templates = %v ะฝะตะฒะฐะปะธะดะฝะธ ัˆะฐะฑะปะพะฝะฐ ัะฐ ะฝะฐะผะตั€ะตะฝะธ pulls.edit.already_changed = ะะตัƒัะฟะตัˆะฝะพ ะทะฐะฟะฐะทะฒะฐะฝะต ะฝะฐ ะฟั€ะพะผะตะฝะธั‚ะต ะฒ ะทะฐัะฒะบะฐั‚ะฐ ะทะฐ ัะปะธะฒะฐะฝะต. ะ˜ะทะณะปะตะถะดะฐ ััŠะดัŠั€ะถะฐะฝะธะตั‚ะพ ะฒะตั‡ะต ะต ะฟั€ะพะผะตะฝะตะฝะพ ะพั‚ ะดั€ัƒะณ ะฟะพั‚ั€ะตะฑะธั‚ะตะป. ะœะพะปั, ะฟั€ะตะทะฐั€ะตะดะตั‚ะต ัั‚ั€ะฐะฝะธั†ะฐั‚ะฐ ะธ ะพะฟะธั‚ะฐะนั‚ะต ะดะฐ ั€ะตะดะฐะบั‚ะธั€ะฐั‚ะต ะพั‚ะฝะพะฒะพ, ะทะฐ ะดะฐ ะธะทะฑะตะณะฝะตั‚ะต ะฟั€ะตะทะฐะฟะธัะฒะฐะฝะตั‚ะพ ะฝะฐ ั‚ะตั…ะฝะธั‚ะต ะฟั€ะพะผะตะฝะธ -migrate.gitbucket.description = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ ะดะฐะฝะฝะธ ะพั‚ GitBucket ะธะฝัั‚ะฐะฝั†ะธะธ. migrate.migrating_git = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ Git ะดะฐะฝะฝะธ commits.newer = ะŸะพ-ะฝะพะฒะธ issues.choose.blank_about = ะกัŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะทะฐะดะฐั‡ะฐ ะพั‚ ัั‚ะฐะฝะดะฐั€ั‚ะตะฝ ัˆะฐะฑะปะพะฝ. issues.filter_no_results = ะัะผะฐ ั€ะตะทัƒะปั‚ะฐั‚ะธ issues.filter_no_results_placeholder = ะžะฟะธั‚ะฐะนั‚ะต ะดะฐ ะบะพั€ะธะณะธั€ะฐั‚ะต ั„ะธะปั‚ั€ะธั‚ะต ัะธ ะทะฐ ั‚ัŠั€ัะตะฝะต. archive.nocomment = ะšะพะผะตะฝั‚ะธั€ะฐะฝะตั‚ะพ ะฝะต ะต ะฒัŠะทะผะพะถะฝะพ, ั‚ัŠะน ะบะฐั‚ะพ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ะต ะฐั€ั…ะธะฒะธั€ะฐะฝะพ. -migrate.gitlab.description = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ ะดะฐะฝะฝะธ ะพั‚ gitlab.com ะธะปะธ ะดั€ัƒะณะธ GitLab ะธะฝัั‚ะฐะฝั†ะธะธ. transfer.no_permission_to_accept = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะดะฐ ะฟั€ะธะตะผะตั‚ะต ั‚ะพะฒะฐ ะฟั€ะตั…ะฒัŠั€ะปัะฝะต. transfer.no_permission_to_reject = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะดะฐ ะพั‚ั…ะฒัŠั€ะปะธั‚ะต ั‚ะพะฒะฐ ะฟั€ะตั…ะฒัŠั€ะปัะฝะต. editor.file_changed_while_editing = ะกัŠะดัŠั€ะถะฐะฝะธะตั‚ะพ ะฝะฐ ั„ะฐะนะปะฐ ะต ะฟั€ะพะผะตะฝะตะฝะพ, ะพั‚ะบะฐะบั‚ะพ ัั‚ะต ะณะพ ะพั‚ะฒะพั€ะธะปะธ. ะฉั€ะฐะบะฝะตั‚ะต ั‚ัƒะบ, ะทะฐ ะดะฐ ะณะพ ะฒะธะดะธั‚ะต, ะธะปะธ ะŸะพะดะฐะนั‚ะต ะฟั€ะพะผะตะฝะธั‚ะต ะพั‚ะฝะพะฒะพ, ะทะฐ ะดะฐ ะณะธ ะฟั€ะตะทะฐะฟะธัˆะตั‚ะต. sync_fork.button = ะกะธะฝั…ั€ะพะฝะธะทะธั€ะฐะฝะต -migrate.onedev.description = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ ะดะฐะฝะฝะธ ะพั‚ code.onedev.io ะธะปะธ ะดั€ัƒะณะธ OneDev ะธะฝัั‚ะฐะฝั†ะธะธ. -migrate.codebase.description = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ ะดะฐะฝะฝะธ ะพั‚ codebasehq.com. migrate.migrating_labels = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ ะตั‚ะธะบะตั‚ะธ migrate.migrating_releases = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ ะธะทะดะฐะฝะธั editor.push_rejected_no_message = ะŸั€ะพะผัะฝะฐั‚ะฐ ะฑะตัˆะต ะพั‚ั…ะฒัŠั€ะปะตะฝะฐ ะพั‚ ััŠั€ะฒัŠั€ะฐ ะฑะตะท ััŠะพะฑั‰ะตะฝะธะต. ะœะพะปั, ะฟั€ะพะฒะตั€ะตั‚ะต Git ะบัƒะบะธั‚ะต. @@ -1627,7 +1579,6 @@ editor.cannot_commit_to_protected_branch = ะะต ะผะพะถะต ะดะฐ ัะต ะฟะพะดะฐะฒะฐ editor.no_commit_to_branch = ะะต ะผะพะถะต ะดะฐ ัะต ะฟะพะดะฐะฒะฐ ะดะธั€ะตะบั‚ะฝะพ ะฒ ะบะปะพะฝะฐ, ะทะฐั‰ะพั‚ะพ: editor.push_rejected = ะŸั€ะพะผัะฝะฐั‚ะฐ ะฑะตัˆะต ะพั‚ั…ะฒัŠั€ะปะตะฝะฐ ะพั‚ ััŠั€ะฒัŠั€ะฐ. ะœะพะปั, ะฟั€ะพะฒะตั€ะตั‚ะต Git ะบัƒะบะธั‚ะต. cite_this_repo = ะฆะธั‚ะธั€ะฐะฝะต ะฝะฐ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต -migrate.gitea.description = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ ะดะฐะฝะฝะธ ะพั‚ gitea.com ะธะปะธ ะดั€ัƒะณะธ Gitea ะธะฝัั‚ะฐะฝั†ะธะธ. editor.push_rejected_summary = ะŸัŠะปะฝะพ ััŠะพะฑั‰ะตะฝะธะต ะฝะฐ ะพั‚ั…ะฒัŠั€ะปัะฝะตั‚ะพ: sync_fork.branch_behind_one = ะขะพะทะธ ะบะปะพะฝ ะต %[1]d ะฟะพะดะฐะฒะฐะฝะต ะทะฐะด %[2]s sync_fork.branch_behind_few = ะขะพะทะธ ะบะปะพะฝ ะต %[1]d ะฟะพะดะฐะฒะฐะฝะธั ะทะฐะด %[2]s @@ -1636,9 +1587,6 @@ editor.commit_id_not_matching = ะคะฐะนะปัŠั‚ ะต ะฟั€ะพะผะตะฝะตะฝ, ะดะพะบะฐั‚ะพ editor.user_no_push_to_branch = ะŸะพั‚ั€ะตะฑะธั‚ะตะปัั‚ ะฝะต ะผะพะถะต ะดะฐ ะธะทั‚ะปะฐัะบะฒะฐ ะฒ ะบะปะพะฝะฐ archive.pull.noreview = ะขะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต ะต ะฐั€ั…ะธะฒะธั€ะฐะฝะพ. ะะต ะผะพะถะตั‚ะต ะดะฐ ั€ะตั†ะตะฝะทะธั€ะฐั‚ะต ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต. migrate.migrating_failed.error = ะะตัƒัะฟะตัˆะฝะพ ะผะธะณั€ะธั€ะฐะฝะต: %s -migrate.github.description = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ ะดะฐะฝะฝะธ ะพั‚ github.com ะธะปะธ GitHub Enterprise ััŠั€ะฒัŠั€. -migrate.forgejo.description = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ ะดะฐะฝะฝะธ ะพั‚ codeberg.org ะธะปะธ ะดั€ัƒะณะธ Forgejo ะธะฝัั‚ะฐะฝั†ะธะธ. -migrate.gogs.description = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ ะดะฐะฝะฝะธ ะพั‚ notabug.org ะธะปะธ ะดั€ัƒะณะธ Gogs ะธะฝัั‚ะฐะฝั†ะธะธ. migrate.migrating_milestones = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ ะตั‚ะฐะฟะธ migrate.failed = ะœะธะณั€ะธั€ะฐะฝะตั‚ะพ ะต ะฝะตัƒัะฟะตัˆะฝะพ: %v pulls.nothing_to_compare_and_allow_empty_pr = ะขะตะทะธ ะบะปะพะฝะพะฒะต ัะฐ ั€ะฐะฒะฝะธ. ะขะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ั‰ะต ะฑัŠะดะต ะฟั€ะฐะทะฝะฐ. @@ -1660,15 +1608,14 @@ pulls.required_status_check_missing = ะัะบะพะธ ะทะฐะดัŠะปะถะธั‚ะตะปะฝะธ ะฟั€ะพ pulls.change_target_branch_at = `ะฟั€ะพะผะตะฝะธ ั†ะตะปะตะฒะธั ะบะปะพะฝ ะพั‚ %s ะฝะฐ %s %s` issues.time_spent_total = ะžะฑั‰ะพ ะธะทั€ะฐะทั…ะพะดะฒะฐะฝะพ ะฒั€ะตะผะต issues.del_time_history = `ะธะทั‚ั€ะธ ะธะทั€ะฐะทั…ะพะดะฒะฐะฝะพั‚ะพ ะฒั€ะตะผะต %s` -pulls.nothing_to_compare_have_tag = ะ˜ะทะฑั€ะฐะฝะธั‚ะต ะบะปะพะฝ/ะผะฐั€ะบะตั€ ัะฐ ั€ะฐะฒะฝะธ. +pulls.nothing_to_compare_have_tag = ะ˜ะทะฑั€ะฐะฝะธั‚ะต ะบะปะพะฝะพะฒะต/ะผะฐั€ะบะตั€ะธ ัะฐ ั€ะฐะฒะฝะธ. pulls.cannot_auto_merge_desc = ะขะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะฝะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ัะปัั‚ะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟะพั€ะฐะดะธ ะบะพะฝั„ะปะธะบั‚ะธ. issues.tracker_auto_close = ะขะฐะนะผะตั€ัŠั‚ ั‰ะต ะฑัŠะดะต ัะฟั€ัะฝ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ, ะบะพะณะฐั‚ะพ ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ ะฑัŠะดะต ะทะฐั‚ะฒะพั€ะตะฝะฐ -issues.force_push_codes = `ะธะทั‚ะปะฐัะบะฐ ะฟั€ะธะฝัƒะดะธั‚ะตะปะฝะพ %[1]s ะพั‚ %[2]s ะบัŠะผ %[4]s %[6]s` +issues.force_push_codes = `ะธะทั‚ะปะฐัะบะฐ ะฟั€ะธะฝัƒะดะธั‚ะตะปะฝะพ %[1]s ะพั‚ %[2]s %[8]s ะบัŠะผ %[4]s %[9]s %[6]s` pulls.blocked_by_official_review_requests = ะขะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะต ะฑะปะพะบะธั€ะฐะฝะฐ, ะทะฐั‰ะพั‚ะพ ะปะธะฟัะฒะฐ ะพะดะพะฑั€ะตะฝะธะต ะพั‚ ะตะดะธะฝ ะธะปะธ ะฟะพะฒะตั‡ะต ะพั„ะธั†ะธะฐะปะฝะธ ั€ะตั†ะตะฝะทะตะฝั‚ะธ. issues.tracker = ะŸั€ะพัะปะตะดัะฒะฐะฝะต ะฝะฐ ะฒั€ะตะผะตั‚ะพ issues.add_time_history = `ะดะพะฑะฐะฒะธ ะธะทั€ะฐะทั…ะพะดะฒะฐะฝะพ ะฒั€ะตะผะต %s` migrate.repo_desc_helper = ะžัั‚ะฐะฒะตั‚ะต ะฟั€ะฐะทะฝะพ, ะทะฐ ะดะฐ ะฒะฝะตัะตั‚ะต ััŠั‰ะตัั‚ะฒัƒะฒะฐั‰ะพั‚ะพ ะพะฟะธัะฐะฝะธะต -migrate.git.description = ะœะธะณั€ะธั€ะฐะฝะต ัะฐะผะพ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะต ะพั‚ ะฒััะบะฐ Git ัƒัะปัƒะณะฐ. mirror_sync = ัะธะฝั…ั€ะพะฝะธะทะธั€ะฐะฝะพ migrate_repo = ะœะธะณั€ะธั€ะฐะฝะต ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะต migrate_options = ะžะฟั†ะธะธ ะทะฐ ะผะธะณั€ะธั€ะฐะฝะตั‚ะพ @@ -1698,178 +1645,176 @@ migrate.migrating_failed_no_addr = ะœะธะณั€ะธั€ะฐะฝะตั‚ะพ ะต ะฝะตัƒัะฟะตัˆะฝะพ. issues.force_push_compare = ะกั€ะฐะฒะฝัะฒะฐะฝะต pulls.status_checking = ะัะบะพะธ ะฟั€ะพะฒะตั€ะบะธ ัะฐ ะฒ ะพั‡ะฐะบะฒะฐะฝะต pulls.nothing_to_compare = ะขะตะทะธ ะบะปะพะฝะพะฒะต ัะฐ ั€ะฐะฒะฝะธ. ะะต ะต ะฝัƒะถะฝะพ ะดะฐ ััŠะทะดะฐะฒะฐั‚ะต ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต. - -rss.must_be_on_branch = ะขั€ัะฑะฒะฐ ะดะฐ ัั‚ะต ะฝะฐ ะบะปะพะฝ, ะทะฐ ะดะฐ ะธะผะฐั‚ะต RSS ะตะผะธัะธั. -admin.manage_flags = ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ ั„ะปะฐะณะพะฒะตั‚ะต -admin.enabled_flags = ะคะปะฐะณะพะฒะต, ะฒะบะปัŽั‡ะตะฝะธ ะทะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ: -admin.update_flags = ะžะฑะฝะพะฒัะฒะฐะฝะต ะฝะฐ ั„ะปะฐะณะพะฒะตั‚ะต -admin.failed_to_replace_flags = ะะตัƒัะฟะตัˆะฝะฐ ะทะฐะผัะฝะฐ ะฝะฐ ั„ะปะฐะณะพะฒะตั‚ะต ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ admin.flags_replaced = ะคะปะฐะณะพะฒะตั‚ะต ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ัะฐ ะทะฐะผะตะฝะตะฝะธ -fork_to_different_account = ะ ะฐะทะบะปะพะฝัะฒะฐะฝะต ะฒ ะดั€ัƒะณ ะฐะบะฐัƒะฝั‚ -mirror_interval = ะ˜ะฝั‚ะตั€ะฒะฐะป ะฝะฐ ะพะณะปะตะดะฐะปะพั‚ะพ (ะฒะฐะปะธะดะฝะธ ะตะดะธะฝะธั†ะธ ะทะฐ ะฒั€ะตะผะต ัะฐ โ€žhโ€œ, โ€žmโ€œ, โ€žsโ€œ). 0 ะทะฐ ะธะทะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ะฟะตั€ะธะพะดะธั‡ะฝะฐั‚ะฐ ัะธะฝั…ั€ะพะฝะธะทะฐั†ะธั. (ะœะธะฝะธะผะฐะปะตะฝ ะธะฝั‚ะตั€ะฒะฐะป: %s) -mirror_interval_invalid = ะ˜ะฝั‚ะตั€ะฒะฐะปัŠั‚ ะฝะฐ ะพะณะปะตะดะฐะปะพั‚ะพ ะฝะต ะต ะฒะฐะปะธะดะตะฝ. -mirror_use_ssh.text = ะ˜ะทะฟะพะปะทะฒะฐะฝะต ะฝะฐ SSH ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะต -mirror_use_ssh.helper = Forgejo ั‰ะต ััŠะทะดะฐะดะต ะพะณะปะตะดะฐะปะพ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ั‡ั€ะตะท Git ะฟั€ะตะท SSH ะธ ั‰ะต ะณะตะฝะตั€ะธั€ะฐ ะดะฒะพะนะบะฐ ะบะปัŽั‡ะพะฒะต ะทะฐ ะฒะฐั, ะบะพะณะฐั‚ะพ ะธะทะฑะตั€ะตั‚ะต ั‚ะฐะทะธ ะพะฟั†ะธั. ะขั€ัะฑะฒะฐ ะดะฐ ัะต ัƒะฒะตั€ะธั‚ะต, ั‡ะต ะณะตะฝะตั€ะธั€ะฐะฝะธัั‚ ะฟัƒะฑะปะธั‡ะตะฝ ะบะปัŽั‡ ะต ัƒะฟัŠะปะฝะพะผะพั‰ะตะฝ ะดะฐ ะธะทั‚ะปะฐัะบะฒะฐ ะบัŠะผ ั†ะตะปะตะฒะพั‚ะพ ั…ั€ะฐะฝะธะปะธั‰ะต. ะะต ะผะพะถะตั‚ะต ะดะฐ ะธะทะฟะพะปะทะฒะฐั‚ะต ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะต, ะฑะฐะทะธั€ะฐะฝะพ ะฝะฐ ะฟะฐั€ะพะปะฐ, ะบะพะณะฐั‚ะพ ะธะทะฑะธั€ะฐั‚ะต ั‚ะพะฒะฐ. -mirror_use_ssh.not_available = SSH ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะตั‚ะพ ะฝะต ะต ะฝะฐะปะธั‡ะฝะพ. -mirror_denied_combination = ะะต ะผะพะถะต ะดะฐ ัะต ะธะทะฟะพะปะทะฒะฐ ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะต ั ะฟัƒะฑะปะธั‡ะตะฝ ะบะปัŽั‡ ะธ ะฟะฐั€ะพะปะฐ ะตะดะฝะพะฒั€ะตะผะตะฝะฝะพ. -mirror_sync_on_commit = ะกะธะฝั…ั€ะพะฝะธะทะธั€ะฐะฝะต ะฟั€ะธ ะธะทั‚ะปะฐัะบะฒะฐะฝะต ะฝะฐ ะฟะพะดะฐะฒะฐะฝะธั -mirror_address_desc = ะŸะพัั‚ะฐะฒะตั‚ะต ะฒัะธั‡ะบะธ ะฝะตะพะฑั…ะพะดะธะผะธ ะดะฐะฝะฝะธ ะทะฐ ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะต ะฒ ัะตะบั†ะธัั‚ะฐ โ€žะฃะฟัŠะปะฝะพะผะพั‰ะฐะฒะฐะฝะตโ€œ. -mirror_address_url_invalid = ะŸั€ะตะดะพัั‚ะฐะฒะตะฝะธัั‚ URL ะต ะฝะตะฒะฐะปะธะดะตะฝ. ะขั€ัะฑะฒะฐ ะดะฐ ะตะบั€ะฐะฝะธั€ะฐั‚ะต ะฟั€ะฐะฒะธะปะฝะพ ะฒัะธั‡ะบะธ ะบะพะผะฟะพะฝะตะฝั‚ะธ ะฝะฐ URL ะฐะดั€ะตัะฐ. -mirror_address_protocol_invalid = ะŸั€ะตะดะพัั‚ะฐะฒะตะฝะธัั‚ URL ะต ะฝะตะฒะฐะปะธะดะตะฝ. ะกะฐะผะพ http(s):// ะธะปะธ git:// ะฐะดั€ะตัะธ ะผะพะณะฐั‚ ะดะฐ ัะต ะธะทะฟะพะปะทะฒะฐั‚ ะทะฐ ะพะณะปะตะดะฐะปะฝะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ. -mirror_lfs = ะกัŠั…ั€ะฐะฝะตะฝะธะต ะฝะฐ ะณะพะปะตะผะธ ั„ะฐะนะปะพะฒะต (LFS) -mirror_password_help = ะŸั€ะพะผะตะฝะตั‚ะต ะฟะพั‚ั€ะตะฑะธั‚ะตะปัะบะพั‚ะพ ะธะผะต, ะทะฐ ะดะฐ ะธะทั‚ั€ะธะตั‚ะต ะทะฐะฟะฐะทะตะฝะฐ ะฟะฐั€ะพะปะฐ. -unit_disabled = ะะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ัŠั‚ ะฝะฐ ัะฐะนั‚ะฐ ะต ะธะทะบะปัŽั‡ะธะป ั‚ะฐะทะธ ัะตะบั†ะธั ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. -summary_card_alt = ะšะฐั€ั‚ะฐ ั ะพะฑะพะฑั‰ะตะฝะธะต ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะต %s -template.items = ะ•ะปะตะผะตะฝั‚ะธ ะฝะฐ ัˆะฐะฑะปะพะฝะฐ -template.git_content = Git ััŠะดัŠั€ะถะฐะฝะธะต (ัั‚ะฐะฝะดะฐั€ั‚ะตะฝ ะบะปะพะฝ) -template.git_hooks = Git ะบัƒะบะธ -template.git_hooks_tooltip = ะ’ ะผะพะผะตะฝั‚ะฐ ะฝะต ะผะพะถะตั‚ะต ะดะฐ ะฟั€ะพะผะตะฝัั‚ะต ะธะปะธ ะฟั€ะตะผะฐั…ะฒะฐั‚ะต Git ะบัƒะบะธ, ัะปะตะด ะบะฐั‚ะพ ัะฐ ะดะพะฑะฐะฒะตะฝะธ. ะ˜ะทะฑะตั€ะตั‚ะต ั‚ะพะฒะฐ ัะฐะผะพ ะฐะบะพ ัะต ะดะพะฒะตั€ัะฒะฐั‚ะต ะฝะฐ ัˆะฐะฑะปะพะฝะฝะพั‚ะพ ั…ั€ะฐะฝะธะปะธั‰ะต. -template.one_item = ะขั€ัะฑะฒะฐ ะดะฐ ะธะทะฑะตั€ะตั‚ะต ะฟะพะฝะต ะตะดะธะฝ ะตะปะตะผะตะฝั‚ ะพั‚ ัˆะฐะฑะปะพะฝะฐ -template.invalid = ะขั€ัะฑะฒะฐ ะดะฐ ะธะทะฑะตั€ะตั‚ะต ัˆะฐะฑะปะพะฝะฝะพ ั…ั€ะฐะฝะธะปะธั‰ะต +editor.cannot_edit_lfs_files = LFS ั„ะฐะนะปะพะฒะต ะฝะต ะผะพะณะฐั‚ ะดะฐ ัะต ั€ะตะดะฐะบั‚ะธั€ะฐั‚ ะฒ ัƒะตะฑ ะธะฝั‚ะตั€ั„ะตะนัะฐ. +commits.ssh_key_fingerprint = ะžั‚ะฟะตั‡ะฐั‚ัŠะบ ะฝะฐ SSH ะบะปัŽั‡ +issues.comment_on_locked = ะะต ะผะพะถะตั‚ะต ะดะฐ ะบะพะผะตะฝั‚ะธั€ะฐั‚ะต ะทะฐะบะปัŽั‡ะตะฝะฐ ะทะฐะดะฐั‡ะฐ. +commit.revert = ะ’ั€ัŠั‰ะฐะฝะต migrate.cancel_migrating_title = ะžั‚ะบะฐะท ะพั‚ ะผะธะณั€ะฐั†ะธัั‚ะฐ migrate.cancel_migrating_confirm = ะ˜ัะบะฐั‚ะต ะปะธ ะดะฐ ะพั‚ะบะฐะถะตั‚ะต ั‚ะฐะทะธ ะผะธะณั€ะฐั†ะธั? -invisible_runes_description = `ะขะพะทะธ ั„ะฐะนะป ััŠะดัŠั€ะถะฐ ะฝะตะฒะธะดะธะผะธ ะฃะฝะธะบะพะด ะทะฝะฐั†ะธ, ะบะพะธั‚ะพ ัะฐ ะฝะตั€ะฐะทะปะธั‡ะธะผะธ ะทะฐ ั…ะพั€ะฐั‚ะฐ, ะฝะพ ะผะพะณะฐั‚ ะดะฐ ะฑัŠะดะฐั‚ ะพะฑั€ะฐะฑะพั‚ะตะฝะธ ะฟะพ ั€ะฐะทะปะธั‡ะตะฝ ะฝะฐั‡ะธะฝ ะพั‚ ะบะพะผะฟัŽั‚ัŠั€. ะะบะพ ัะผัั‚ะฐั‚ะต, ั‡ะต ั‚ะพะฒะฐ ะต ัƒะผะธัˆะปะตะฝะพ, ะผะพะถะตั‚ะต ัะฟะพะบะพะนะฝะพ ะดะฐ ะฟั€ะตะฝะตะฑั€ะตะณะฝะตั‚ะต ั‚ะพะฒะฐ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต. ะ˜ะทะฟะพะปะทะฒะฐะนั‚ะต ะฑัƒั‚ะพะฝะฐ โ€žะ•ะบั€ะฐะฝะธั€ะฐะฝะตโ€œ, ะทะฐ ะดะฐ ะณะธ ั€ะฐะทะบั€ะธะตั‚ะต.` -ambiguous_runes_header = `ะขะพะทะธ ั„ะฐะนะป ััŠะดัŠั€ะถะฐ ะดะฒัƒัะผะธัะปะตะฝะธ ะฃะฝะธะบะพะด ะทะฝะฐั†ะธ` -ambiguous_runes_description = `ะขะพะทะธ ั„ะฐะนะป ััŠะดัŠั€ะถะฐ ะฃะฝะธะบะพะด ะทะฝะฐั†ะธ, ะบะพะธั‚ะพ ะผะพะณะฐั‚ ะดะฐ ะฑัŠะดะฐั‚ ะพะฑัŠั€ะบะฐะฝะธ ั ะดั€ัƒะณะธ ะทะฝะฐั†ะธ. ะะบะพ ัะผัั‚ะฐั‚ะต, ั‡ะต ั‚ะพะฒะฐ ะต ัƒะผะธัˆะปะตะฝะพ, ะผะพะถะตั‚ะต ัะฟะพะบะพะนะฝะพ ะดะฐ ะฟั€ะตะฝะตะฑั€ะตะณะฝะตั‚ะต ั‚ะพะฒะฐ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต. ะ˜ะทะฟะพะปะทะฒะฐะนั‚ะต ะฑัƒั‚ะพะฝะฐ โ€žะ•ะบั€ะฐะฝะธั€ะฐะฝะตโ€œ, ะทะฐ ะดะฐ ะณะธ ั€ะฐะทะบั€ะธะตั‚ะต.` -file_copy_permalink = ะšะพะฟะธั€ะฐะฝะต ะฝะฐ ะฟะพัั‚ะพัะฝะฝะฐ ะฒั€ัŠะทะบะฐ -view_git_blame = ะŸั€ะตะณะปะตะด ะฝะฐ git blame -video_not_supported_in_browser = ะ’ะฐัˆะธัั‚ ะฑั€ะฐัƒะทัŠั€ ะฝะต ะฟะพะดะดัŠั€ะถะฐ HTML5 ั‚ะฐะณะฐ โ€žvideoโ€œ. -audio_not_supported_in_browser = ะ’ะฐัˆะธัั‚ ะฑั€ะฐัƒะทัŠั€ ะฝะต ะฟะพะดะดัŠั€ะถะฐ HTML5 ั‚ะฐะณะฐ โ€žaudioโ€œ. -stored_lfs = ะกัŠั…ั€ะฐะฝะตะฝะพ ั Git LFS -commit_graph.select = ะ˜ะทะฑะตั€ะตั‚ะต ะบะปะพะฝะพะฒะต -editor.cannot_edit_lfs_files = LFS ั„ะฐะนะปะพะฒะต ะฝะต ะผะพะณะฐั‚ ะดะฐ ัะต ั€ะตะดะฐะบั‚ะธั€ะฐั‚ ะฒ ัƒะตะฑ ะธะฝั‚ะตั€ั„ะตะนัะฐ. -editor.filename_help = ะ”ะพะฑะฐะฒะตั‚ะต ะดะธั€ะตะบั‚ะพั€ะธั, ะบะฐั‚ะพ ะฒัŠะฒะตะดะตั‚ะต ะธะผะตั‚ะพ ั, ะฟะพัะปะตะดะฒะฐะฝะพ ะพั‚ ะฝะฐะบะปะพะฝะตะฝะฐ ั‡ะตั€ั‚ะฐ (โ€ž/โ€œ). ะŸั€ะตะผะฐั…ะฝะตั‚ะต ะดะธั€ะตะบั‚ะพั€ะธั, ะบะฐั‚ะพ ะฝะฐั‚ะธัะฝะตั‚ะต backspace ะฒ ะฝะฐั‡ะฐะปะพั‚ะพ ะฝะฐ ะฟะพะปะตั‚ะพ ะทะฐ ะฒัŠะฒะตะถะดะฐะฝะต. -editor.commit_signed_changes = ะŸะพะดะฐะฒะฐะฝะต ะฝะฐ ะฟะพะดะฟะธัะฐะฝะธ ะฟั€ะพะผะตะฝะธ -editor.require_signed_commit = ะšะปะพะฝัŠั‚ ะธะทะธัะบะฒะฐ ะฟะพะดะฟะธัะฐะฝะพ ะฟะพะดะฐะฒะฐะฝะต -editor.commit_email = ะ•ะป. ะฟะพั‰ะฐ ะฝะฐ ะฟะพะดะฐะฒะฐะฝะตั‚ะพ -commits.desc = ะ ะฐะทะณะปะตะถะดะฐะฝะต ะฝะฐ ะธัั‚ะพั€ะธัั‚ะฐ ะฝะฐ ะฟั€ะพะผะตะฝะธั‚ะต ะฒ ะฟั€ะพะณั€ะฐะผะฝะธั ะบะพะด. -commits.search.tooltip = ะœะพะถะตั‚ะต ะดะฐ ะดะพะฑะฐะฒะธั‚ะต ะฟั€ะตั„ะธะบั ะบัŠะผ ะบะปัŽั‡ะพะฒะธั‚ะต ะดัƒะผะธ ั โ€žauthor:โ€œ, โ€žcommitter:โ€œ, โ€žafter:โ€œ ะธะปะธ โ€žbefore:โ€œ, ะฝะฐะฟั€. โ€žrevert author:Alice before:2019-01-13โ€œ. +issues.choose.invalid_config = ะšะพะฝั„ะธะณัƒั€ะฐั†ะธัั‚ะฐ ะฝะฐ ะทะฐะดะฐั‡ะธั‚ะต ััŠะดัŠั€ะถะฐ ะณั€ะตัˆะบะธ: +unit_disabled = ะะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ัŠั‚ ะฝะฐ ัะฐะนั‚ะฐ ะต ะธะทะบะปัŽั‡ะธะป ั‚ะฐะทะธ ัะตะบั†ะธั ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. +issues.blocked_by_user = ะะต ะผะพะถะตั‚ะต ะดะฐ ััŠะทะดะฐะฒะฐั‚ะต ะทะฐะดะฐั‡ะธ ะฒ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต, ะทะฐั‰ะพั‚ะพ ัั‚ะต ะฑะปะพะบะธั€ะฐะฝะธ ะพั‚ ะฟั€ะธั‚ะตะถะฐั‚ะตะปั ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. commits.signed_by = ะŸะพะดะฟะธัะฐะฝะพ ะพั‚ commits.signed_by_untrusted_user = ะŸะพะดะฟะธัะฐะฝะพ ะพั‚ ะฝะตะดะพะฒะตั€ะตะฝ ะฟะพั‚ั€ะตะฑะธั‚ะตะป commits.signed_by_untrusted_user_unmatched = ะŸะพะดะฟะธัะฐะฝะพ ะพั‚ ะฝะตะดะพะฒะตั€ะตะฝ ะฟะพั‚ั€ะตะฑะธั‚ะตะป, ะบะพะนั‚ะพ ะฝะต ััŠะฒะฟะฐะดะฐ ั ะฟะพะดะฐะฒะฐั‰ะธั -commits.ssh_key_fingerprint = ะžั‚ะฟะตั‡ะฐั‚ัŠะบ ะฝะฐ SSH ะบะปัŽั‡ -commits.view_single_diff = ะŸั€ะตะณะปะตะด ะฝะฐ ะฟั€ะพะผะตะฝะธั‚ะต ะฒ ั‚ะพะทะธ ั„ะฐะนะป, ะฒัŠะฒะตะดะตะฝะธ ะฒ ั‚ะพะฒะฐ ะฟะพะดะฐะฒะฐะฝะต -commit.revert = ะ’ั€ัŠั‰ะฐะฝะต -commit.revert-header = ะ’ั€ัŠั‰ะฐะฝะต: %s -commit.revert-content = ะ˜ะทะฑะตั€ะตั‚ะต ะบะปะพะฝ, ะฒัŠั€ั…ัƒ ะบะพะนั‚ะพ ะดะฐ ัะต ะฒัŠั€ะฝะต: -issues.desc = ะžั€ะณะฐะฝะธะทะธั€ะฐะนั‚ะต ะดะพะบะปะฐะดะธ ะทะฐ ะณั€ะตัˆะบะธ, ะทะฐะดะฐั‡ะธ ะธ ะตั‚ะฐะฟะธ. -issues.choose.ignore_invalid_templates = ะะตะฒะฐะปะธะดะฝะธั‚ะต ัˆะฐะฑะปะพะฝะธ ัะฐ ะธะณะฝะพั€ะธั€ะฐะฝะธ -issues.choose.invalid_config = ะšะพะฝั„ะธะณัƒั€ะฐั†ะธัั‚ะฐ ะฝะฐ ะทะฐะดะฐั‡ะธั‚ะต ััŠะดัŠั€ะถะฐ ะณั€ะตัˆะบะธ: -issues.filter_type.all_pull_requests = ะ’ัะธั‡ะบะธ ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต -issues.role.member_helper = ะขะพะทะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะป ะต ัƒั‡ะฐัั‚ะฝะธะบ ะฒ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ, ะฟั€ะธั‚ะตะถะฐะฒะฐั‰ะฐ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต. -issues.lock.unknown_reason = ะะต ะผะพะถะต ะดะฐ ัะต ะทะฐะบะปัŽั‡ะธ ะทะฐะดะฐั‡ะฐ ั ะฝะตะธะทะฒะตัั‚ะฝะฐ ะฟั€ะธั‡ะธะฝะฐ. -issues.lock_duplicate = ะ—ะฐะดะฐั‡ะฐ ะฝะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ะทะฐะบะปัŽั‡ะตะฝะฐ ะดะฒะฐ ะฟัŠั‚ะธ. -issues.unlock_error = ะะต ะผะพะถะต ะดะฐ ัะต ะพั‚ะบะปัŽั‡ะธ ะทะฐะดะฐั‡ะฐ, ะบะพัั‚ะพ ะฝะต ะต ะทะฐะบะปัŽั‡ะตะฝะฐ. issues.lock.notice_1 = - ะ”ั€ัƒะณะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะปะธ ะฝะต ะผะพะณะฐั‚ ะดะฐ ะดะพะฑะฐะฒัั‚ ะฝะพะฒะธ ะบะพะผะตะฝั‚ะฐั€ะธ ะบัŠะผ ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ. -issues.lock.notice_2 = - ะ’ะธะต ะธ ะดั€ัƒะณะธ ััŠั‚ั€ัƒะดะฝะธั†ะธ ั ะดะพัั‚ัŠะฟ ะดะพ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต ะฒัะต ะพั‰ะต ะผะพะถะตั‚ะต ะดะฐ ะพัั‚ะฐะฒัั‚ะต ะบะพะผะตะฝั‚ะฐั€ะธ, ะบะพะธั‚ะพ ะดั€ัƒะณะธั‚ะต ะดะฐ ะฒะธะถะดะฐั‚. -issues.lock.notice_3 = - ะ’ะธะฝะฐะณะธ ะผะพะถะตั‚ะต ะดะฐ ะพั‚ะบะปัŽั‡ะธั‚ะต ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ ะพั‚ะฝะพะฒะพ ะฒ ะฑัŠะดะตั‰ะต. -issues.unlock.notice_1 = - ะ’ัะตะบะธ ั‰ะต ะผะพะถะต ะพั‚ะฝะพะฒะพ ะดะฐ ะบะพะผะตะฝั‚ะธั€ะฐ ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ. issues.unlock.notice_2 = - ะ’ะธะฝะฐะณะธ ะผะพะถะตั‚ะต ะดะฐ ะทะฐะบะปัŽั‡ะธั‚ะต ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ ะพั‚ะฝะพะฒะพ ะฒ ะฑัŠะดะตั‰ะต. -issues.lock.title = ะ—ะฐะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ะพะฑััŠะถะดะฐะฝะตั‚ะพ ะฟะพ ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ. issues.unlock.title = ะžั‚ะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ะพะฑััŠะถะดะฐะฝะตั‚ะพ ะฟะพ ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ. -issues.comment_on_locked = ะะต ะผะพะถะตั‚ะต ะดะฐ ะบะพะผะตะฝั‚ะธั€ะฐั‚ะต ะทะฐะบะปัŽั‡ะตะฝะฐ ะทะฐะดะฐั‡ะฐ. -issues.delete.text = ะะฐะธัั‚ะธะฝะฐ ะปะธ ะธัะบะฐั‚ะต ะดะฐ ะธะทั‚ั€ะธะตั‚ะต ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ? (ะขะพะฒะฐ ั‰ะต ะฟั€ะตะผะฐั…ะฝะต ั‚ั€ะฐะนะฝะพ ั†ัะปะพั‚ะพ ััŠะดัŠั€ะถะฐะฝะธะต. ะŸะพะผะธัะปะตั‚ะต ะดะฐะปะธ ะฒะผะตัั‚ะพ ั‚ะพะฒะฐ ะดะฐ ะฝะต ั ะทะฐั‚ะฒะพั€ะธั‚ะต, ะฐะบะพ ะฒัŠะทะฝะฐะผะตั€ัะฒะฐั‚ะต ะดะฐ ั ะทะฐะฟะฐะทะธั‚ะต ะฐั€ั…ะธะฒะธั€ะฐะฝะฐ) -issues.cancel_tracking_history = `ะพั‚ะผะตะฝะธ ะฟั€ะพัะปะตะดัะฒะฐะฝะตั‚ะพ ะฝะฐ ะฒั€ะตะผะตั‚ะพ %s` -issues.add_time_sum_to_small = ะะต ะต ะฒัŠะฒะตะดะตะฝะพ ะฒั€ะตะผะต. -issues.due_date_form = ะณะณะณะณ-ะผะผ-ะดะด -issues.due_date_invalid = ะšั€ะฐะนะฝะธัั‚ ัั€ะพะบ ะต ะฝะตะฒะฐะปะธะดะตะฝ ะธะปะธ ะธะทะฒัŠะฝ ะพะฑั…ะฒะฐั‚ะฐ. ะœะพะปั, ะธะทะฟะพะปะทะฒะฐะนั‚ะต ั„ะพั€ะผะฐั‚ะฐ โ€žะณะณะณะณ-ะผะผ-ะดะดโ€œ. issues.dependency.no_permission_1 = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะดะฐ ะฟั€ะพั‡ะตั‚ะตั‚ะต %d ะทะฐะฒะธัะธะผะพัั‚ -issues.dependency.no_permission_n = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะดะฐ ะฟั€ะพั‡ะตั‚ะตั‚ะต %d ะทะฐะฒะธัะธะผะพัั‚ะธ -issues.dependency.no_permission.can_remove = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะดะฐ ะฟั€ะพั‡ะตั‚ะตั‚ะต ั‚ะฐะทะธ ะทะฐะฒะธัะธะผะพัั‚, ะฝะพ ะผะพะถะตั‚ะต ะดะฐ ั ะฟั€ะตะผะฐั…ะฝะตั‚ะต -issues.dependency.issue_batch_close_blocked = ะะต ะผะพะณะฐั‚ ะดะฐ ะฑัŠะดะฐั‚ ะทะฐั‚ะฒะพั€ะตะฝะธ ะณั€ัƒะฟะพะฒะพ ะธะทะฑั€ะฐะฝะธั‚ะต ะทะฐะดะฐั‡ะธ, ะทะฐั‰ะพั‚ะพ ะทะฐะดะฐั‡ะฐ #%d ะฒัะต ะพั‰ะต ะธะผะฐ ะพั‚ะฒะพั€ะตะฝะธ ะทะฐะฒะธัะธะผะพัั‚ะธ -issues.dependency.blocked_by_short = ะ—ะฐะฒะธัะธ ะพั‚ -issues.dependency.setting = ะ’ะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ะทะฐะฒะธัะธะผะพัั‚ะธ ะทะฐ ะทะฐะดะฐั‡ะธ ะธ ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต -issues.dependency.add_error_same_issue = ะะต ะผะพะถะตั‚ะต ะดะฐ ะฝะฐะฟั€ะฐะฒะธั‚ะต ะทะฐะดะฐั‡ะฐ ะทะฐะฒะธัะธะผะฐ ะพั‚ ัะฐะผะฐั‚ะฐ ะฝะตั. -issues.dependency.add_error_dep_issue_not_exist = ะ—ะฐะฒะธัะธะผะฐั‚ะฐ ะทะฐะดะฐั‡ะฐ ะฝะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ. -issues.dependency.add_error_cannot_create_circular = ะะต ะผะพะถะตั‚ะต ะดะฐ ััŠะทะดะฐะดะตั‚ะต ะทะฐะฒะธัะธะผะพัั‚ ั ะดะฒะต ะทะฐะดะฐั‡ะธ, ะบะพะธั‚ะพ ัะต ะฑะปะพะบะธั€ะฐั‚ ะฒะทะฐะธะผะฝะพ. -issues.dependency.add_error_dep_not_same_repo = ะ˜ ะดะฒะตั‚ะต ะทะฐะดะฐั‡ะธ ั‚ั€ัะฑะฒะฐ ะดะฐ ัะฐ ะฒ ะตะดะฝะพ ะธ ััŠั‰ะพ ั…ั€ะฐะฝะธะปะธั‰ะต. -issues.review.self.rejection = ะะต ะผะพะถะตั‚ะต ะดะฐ ะฟะพะธัะบะฐั‚ะต ะฟั€ะพะผะตะฝะธ ะฒ ัะพะฑัั‚ะฒะตะฝะฐั‚ะฐ ัะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต. -issues.review.dismissed = ะพั‚ั…ะฒัŠั€ะปะธ ั€ะตั†ะตะฝะทะธัั‚ะฐ ะฝะฐ %s %s -issues.review.content.empty = ะขั€ัะฑะฒะฐ ะดะฐ ะพัั‚ะฐะฒะธั‚ะต ะบะพะผะตะฝั‚ะฐั€, ะฟะพัะพั‡ะฒะฐั‰ ะธัะบะฐะฝะธั‚ะต ะฟั€ะพะผะตะฝะธ. -issues.review.add_review_requests = ะฟะพะธัะบะฐ ั€ะตั†ะตะฝะทะธะธ ะพั‚ %[1]s %[2]s -issues.review.remove_review_request = ะฟั€ะตะผะฐั…ะฝะฐ ะทะฐัะฒะบะฐั‚ะฐ ะทะฐ ั€ะตั†ะตะฝะทะธั ะทะฐ %[1]s %[2]s -issues.review.remove_review_requests = ะฟั€ะตะผะฐั…ะฝะฐ ะทะฐัะฒะบะธั‚ะต ะทะฐ ั€ะตั†ะตะฝะทะธั ะทะฐ %[1]s %[2]s -issues.review.remove_review_request_self = ะพั‚ะบะฐะทะฐ ะดะฐ ั€ะตั†ะตะฝะทะธั€ะฐ %s -issues.review.pending.tooltip = ะขะพะทะธ ะบะพะผะตะฝั‚ะฐั€ ะฒ ะผะพะผะตะฝั‚ะฐ ะฝะต ะต ะฒะธะดะธะผ ะทะฐ ะดั€ัƒะณะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะปะธ. ะ—ะฐ ะดะฐ ะธะทะฟั€ะฐั‚ะธั‚ะต ะธะทั‡ะฐะบะฒะฐั‰ะธั‚ะต ัะธ ะบะพะผะตะฝั‚ะฐั€ะธ, ะธะทะฑะตั€ะตั‚ะต โ€ž%sโ€œ -> โ€ž%s/%s/%sโ€œ ะฒ ะณะพั€ะฝะฐั‚ะฐ ั‡ะฐัั‚ ะฝะฐ ัั‚ั€ะฐะฝะธั†ะฐั‚ะฐ. -issues.review.outdated = ะžัั‚ะฐั€ัะป -issues.review.outdated_description = ะกัŠะดัŠั€ะถะฐะฝะธะตั‚ะพ ะต ะฟั€ะพะผะตะฝะตะฝะพ, ัะปะตะด ะบะฐั‚ะพ ะต ะฝะฐะฟั€ะฐะฒะตะฝ ั‚ะพะทะธ ะบะพะผะตะฝั‚ะฐั€ -issues.review.show_outdated = ะŸะพะบะฐะทะฒะฐะฝะต ะฝะฐ ะพัั‚ะฐั€ะตะปะธ -issues.review.hide_outdated = ะกะบั€ะธะฒะฐะฝะต ะฝะฐ ะพัั‚ะฐั€ะตะปะธ -issues.content_history.options = ะžะฟั†ะธะธ -issues.blocked_by_user = ะะต ะผะพะถะตั‚ะต ะดะฐ ััŠะทะดะฐะฒะฐั‚ะต ะทะฐะดะฐั‡ะธ ะฒ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต, ะทะฐั‰ะพั‚ะพ ัั‚ะต ะฑะปะพะบะธั€ะฐะฝะธ ะพั‚ ะฟั€ะธั‚ะตะถะฐั‚ะตะปั ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. -comment.blocked_by_user = ะšะพะผะตะฝั‚ะธั€ะฐะฝะตั‚ะพ ะฝะต ะต ะฒัŠะทะผะพะถะฝะพ, ะทะฐั‰ะพั‚ะพ ัั‚ะต ะฑะปะพะบะธั€ะฐะฝะธ ะพั‚ ะฟั€ะธั‚ะตะถะฐั‚ะตะปั ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ะธะปะธ ะพั‚ ะฐะฒั‚ะพั€ะฐ. issues.reopen.blocked_by_user = ะะต ะผะพะถะตั‚ะต ะดะฐ ะพั‚ะฒะพั€ะธั‚ะต ะฝะฐะฝะพะฒะพ ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ, ะทะฐั‰ะพั‚ะพ ัั‚ะต ะฑะปะพะบะธั€ะฐะฝะธ ะพั‚ ะฟั€ะธั‚ะตะถะฐั‚ะตะปั ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ะธะปะธ ะพั‚ ะฐะฒั‚ะพั€ะฐ ะฝะฐ ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ. compare.compare_base = ะพัะฝะพะฒะฐ compare.compare_head = ัั€ะฐะฒะฝัะฒะฐะฝะต +template.one_item = ะขั€ัะฑะฒะฐ ะดะฐ ะธะทะฑะตั€ะตั‚ะต ะฟะพะฝะต ะตะดะธะฝ ะตะปะตะผะตะฝั‚ ะพั‚ ัˆะฐะฑะปะพะฝะฐ +admin.failed_to_replace_flags = ะะตัƒัะฟะตัˆะฝะฐ ะทะฐะผัะฝะฐ ะฝะฐ ั„ะปะฐะณะพะฒะตั‚ะต ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ +mirror_interval_invalid = ะ˜ะฝั‚ะตั€ะฒะฐะปัŠั‚ ะฝะฐ ะพะณะปะตะดะฐะปะพั‚ะพ ะฝะต ะต ะฒะฐะปะธะดะตะฝ. +mirror_use_ssh.not_available = SSH ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะตั‚ะพ ะฝะต ะต ะฝะฐะปะธั‡ะฝะพ. +mirror_address_desc = ะŸะพัั‚ะฐะฒะตั‚ะต ะฒัะธั‡ะบะธ ะฝะตะพะฑั…ะพะดะธะผะธ ะดะฐะฝะฝะธ ะทะฐ ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะต ะฒ ัะตะบั†ะธัั‚ะฐ โ€žะฃะฟัŠะปะฝะพะผะพั‰ะฐะฒะฐะฝะตโ€œ. +template.git_hooks = Git ะบัƒะบะธ +template.invalid = ะขั€ัะฑะฒะฐ ะดะฐ ะธะทะฑะตั€ะตั‚ะต ัˆะฐะฑะปะพะฝะฝะพ ั…ั€ะฐะฝะธะปะธั‰ะต +issues.review.outdated = ะžัั‚ะฐั€ัะป +issues.dependency.add_error_dep_issue_not_exist = ะ—ะฐะฒะธัะธะผะฐั‚ะฐ ะทะฐะดะฐั‡ะฐ ะฝะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ. +template.items = ะ•ะปะตะผะตะฝั‚ะธ ะฝะฐ ัˆะฐะฑะปะพะฝะฐ +issues.review.dismissed = ะพั‚ั…ะฒัŠั€ะปะธ ั€ะตั†ะตะฝะทะธัั‚ะฐ ะฝะฐ %s %s +audio_not_supported_in_browser = ะ’ะฐัˆะธัั‚ ะฑั€ะฐัƒะทัŠั€ ะฝะต ะฟะพะดะดัŠั€ะถะฐ HTML5 ั‚ะฐะณะฐ โ€žaudioโ€œ. +stored_lfs = ะกัŠั…ั€ะฐะฝะตะฝะพ ั Git LFS +commit_graph.select = ะ˜ะทะฑะตั€ะตั‚ะต ะบะปะพะฝะพะฒะต +issues.content_history.options = ะžะฟั†ะธะธ +editor.commit_email = ะ•ะป. ะฟะพั‰ะฐ ะฝะฐ ะฟะพะดะฐะฒะฐะฝะตั‚ะพ +commit.revert-header = ะ’ั€ัŠั‰ะฐะฝะต: %s +commits.desc = ะ ะฐะทะณะปะตะถะดะฐะฝะต ะฝะฐ ะธัั‚ะพั€ะธัั‚ะฐ ะฝะฐ ะฟั€ะพะผะตะฝะธั‚ะต ะฒ ะฟั€ะพะณั€ะฐะผะฝะธั ะบะพะด. +commits.search.tooltip = ะœะพะถะตั‚ะต ะดะฐ ะดะพะฑะฐะฒะธั‚ะต ะฟั€ะตั„ะธะบั ะบัŠะผ ะบะปัŽั‡ะพะฒะธั‚ะต ะดัƒะผะธ ั โ€žauthor:โ€œ, โ€žcommitter:โ€œ, โ€žafter:โ€œ ะธะปะธ โ€žbefore:โ€œ, ะฝะฐะฟั€. โ€žrevert author:Alice before:2019-01-13โ€œ. +issues.unlock_error = ะะต ะผะพะถะต ะดะฐ ัะต ะพั‚ะบะปัŽั‡ะธ ะทะฐะดะฐั‡ะฐ, ะบะพัั‚ะพ ะฝะต ะต ะทะฐะบะปัŽั‡ะตะฝะฐ. +issues.lock.unknown_reason = ะะต ะผะพะถะต ะดะฐ ัะต ะทะฐะบะปัŽั‡ะธ ะทะฐะดะฐั‡ะฐ ั ะฝะตะธะทะฒะตัั‚ะฝะฐ ะฟั€ะธั‡ะธะฝะฐ. +issues.cancel_tracking_history = `ะพั‚ะผะตะฝะธ ะฟั€ะพัะปะตะดัะฒะฐะฝะตั‚ะพ ะฝะฐ ะฒั€ะตะผะตั‚ะพ %s` +issues.dependency.add_error_dep_not_same_repo = ะ˜ ะดะฒะตั‚ะต ะทะฐะดะฐั‡ะธ ั‚ั€ัะฑะฒะฐ ะดะฐ ัะฐ ะฒ ะตะดะฝะพ ะธ ััŠั‰ะพ ั…ั€ะฐะฝะธะปะธั‰ะต. +issues.review.remove_review_requests = ะฟั€ะตะผะฐั…ะฝะฐ ะทะฐัะฒะบะธั‚ะต ะทะฐ ั€ะตั†ะตะฝะทะธั ะทะฐ %[1]s %[2]s +issues.review.content.empty = ะขั€ัะฑะฒะฐ ะดะฐ ะพัั‚ะฐะฒะธั‚ะต ะบะพะผะตะฝั‚ะฐั€, ะฟะพัะพั‡ะฒะฐั‰ ะธัะบะฐะฝะธั‚ะต ะฟั€ะพะผะตะฝะธ. +issues.review.hide_outdated = ะกะบั€ะธะฒะฐะฝะต ะฝะฐ ะพัั‚ะฐั€ะตะปะธ pulls.desc = ะ’ะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต ะธ ั€ะตั†ะตะฝะทะธะธ ะฝะฐ ะบะพะด. +issues.review.show_outdated = ะŸะพะบะฐะทะฒะฐะฝะต ะฝะฐ ะพัั‚ะฐั€ะตะปะธ +ambiguous_runes_header = `ะขะพะทะธ ั„ะฐะนะป ััŠะดัŠั€ะถะฐ ะดะฒัƒัะผะธัะปะตะฝะธ ะฃะฝะธะบะพะด ะทะฝะฐั†ะธ` +admin.update_flags = ะžะฑะฝะพะฒัะฒะฐะฝะต ะฝะฐ ั„ะปะฐะณะพะฒะตั‚ะต +issues.dependency.blocked_by_short = ะ—ะฐะฒะธัะธ ะพั‚ +mirror_lfs = ะกัŠั…ั€ะฐะฝะตะฝะธะต ะฝะฐ ะณะพะปะตะผะธ ั„ะฐะนะปะพะฒะต (LFS) +mirror_use_ssh.text = ะ˜ะทะฟะพะปะทะฒะฐะฝะต ะฝะฐ SSH ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะต +mirror_denied_combination = ะะต ะผะพะถะต ะดะฐ ัะต ะธะทะฟะพะปะทะฒะฐ ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะต ั ะฟัƒะฑะปะธั‡ะตะฝ ะบะปัŽั‡ ะธ ะฟะฐั€ะพะปะฐ ะตะดะฝะพะฒั€ะตะผะตะฝะฝะพ. +rss.must_be_on_branch = ะขั€ัะฑะฒะฐ ะดะฐ ัั‚ะต ะฝะฐ ะบะปะพะฝ, ะทะฐ ะดะฐ ะธะผะฐั‚ะต RSS ะตะผะธัะธั. +admin.manage_flags = ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ ั„ะปะฐะณะพะฒะตั‚ะต +admin.enabled_flags = ะคะปะฐะณะพะฒะต, ะฒะบะปัŽั‡ะตะฝะธ ะทะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ: +mirror_password_help = ะŸั€ะพะผะตะฝะตั‚ะต ะฟะพั‚ั€ะตะฑะธั‚ะตะปัะบะพั‚ะพ ะธะผะต, ะทะฐ ะดะฐ ะธะทั‚ั€ะธะตั‚ะต ะทะฐะฟะฐะทะตะฝะฐ ะฟะฐั€ะพะปะฐ. +issues.review.remove_review_request = ะฟั€ะตะผะฐั…ะฝะฐ ะทะฐัะฒะบะฐั‚ะฐ ะทะฐ ั€ะตั†ะตะฝะทะธั ะทะฐ %[1]s %[2]s +issues.review.outdated_description = ะกัŠะดัŠั€ะถะฐะฝะธะตั‚ะพ ะต ะฟั€ะพะผะตะฝะตะฝะพ, ัะปะตะด ะบะฐั‚ะพ ะต ะฝะฐะฟั€ะฐะฒะตะฝ ั‚ะพะทะธ ะบะพะผะตะฝั‚ะฐั€ +issues.dependency.setting = ะ’ะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ะทะฐะฒะธัะธะผะพัั‚ะธ ะทะฐ ะทะฐะดะฐั‡ะธ ะธ ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต +issues.dependency.add_error_same_issue = ะะต ะผะพะถะตั‚ะต ะดะฐ ะฝะฐะฟั€ะฐะฒะธั‚ะต ะทะฐะดะฐั‡ะฐ ะทะฐะฒะธัะธะผะฐ ะพั‚ ัะฐะผะฐั‚ะฐ ะฝะตั. +issues.review.self.rejection = ะะต ะผะพะถะตั‚ะต ะดะฐ ะฟะพะธัะบะฐั‚ะต ะฟั€ะพะผะตะฝะธ ะฒ ัะพะฑัั‚ะฒะตะฝะฐั‚ะฐ ัะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต. +issues.filter_type.all_pull_requests = ะ’ัะธั‡ะบะธ ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต +fork_to_different_account = ะ ะฐะทะบะปะพะฝัะฒะฐะฝะต ะฒ ะดั€ัƒะณ ะฐะบะฐัƒะฝั‚ +mirror_sync_on_commit = ะกะธะฝั…ั€ะพะฝะธะทะธั€ะฐะฝะต ะฟั€ะธ ะธะทั‚ะปะฐัะบะฒะฐะฝะต ะฝะฐ ะฟะพะดะฐะฒะฐะฝะธั +mirror_address_protocol_invalid = ะŸั€ะตะดะพัั‚ะฐะฒะตะฝะธัั‚ URL ะต ะฝะตะฒะฐะปะธะดะตะฝ. ะกะฐะผะพ http(s):// ะธะปะธ git:// ะฐะดั€ะตัะธ ะผะพะณะฐั‚ ะดะฐ ัะต ะธะทะฟะพะปะทะฒะฐั‚ ะทะฐ ะพะณะปะตะดะฐะปะฝะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ. +template.git_hooks_tooltip = ะ’ ะผะพะผะตะฝั‚ะฐ ะฝะต ะผะพะถะตั‚ะต ะดะฐ ะฟั€ะพะผะตะฝัั‚ะต ะธะปะธ ะฟั€ะตะผะฐั…ะฒะฐั‚ะต Git ะบัƒะบะธ, ัะปะตะด ะบะฐั‚ะพ ัะฐ ะดะพะฑะฐะฒะตะฝะธ. ะ˜ะทะฑะตั€ะตั‚ะต ั‚ะพะฒะฐ ัะฐะผะพ ะฐะบะพ ัะต ะดะพะฒะตั€ัะฒะฐั‚ะต ะฝะฐ ัˆะฐะฑะปะพะฝะฝะพั‚ะพ ั…ั€ะฐะฝะธะปะธั‰ะต. +editor.commit_signed_changes = ะŸะพะดะฐะฒะฐะฝะต ะฝะฐ ะฟะพะดะฟะธัะฐะฝะธ ะฟั€ะพะผะตะฝะธ +editor.require_signed_commit = ะšะปะพะฝัŠั‚ ะธะทะธัะบะฒะฐ ะฟะพะดะฟะธัะฐะฝะพ ะฟะพะดะฐะฒะฐะฝะต +issues.desc = ะžั€ะณะฐะฝะธะทะธั€ะฐะนั‚ะต ะดะพะบะปะฐะดะธ ะทะฐ ะณั€ะตัˆะบะธ, ะทะฐะดะฐั‡ะธ ะธ ะตั‚ะฐะฟะธ. +issues.lock_duplicate = ะ—ะฐะดะฐั‡ะฐ ะฝะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ะทะฐะบะปัŽั‡ะตะฝะฐ ะดะฒะฐ ะฟัŠั‚ะธ. +issues.lock.notice_2 = - ะ’ะธะต ะธ ะดั€ัƒะณะธ ััŠั‚ั€ัƒะดะฝะธั†ะธ ั ะดะพัั‚ัŠะฟ ะดะพ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต ะฒัะต ะพั‰ะต ะผะพะถะตั‚ะต ะดะฐ ะพัั‚ะฐะฒัั‚ะต ะบะพะผะตะฝั‚ะฐั€ะธ, ะบะพะธั‚ะพ ะดั€ัƒะณะธั‚ะต ะดะฐ ะฒะธะถะดะฐั‚. +issues.due_date_invalid = ะšั€ะฐะนะฝะธัั‚ ัั€ะพะบ ะต ะฝะตะฒะฐะปะธะดะตะฝ ะธะปะธ ะธะทะฒัŠะฝ ะพะฑั…ะฒะฐั‚ะฐ. ะœะพะปั, ะธะทะฟะพะปะทะฒะฐะนั‚ะต ั„ะพั€ะผะฐั‚ะฐ โ€žะณะณะณะณ-ะผะผ-ะดะดโ€œ. +mirror_interval = ะ˜ะฝั‚ะตั€ะฒะฐะป ะฝะฐ ะพะณะปะตะดะฐะปะพั‚ะพ (ะฒะฐะปะธะดะฝะธ ะตะดะธะฝะธั†ะธ ะทะฐ ะฒั€ะตะผะต ัะฐ โ€žhโ€œ, โ€žmโ€œ, โ€žsโ€œ). 0 ะทะฐ ะธะทะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ะฟะตั€ะธะพะดะธั‡ะฝะฐั‚ะฐ ัะธะฝั…ั€ะพะฝะธะทะฐั†ะธั. (ะœะธะฝะธะผะฐะปะตะฝ ะธะฝั‚ะตั€ะฒะฐะป: %s) +summary_card_alt = ะšะฐั€ั‚ะฐ ั ะพะฑะพะฑั‰ะตะฝะธะต ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะต %s +file_copy_permalink = ะšะพะฟะธั€ะฐะฝะต ะฝะฐ ะฟะพัั‚ะพัะฝะฝะฐ ะฒั€ัŠะทะบะฐ +view_git_blame = ะŸั€ะตะณะปะตะด ะฝะฐ git blame +commit.revert-content = ะ˜ะทะฑะตั€ะตั‚ะต ะบะปะพะฝ, ะฒัŠั€ั…ัƒ ะบะพะนั‚ะพ ะดะฐ ัะต ะฒัŠั€ะฝะต: +issues.unlock.notice_1 = - ะ’ัะตะบะธ ั‰ะต ะผะพะถะต ะพั‚ะฝะพะฒะพ ะดะฐ ะบะพะผะตะฝั‚ะธั€ะฐ ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ. +issues.delete.text = ะะฐะธัั‚ะธะฝะฐ ะปะธ ะธัะบะฐั‚ะต ะดะฐ ะธะทั‚ั€ะธะตั‚ะต ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ? (ะขะพะฒะฐ ั‰ะต ะฟั€ะตะผะฐั…ะฝะต ั‚ั€ะฐะนะฝะพ ั†ัะปะพั‚ะพ ััŠะดัŠั€ะถะฐะฝะธะต. ะŸะพะผะธัะปะตั‚ะต ะดะฐะปะธ ะฒะผะตัั‚ะพ ั‚ะพะฒะฐ ะดะฐ ะฝะต ั ะทะฐั‚ะฒะพั€ะธั‚ะต, ะฐะบะพ ะฒัŠะทะฝะฐะผะตั€ัะฒะฐั‚ะต ะดะฐ ั ะทะฐะฟะฐะทะธั‚ะต ะฐั€ั…ะธะฒะธั€ะฐะฝะฐ) +issues.add_time_sum_to_small = ะะต ะต ะฒัŠะฒะตะดะตะฝะพ ะฒั€ะตะผะต. +issues.dependency.no_permission_n = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะดะฐ ะฟั€ะพั‡ะตั‚ะตั‚ะต %d ะทะฐะฒะธัะธะผะพัั‚ะธ +issues.review.pending.tooltip = ะขะพะทะธ ะบะพะผะตะฝั‚ะฐั€ ะฒ ะผะพะผะตะฝั‚ะฐ ะฝะต ะต ะฒะธะดะธะผ ะทะฐ ะดั€ัƒะณะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะปะธ. ะ—ะฐ ะดะฐ ะธะทะฟั€ะฐั‚ะธั‚ะต ะธะทั‡ะฐะบะฒะฐั‰ะธั‚ะต ัะธ ะบะพะผะตะฝั‚ะฐั€ะธ, ะธะทะฑะตั€ะตั‚ะต โ€ž%sโ€œ -> โ€ž%s/%s/%sโ€œ ะฒ ะณะพั€ะฝะฐั‚ะฐ ั‡ะฐัั‚ ะฝะฐ ัั‚ั€ะฐะฝะธั†ะฐั‚ะฐ. +invisible_runes_description = `ะขะพะทะธ ั„ะฐะนะป ััŠะดัŠั€ะถะฐ ะฝะตะฒะธะดะธะผะธ ะฃะฝะธะบะพะด ะทะฝะฐั†ะธ, ะบะพะธั‚ะพ ัะฐ ะฝะตั€ะฐะทะปะธั‡ะธะผะธ ะทะฐ ั…ะพั€ะฐั‚ะฐ, ะฝะพ ะผะพะณะฐั‚ ะดะฐ ะฑัŠะดะฐั‚ ะพะฑั€ะฐะฑะพั‚ะตะฝะธ ะฟะพ ั€ะฐะทะปะธั‡ะตะฝ ะฝะฐั‡ะธะฝ ะพั‚ ะบะพะผะฟัŽั‚ัŠั€. ะะบะพ ัะผัั‚ะฐั‚ะต, ั‡ะต ั‚ะพะฒะฐ ะต ัƒะผะธัˆะปะตะฝะพ, ะผะพะถะตั‚ะต ัะฟะพะบะพะนะฝะพ ะดะฐ ะฟั€ะตะฝะตะฑั€ะตะณะฝะตั‚ะต ั‚ะพะฒะฐ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต. ะ˜ะทะฟะพะปะทะฒะฐะนั‚ะต ะฑัƒั‚ะพะฝะฐ โ€žะ•ะบั€ะฐะฝะธั€ะฐะฝะตโ€œ, ะทะฐ ะดะฐ ะณะธ ั€ะฐะทะบั€ะธะตั‚ะต.` +video_not_supported_in_browser = ะ’ะฐัˆะธัั‚ ะฑั€ะฐัƒะทัŠั€ ะฝะต ะฟะพะดะดัŠั€ะถะฐ HTML5 ั‚ะฐะณะฐ โ€žvideoโ€œ. +editor.filename_help = ะ”ะพะฑะฐะฒะตั‚ะต ะดะธั€ะตะบั‚ะพั€ะธั, ะบะฐั‚ะพ ะฒัŠะฒะตะดะตั‚ะต ะธะผะตั‚ะพ ั, ะฟะพัะปะตะดะฒะฐะฝะพ ะพั‚ ะฝะฐะบะปะพะฝะตะฝะฐ ั‡ะตั€ั‚ะฐ (โ€ž/โ€œ). ะŸั€ะตะผะฐั…ะฝะตั‚ะต ะดะธั€ะตะบั‚ะพั€ะธั, ะบะฐั‚ะพ ะฝะฐั‚ะธัะฝะตั‚ะต backspace ะฒ ะฝะฐั‡ะฐะปะพั‚ะพ ะฝะฐ ะฟะพะปะตั‚ะพ ะทะฐ ะฒัŠะฒะตะถะดะฐะฝะต. +commits.view_single_diff = ะŸั€ะตะณะปะตะด ะฝะฐ ะฟั€ะพะผะตะฝะธั‚ะต ะฒ ั‚ะพะทะธ ั„ะฐะนะป, ะฒัŠะฒะตะดะตะฝะธ ะฒ ั‚ะพะฒะฐ ะฟะพะดะฐะฒะฐะฝะต +issues.choose.ignore_invalid_templates = ะะตะฒะฐะปะธะดะฝะธั‚ะต ัˆะฐะฑะปะพะฝะธ ัะฐ ะธะณะฝะพั€ะธั€ะฐะฝะธ +issues.due_date_form = ะณะณะณะณ-ะผะผ-ะดะด +issues.dependency.no_permission.can_remove = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะดะฐ ะฟั€ะพั‡ะตั‚ะตั‚ะต ั‚ะฐะทะธ ะทะฐะฒะธัะธะผะพัั‚, ะฝะพ ะผะพะถะตั‚ะต ะดะฐ ั ะฟั€ะตะผะฐั…ะฝะตั‚ะต +issues.review.remove_review_request_self = ะพั‚ะบะฐะทะฐ ะดะฐ ั€ะตั†ะตะฝะทะธั€ะฐ %s +mirror_use_ssh.helper = Forgejo ั‰ะต ััŠะทะดะฐะดะต ะพะณะปะตะดะฐะปะพ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ั‡ั€ะตะท Git ะฟั€ะตะท SSH ะธ ั‰ะต ะณะตะฝะตั€ะธั€ะฐ ะดะฒะพะนะบะฐ ะบะปัŽั‡ะพะฒะต ะทะฐ ะฒะฐั, ะบะพะณะฐั‚ะพ ะธะทะฑะตั€ะตั‚ะต ั‚ะฐะทะธ ะพะฟั†ะธั. ะขั€ัะฑะฒะฐ ะดะฐ ัะต ัƒะฒะตั€ะธั‚ะต, ั‡ะต ะณะตะฝะตั€ะธั€ะฐะฝะธัั‚ ะฟัƒะฑะปะธั‡ะตะฝ ะบะปัŽั‡ ะต ัƒะฟัŠะปะฝะพะผะพั‰ะตะฝ ะดะฐ ะธะทั‚ะปะฐัะบะฒะฐ ะบัŠะผ ั†ะตะปะตะฒะพั‚ะพ ั…ั€ะฐะฝะธะปะธั‰ะต. ะะต ะผะพะถะตั‚ะต ะดะฐ ะธะทะฟะพะปะทะฒะฐั‚ะต ัƒะดะพัั‚ะพะฒะตั€ัะฒะฐะฝะต, ะฑะฐะทะธั€ะฐะฝะพ ะฝะฐ ะฟะฐั€ะพะปะฐ, ะบะพะณะฐั‚ะพ ะธะทะฑะธั€ะฐั‚ะต ั‚ะพะฒะฐ. +mirror_address_url_invalid = ะŸั€ะตะดะพัั‚ะฐะฒะตะฝะธัั‚ URL ะต ะฝะตะฒะฐะปะธะดะตะฝ. ะฃะฒะตั€ะตั‚ะต ัะต, ั‡ะต ะบะพะผะฟะพะฝะตะฝั‚ะธั‚ะต ะฝะฐ URL ะฐะดั€ะตัะฐ ัะฐ ะตะบั€ะฐะฝะธั€ะฐะฝะธ ะฟั€ะฐะฒะธะปะฝะพ. +template.git_content = Git ััŠะดัŠั€ะถะฐะฝะธะต (ัั‚ะฐะฝะดะฐั€ั‚ะตะฝ ะบะปะพะฝ) +ambiguous_runes_description = `ะขะพะทะธ ั„ะฐะนะป ััŠะดัŠั€ะถะฐ ะฃะฝะธะบะพะด ะทะฝะฐั†ะธ, ะบะพะธั‚ะพ ะผะพะณะฐั‚ ะดะฐ ะฑัŠะดะฐั‚ ะพะฑัŠั€ะบะฐะฝะธ ั ะดั€ัƒะณะธ ะทะฝะฐั†ะธ. ะะบะพ ัะผัั‚ะฐั‚ะต, ั‡ะต ั‚ะพะฒะฐ ะต ัƒะผะธัˆะปะตะฝะพ, ะผะพะถะตั‚ะต ัะฟะพะบะพะนะฝะพ ะดะฐ ะฟั€ะตะฝะตะฑั€ะตะณะฝะตั‚ะต ั‚ะพะฒะฐ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต. ะ˜ะทะฟะพะปะทะฒะฐะนั‚ะต ะฑัƒั‚ะพะฝะฐ โ€žะ•ะบั€ะฐะฝะธั€ะฐะฝะตโ€œ, ะทะฐ ะดะฐ ะณะธ ั€ะฐะทะบั€ะธะตั‚ะต.` +issues.lock.notice_3 = - ะ’ะธะฝะฐะณะธ ะผะพะถะตั‚ะต ะดะฐ ะพั‚ะบะปัŽั‡ะธั‚ะต ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ ะพั‚ะฝะพะฒะพ ะฒ ะฑัŠะดะตั‰ะต. +issues.lock.title = ะ—ะฐะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ะพะฑััŠะถะดะฐะฝะตั‚ะพ ะฟะพ ั‚ะฐะทะธ ะทะฐะดะฐั‡ะฐ. +issues.dependency.issue_batch_close_blocked = ะะต ะผะพะณะฐั‚ ะดะฐ ะฑัŠะดะฐั‚ ะทะฐั‚ะฒะพั€ะตะฝะธ ะณั€ัƒะฟะพะฒะพ ะธะทะฑั€ะฐะฝะธั‚ะต ะทะฐะดะฐั‡ะธ, ะทะฐั‰ะพั‚ะพ ะทะฐะดะฐั‡ะฐ #%d ะฒัะต ะพั‰ะต ะธะผะฐ ะพั‚ะฒะพั€ะตะฝะธ ะทะฐะฒะธัะธะผะพัั‚ะธ +issues.dependency.add_error_cannot_create_circular = ะะต ะผะพะถะตั‚ะต ะดะฐ ััŠะทะดะฐะดะตั‚ะต ะทะฐะฒะธัะธะผะพัั‚ ั ะดะฒะต ะทะฐะดะฐั‡ะธ, ะบะพะธั‚ะพ ัะต ะฑะปะพะบะธั€ะฐั‚ ะฒะทะฐะธะผะฝะพ. +issues.review.add_review_requests = ะฟะพะธัะบะฐ ั€ะตั†ะตะฝะทะธะธ ะพั‚ %[1]s %[2]s +comment.blocked_by_user = ะšะพะผะตะฝั‚ะธั€ะฐะฝะตั‚ะพ ะฝะต ะต ะฒัŠะทะผะพะถะฝะพ, ะทะฐั‰ะพั‚ะพ ัั‚ะต ะฑะปะพะบะธั€ะฐะฝะธ ะพั‚ ะฟั€ะธั‚ะตะถะฐั‚ะตะปั ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ะธะปะธ ะพั‚ ะฐะฒั‚ะพั€ะฐ. pulls.view = ะŸั€ะตะณะปะตะด ะฝะฐ ะทะฐัะฒะบะฐั‚ะฐ ะทะฐ ัะปะธะฒะฐะฝะต -pulls.allow_edits_from_maintainers_desc = ะŸะพั‚ั€ะตะฑะธั‚ะตะปะธ ั ะฟั€ะฐะฒะพ ะฝะฐ ะทะฐะฟะธั ะฒ ะพัะฝะพะฒะฝะธั ะบะปะพะฝ ะผะพะณะฐั‚ ััŠั‰ะพ ะดะฐ ะธะทั‚ะปะฐัะบะฒะฐั‚ ะบัŠะผ ั‚ะพะทะธ ะบะปะพะฝ -pulls.allow_edits_from_maintainers_err = ะžะฑะฝะพะฒัะฒะฐะฝะตั‚ะพ ะต ะฝะตัƒัะฟะตัˆะฝะพ -pulls.has_changed_since_last_review = ะŸั€ะพะผะตะฝะตะฝะพ ัะปะตะด ะฟะพัะปะตะดะฝะฐั‚ะฐ ะฒะธ ั€ะตั†ะตะฝะทะธั -pulls.switch_comparison_type = ะŸั€ะตะฒะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ั‚ะธะฟะฐ ัั€ะฐะฒะฝะตะฝะธะต -pulls.filter_branch = ะคะธะปั‚ั€ะธั€ะฐะฝะต ะฝะฐ ะบะปะพะฝ -pulls.review_only_possible_for_full_diff = ะ ะตั†ะตะฝะทะธั€ะฐะฝะตั‚ะพ ะต ะฒัŠะทะผะพะถะฝะพ ัะฐะผะพ ะฟั€ะธ ะฟั€ะตะณะปะตะด ะฝะฐ ะฟัŠะปะฝะธั‚ะต ั€ะฐะทะปะธะบะธ -pulls.wrong_commit_id = ID ะฝะฐ ะฟะพะดะฐะฒะฐะฝะตั‚ะพ ั‚ั€ัะฑะฒะฐ ะดะฐ ะฑัŠะดะต ID ะฝะฐ ะฟะพะดะฐะฒะฐะฝะต ะฒ ั†ะตะปะตะฒะธั ะบะปะพะฝ -pulls.blocked_by_user = ะะต ะผะพะถะตั‚ะต ะดะฐ ััŠะทะดะฐะดะตั‚ะต ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะฒ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต, ะทะฐั‰ะพั‚ะพ ัั‚ะต ะฑะปะพะบะธั€ะฐะฝะธ ะพั‚ ะฟั€ะธั‚ะตะถะฐั‚ะตะปั ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. pulls.no_merge_desc = ะขะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะฝะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ัะปัั‚ะฐ, ะทะฐั‰ะพั‚ะพ ะฒัะธั‡ะบะธ ะพะฟั†ะธะธ ะทะฐ ัะปะธะฒะฐะฝะต ะฒ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ัะฐ ะธะทะบะปัŽั‡ะตะฝะธ. -pulls.no_merge_helper = ะ’ะบะปัŽั‡ะตั‚ะต ะพะฟั†ะธะธั‚ะต ะทะฐ ัะปะธะฒะฐะฝะต ะฒ ะฝะฐัั‚ั€ะพะนะบะธั‚ะต ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ะธะปะธ ัะปะตะนั‚ะต ะทะฐัะฒะบะฐั‚ะฐ ะทะฐ ัะปะธะฒะฐะฝะต ั€ัŠั‡ะฝะพ. pulls.no_merge_wip = ะขะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะฝะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ัะปัั‚ะฐ, ะทะฐั‰ะพั‚ะพ ะต ะพั‚ะฑะตะปัะทะฐะฝะฐ ะบะฐั‚ะพ ะฒ ะฟั€ะพั†ะตั ะฝะฐ ั€ะฐะฑะพั‚ะฐ. +pulls.switch_comparison_type = ะŸั€ะตะฒะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ั‚ะธะฟะฐ ัั€ะฐะฒะฝะตะฝะธะต +pulls.has_changed_since_last_review = ะŸั€ะพะผะตะฝะตะฝะพ ัะปะตะด ะฟะพัะปะตะดะฝะฐั‚ะฐ ะฒะธ ั€ะตั†ะตะฝะทะธั +pulls.filter_branch = ะคะธะปั‚ั€ะธั€ะฐะฝะต ะฝะฐ ะบะปะพะฝ pulls.squash_merge_pull_request = ะกัŠะทะดะฐะฒะฐะฝะต ะฝะฐ ัะฟะปะตัะบะฐะฝะพ ะฟะพะดะฐะฒะฐะฝะต +pulls.rebase_conflict_summary = ะกัŠะพะฑั‰ะตะฝะธะต ะทะฐ ะณั€ะตัˆะบะฐ +pulls.auto_merge_button_when_succeed = (ะšะพะณะฐั‚ะพ ะฟั€ะพะฒะตั€ะบะธั‚ะต ัะฐ ัƒัะฟะตัˆะฝะธ) +pulls.auto_merge_newly_scheduled_comment = `ะฝะฐัั€ะพั‡ะธ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะทะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ัะปะธะฒะฐะฝะต, ะบะพะณะฐั‚ะพ ะฒัะธั‡ะบะธ ะฟั€ะพะฒะตั€ะบะธ ัะฐ ัƒัะฟะตัˆะฝะธ %[1]s` +pulls.auto_merge_canceled_schedule_comment = `ะพั‚ะผะตะฝะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพั‚ะพ ัะปะธะฒะฐะฝะต ะฝะฐ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต, ะบะพะณะฐั‚ะพ ะฒัะธั‡ะบะธ ะฟั€ะพะฒะตั€ะบะธ ัะฐ ัƒัะฟะตัˆะฝะธ %[1]s` pulls.merge_manually = ะ ัŠั‡ะฝะพ ัะปัั‚ะฐ pulls.merge_commit_id = ID ะฝะฐ ะฟะพะดะฐะฒะฐะฝะตั‚ะพ ััŠั ัะปะธะฒะฐะฝะต pulls.require_signed_wont_sign = ะšะปะพะฝัŠั‚ ะธะทะธัะบะฒะฐ ะฟะพะดะฟะธัะฐะฝะธ ะฟะพะดะฐะฒะฐะฝะธั, ะฝะพ ั‚ะพะฒะฐ ัะปะธะฒะฐะฝะต ะฝัะผะฐ ะดะฐ ะฑัŠะดะต ะฟะพะดะฟะธัะฐะฝะพ -pulls.merge_conflict = ะกะปะธะฒะฐะฝะตั‚ะพ ะต ะฝะตัƒัะฟะตัˆะฝะพ: ะ’ัŠะทะฝะธะบะฝะฐ ะบะพะฝั„ะปะธะบั‚ ะฟะพ ะฒั€ะตะผะต ะฝะฐ ัะปะธะฒะฐะฝะตั‚ะพ. ะŸะพะดัะบะฐะทะบะฐ: ะžะฟะธั‚ะฐะนั‚ะต ั€ะฐะทะปะธั‡ะฝะฐ ัั‚ั€ะฐั‚ะตะณะธั -pulls.merge_conflict_summary = ะกัŠะพะฑั‰ะตะฝะธะต ะทะฐ ะณั€ะตัˆะบะฐ -pulls.rebase_conflict_summary = ะกัŠะพะฑั‰ะตะฝะธะต ะทะฐ ะณั€ะตัˆะบะฐ -pulls.has_merged = ะะตัƒัะฟะตัˆะฝะพ: ะ—ะฐัะฒะบะฐั‚ะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะต ัะปัั‚ะฐ, ะฝะต ะผะพะถะตั‚ะต ะดะฐ ัะปะตะตั‚ะต ะพั‚ะฝะพะฒะพ ะธะปะธ ะดะฐ ะฟั€ะพะผะตะฝะธั‚ะต ั†ะตะปะตะฒะธั ะบะปะพะฝ. +pulls.no_merge_helper = ะ’ะบะปัŽั‡ะตั‚ะต ะพะฟั†ะธะธั‚ะต ะทะฐ ัะปะธะฒะฐะฝะต ะฒ ะฝะฐัั‚ั€ะพะนะบะธั‚ะต ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ะธะปะธ ัะปะตะนั‚ะต ะทะฐัะฒะบะฐั‚ะฐ ะทะฐ ัะปะธะฒะฐะฝะต ั€ัŠั‡ะฝะพ. +pulls.review_only_possible_for_full_diff = ะ ะตั†ะตะฝะทะธั€ะฐะฝะตั‚ะพ ะต ะฒัŠะทะผะพะถะฝะพ ัะฐะผะพ ะฟั€ะธ ะฟั€ะตะณะปะตะด ะฝะฐ ะฟัŠะปะฝะธั‚ะต ั€ะฐะทะปะธะบะธ pulls.push_rejected = ะ˜ะทั‚ะปะฐัะบะฒะฐะฝะตั‚ะพ ะต ะฝะตัƒัะฟะตัˆะฝะพ: ะ˜ะทั‚ะปะฐัะบะฒะฐะฝะตั‚ะพ ะต ะพั‚ั…ะฒัŠั€ะปะตะฝะพ. ะŸั€ะตะณะปะตะดะฐะนั‚ะต Git ะบัƒะบะธั‚ะต ะทะฐ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต. -pulls.push_rejected_no_message = ะ˜ะทั‚ะปะฐัะบะฒะฐะฝะตั‚ะพ ะต ะฝะตัƒัะฟะตัˆะฝะพ: ะ˜ะทั‚ะปะฐัะบะฒะฐะฝะตั‚ะพ ะต ะพั‚ั…ะฒัŠั€ะปะตะฝะพ, ะฝะพ ะฝัะผะฐ ะพั‚ะดะฐะปะตั‡ะตะฝะพ ััŠะพะฑั‰ะตะฝะธะต. ะŸั€ะตะณะปะตะดะฐะนั‚ะต Git ะบัƒะบะธั‚ะต ะทะฐ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต -pulls.update_not_allowed = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะดะฐ ะพะฑะฝะพะฒัะฒะฐั‚ะต ะบะปะพะฝะฐ -pulls.outdated_with_base_branch = ะขะพะทะธ ะบะปะพะฝ ะต ะพัั‚ะฐั€ัะป ัะฟั€ัะผะพ ะพัะฝะพะฒะฝะธั ะบะปะพะฝ -pulls.cmd_instruction_merge_warning = ะŸั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต: ะะฐัั‚ั€ะพะนะบะฐั‚ะฐ โ€žะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะพั‚ะบั€ะธะฒะฐะฝะต ะฝะฐ ั€ัŠั‡ะฝะพ ัะปะธะฒะฐะฝะตโ€œ ะฝะต ะต ะฒะบะปัŽั‡ะตะฝะฐ ะทะฐ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต, ั‰ะต ั‚ั€ัะฑะฒะฐ ะดะฐ ะพั‚ะฑะตะปะตะถะธั‚ะต ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะบะฐั‚ะพ ั€ัŠั‡ะฝะพ ัะปัั‚ะฐ ัะปะตะด ั‚ะพะฒะฐ. -pulls.editable_explanation = ะขะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะฟะพะทะฒะพะปัะฒะฐ ั€ะตะดะฐะบั†ะธะธ ะพั‚ ะฟะพะดะดัŠั€ะถะฐั‰ะธั‚ะต. ะœะพะถะตั‚ะต ะดะฐ ะดะพะฟั€ะธะฝะตัะตั‚ะต ะดะธั€ะตะบั‚ะฝะพ ะบัŠะผ ะฝะตั. -pulls.auto_merge_button_when_succeed = (ะšะพะณะฐั‚ะพ ะฟั€ะพะฒะตั€ะบะธั‚ะต ัะฐ ัƒัะฟะตัˆะฝะธ) -pulls.auto_merge_when_succeed = ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ัะปะธะฒะฐะฝะต, ะบะพะณะฐั‚ะพ ะฒัะธั‡ะบะธ ะฟั€ะพะฒะตั€ะบะธ ัะฐ ัƒัะฟะตัˆะฝะธ -pulls.auto_merge_newly_scheduled = ะ—ะฐัะฒะบะฐั‚ะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะต ะฝะฐัั€ะพั‡ะตะฝะฐ ะทะฐ ัะปะธะฒะฐะฝะต, ะบะพะณะฐั‚ะพ ะฒัะธั‡ะบะธ ะฟั€ะพะฒะตั€ะบะธ ัะฐ ัƒัะฟะตัˆะฝะธ. -pulls.auto_merge_has_pending_schedule = %[1]s ะฝะฐัั€ะพั‡ะธ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะทะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ัะปะธะฒะฐะฝะต, ะบะพะณะฐั‚ะพ ะฒัะธั‡ะบะธ ะฟั€ะพะฒะตั€ะบะธ ัะฐ ัƒัะฟะตัˆะฝะธ %[2]s. +pulls.auto_merge_canceled_schedule = ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพั‚ะพ ัะปะธะฒะฐะฝะต ะต ะพั‚ะผะตะฝะตะฝะพ ะทะฐ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต. +pulls.allow_edits_from_maintainers_err = ะžะฑะฝะพะฒัะฒะฐะฝะตั‚ะพ ะต ะฝะตัƒัะฟะตัˆะฝะพ pulls.auto_merge_cancel_schedule = ะžั‚ะผัะฝะฐ ะฝะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพั‚ะพ ัะปะธะฒะฐะฝะต pulls.auto_merge_not_scheduled = ะขะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะฝะต ะต ะฝะฐัั€ะพั‡ะตะฝะฐ ะทะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ัะปะธะฒะฐะฝะต. -pulls.auto_merge_canceled_schedule = ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพั‚ะพ ัะปะธะฒะฐะฝะต ะต ะพั‚ะผะตะฝะตะฝะพ ะทะฐ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต. -pulls.auto_merge_newly_scheduled_comment = `ะฝะฐัั€ะพั‡ะธ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะทะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ัะปะธะฒะฐะฝะต, ะบะพะณะฐั‚ะพ ะฒัะธั‡ะบะธ ะฟั€ะพะฒะตั€ะบะธ ัะฐ ัƒัะฟะตัˆะฝะธ %[1]s` -pulls.auto_merge_canceled_schedule_comment = `ะพั‚ะผะตะฝะธ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพั‚ะพ ัะปะธะฒะฐะฝะต ะฝะฐ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต, ะบะพะณะฐั‚ะพ ะฒัะธั‡ะบะธ ะฟั€ะพะฒะตั€ะบะธ ัะฐ ัƒัะฟะตัˆะฝะธ %[1]s` +pulls.outdated_with_base_branch = ะขะพะทะธ ะบะปะพะฝ ะต ะพัั‚ะฐั€ัะป ัะฟั€ัะผะพ ะพัะฝะพะฒะฝะธั ะบะปะพะฝ +pulls.update_not_allowed = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะดะฐ ะพะฑะฝะพะฒัะฒะฐั‚ะต ะบะปะพะฝะฐ +pulls.wrong_commit_id = ID ะฝะฐ ะฟะพะดะฐะฒะฐะฝะตั‚ะพ ั‚ั€ัะฑะฒะฐ ะดะฐ ะฑัŠะดะต ID ะฝะฐ ะฟะพะดะฐะฒะฐะฝะต ะฒ ั†ะตะปะตะฒะธั ะบะปะพะฝ +pulls.blocked_by_user = ะะต ะผะพะถะตั‚ะต ะดะฐ ััŠะทะดะฐะดะตั‚ะต ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะฒ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต, ะทะฐั‰ะพั‚ะพ ัั‚ะต ะฑะปะพะบะธั€ะฐะฝะธ ะพั‚ ะฟั€ะธั‚ะตะถะฐั‚ะตะปั ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. +pulls.merge_conflict_summary = ะกัŠะพะฑั‰ะตะฝะธะต ะทะฐ ะณั€ะตัˆะบะฐ +pulls.editable_explanation = ะขะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะฟะพะทะฒะพะปัะฒะฐ ั€ะตะดะฐะบั†ะธะธ ะพั‚ ะฟะพะดะดัŠั€ะถะฐั‰ะธั‚ะต. ะœะพะถะตั‚ะต ะดะฐ ะดะพะฟั€ะธะฝะตัะตั‚ะต ะดะธั€ะตะบั‚ะฝะพ ะบัŠะผ ะฝะตั. +pulls.allow_edits_from_maintainers_desc = ะŸะพั‚ั€ะตะฑะธั‚ะตะปะธ ั ะฟั€ะฐะฒะพ ะฝะฐ ะทะฐะฟะธั ะฒ ะพัะฝะพะฒะฝะธั ะบะปะพะฝ ะผะพะณะฐั‚ ััŠั‰ะพ ะดะฐ ะธะทั‚ะปะฐัะบะฒะฐั‚ ะบัŠะผ ั‚ะพะทะธ ะบะปะพะฝ +pulls.merge_conflict = ะกะปะธะฒะฐะฝะตั‚ะพ ะต ะฝะตัƒัะฟะตัˆะฝะพ: ะ’ัŠะทะฝะธะบะฝะฐ ะบะพะฝั„ะปะธะบั‚ ะฟะพ ะฒั€ะตะผะต ะฝะฐ ัะปะธะฒะฐะฝะตั‚ะพ. ะŸะพะดัะบะฐะทะบะฐ: ะžะฟะธั‚ะฐะนั‚ะต ั€ะฐะทะปะธั‡ะฝะฐ ัั‚ั€ะฐั‚ะตะณะธั +pulls.has_merged = ะะตัƒัะฟะตัˆะฝะพ: ะ—ะฐัะฒะบะฐั‚ะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะต ัะปัั‚ะฐ, ะฝะต ะผะพะถะตั‚ะต ะดะฐ ัะปะตะตั‚ะต ะพั‚ะฝะพะฒะพ ะธะปะธ ะดะฐ ะฟั€ะพะผะตะฝะธั‚ะต ั†ะตะปะตะฒะธั ะบะปะพะฝ. +pulls.cmd_instruction_merge_warning = ะŸั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต: ะะฐัั‚ั€ะพะนะบะฐั‚ะฐ โ€žะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะพั‚ะบั€ะธะฒะฐะฝะต ะฝะฐ ั€ัŠั‡ะฝะพ ัะปะธะฒะฐะฝะตโ€œ ะฝะต ะต ะฒะบะปัŽั‡ะตะฝะฐ ะทะฐ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต, ั‰ะต ั‚ั€ัะฑะฒะฐ ะดะฐ ะพั‚ะฑะตะปะตะถะธั‚ะต ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะบะฐั‚ะพ ั€ัŠั‡ะฝะพ ัะปัั‚ะฐ ัะปะตะด ั‚ะพะฒะฐ. pulls.delete.title = ะ”ะฐ ัะต ะธะทั‚ั€ะธะต ะปะธ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต? +pulls.push_rejected_no_message = ะ˜ะทั‚ะปะฐัะบะฒะฐะฝะตั‚ะพ ะต ะฝะตัƒัะฟะตัˆะฝะพ: ะ˜ะทั‚ะปะฐัะบะฒะฐะฝะตั‚ะพ ะต ะพั‚ั…ะฒัŠั€ะปะตะฝะพ, ะฝะพ ะฝัะผะฐ ะพั‚ะดะฐะปะตั‡ะตะฝะพ ััŠะพะฑั‰ะตะฝะธะต. ะŸั€ะตะณะปะตะดะฐะนั‚ะต Git ะบัƒะบะธั‚ะต ะทะฐ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต +pulls.auto_merge_newly_scheduled = ะ—ะฐัะฒะบะฐั‚ะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะต ะฝะฐัั€ะพั‡ะตะฝะฐ ะทะฐ ัะปะธะฒะฐะฝะต, ะบะพะณะฐั‚ะพ ะฒัะธั‡ะบะธ ะฟั€ะพะฒะตั€ะบะธ ัะฐ ัƒัะฟะตัˆะฝะธ. pulls.delete.text = ะะฐะธัั‚ะธะฝะฐ ะปะธ ะธัะบะฐั‚ะต ะดะฐ ะธะทั‚ั€ะธะตั‚ะต ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต? (ะขะพะฒะฐ ั‰ะต ะฟั€ะตะผะฐั…ะฝะต ั‚ั€ะฐะนะฝะพ ั†ัะปะพั‚ะพ ััŠะดัŠั€ะถะฐะฝะธะต. ะŸะพะผะธัะปะตั‚ะต ะดะฐะปะธ ะฒะผะตัั‚ะพ ั‚ะพะฒะฐ ะดะฐ ะฝะต ั ะทะฐั‚ะฒะพั€ะธั‚ะต, ะฐะบะพ ะฒัŠะทะฝะฐะผะตั€ัะฒะฐั‚ะต ะดะฐ ั ะทะฐะฟะฐะทะธั‚ะต ะฐั€ั…ะธะฒะธั€ะฐะฝะฐ) -diff.data_not_available = ะกัŠะดัŠั€ะถะฐะฝะธะตั‚ะพ ะฝะฐ ั€ะฐะทะปะธะบะธั‚ะต ะฝะต ะต ะฝะฐะปะธั‡ะฝะพ +pulls.auto_merge_has_pending_schedule = %[1]s ะฝะฐัั€ะพั‡ะธ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต ะทะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ัะปะธะฒะฐะฝะต, ะบะพะณะฐั‚ะพ ะฒัะธั‡ะบะธ ะฟั€ะพะฒะตั€ะบะธ ัะฐ ัƒัะฟะตัˆะฝะธ %[2]s. +pulls.auto_merge_when_succeed = ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ัะปะธะฒะฐะฝะต, ะบะพะณะฐั‚ะพ ะฒัะธั‡ะบะธ ะฟั€ะพะฒะตั€ะบะธ ัะฐ ัƒัะฟะตัˆะฝะธ +error.csv.invalid_field_count = ะะต ะผะพะถะต ะดะฐ ัะต ะฒะธะทัƒะฐะปะธะทะธั€ะฐ ั‚ะพะทะธ ั„ะฐะนะป, ะทะฐั‰ะพั‚ะพ ะธะผะฐ ะณั€ะตัˆะตะฝ ะฑั€ะพะน ะฟะพะปะตั‚ะฐ ะฝะฐ ั€ะตะด %d. diff.bin = ะ”ะ’ะžะ˜ะงะ•ะ -diff.file_suppressed_line_too_long = ะ ะฐะทะปะธะบะธั‚ะต ะฒัŠะฒ ั„ะฐะนะปะฐ ัะฐ ะฟะพั‚ะธัะฝะฐั‚ะธ, ะทะฐั‰ะพั‚ะพ ะตะดะธะฝ ะธะปะธ ะฟะพะฒะตั‡ะต ั€ะตะดะพะฒะต ัะฐ ั‚ะฒัŠั€ะดะต ะดัŠะปะณะธ -diff.too_many_files = ะัะบะพะธ ั„ะฐะนะปะพะฒะต ะฝะต ะฑัั…ะฐ ะฟะพะบะฐะทะฐะฝะธ, ะทะฐั‰ะพั‚ะพ ั‚ะฒัŠั€ะดะต ะผะฝะพะณะพ ั„ะฐะนะปะพะฒะต ะธะผะฐั‚ ะฟั€ะพะผะตะฝะธ ะฒ ั‚ะตะทะธ ั€ะฐะทะปะธะบะธ -diff.show_more = ะŸะพะบะฐะทะฒะฐะฝะต ะฝะฐ ะพั‰ะต +release.add_tag_msg = ะ˜ะทะฟะพะปะทะฒะฐะฝะต ะฝะฐ ะทะฐะณะปะฐะฒะธะตั‚ะพ ะธ ััŠะดัŠั€ะถะฐะฝะธะตั‚ะพ ะฝะฐ ะธะทะดะฐะฝะธะตั‚ะพ ะบะฐั‚ะพ ััŠะพะฑั‰ะตะฝะธะต ะฝะฐ ะผะฐั€ะบะตั€ะฐ. +release.hide_archive_links_helper = ะกะบั€ะธะนั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะณะตะฝะตั€ะธั€ะฐะฝะธั‚ะต ะฐั€ั…ะธะฒะธ ั ะฟั€ะพะณั€ะฐะผะตะฝ ะบะพะด ะทะฐ ั‚ะพะฒะฐ ะธะทะดะฐะฝะธะต. ะะฐะฟั€ะธะผะตั€, ะฐะบะพ ะบะฐั‡ะฒะฐั‚ะต ัะฒะพะธ ัะพะฑัั‚ะฒะตะฝะธ. +diff.data_not_available = ะกัŠะดัŠั€ะถะฐะฝะธะตั‚ะพ ะฝะฐ ั€ะฐะทะปะธะบะธั‚ะต ะฝะต ะต ะฝะฐะปะธั‡ะฝะพ +diff.has_escaped = ะขะพะทะธ ั€ะตะด ะธะผะฐ ัะบั€ะธั‚ะธ ะฃะฝะธะบะพะด ะทะฝะฐั†ะธ +branch.protected_deletion_failed = ะšะปะพะฝัŠั‚ โ€ž%sโ€œ ะต ะทะฐั‰ะธั‚ะตะฝ. ะะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ะธะทั‚ั€ะธั‚. +branch.default_deletion_failed = ะšะปะพะฝัŠั‚ โ€ž%sโ€œ ะต ัั‚ะฐะฝะดะฐั€ั‚ะฝะธัั‚ ะบะปะพะฝ. ะะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ะธะทั‚ั€ะธั‚. diff.generated = ะณะตะฝะตั€ะธั€ะฐะฝ diff.comment.add_line_comment = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะบะพะผะตะฝั‚ะฐั€ ะบัŠะผ ั€ะตะด diff.comment.add_review_comment = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะบะพะผะตะฝั‚ะฐั€ -diff.review.self_reject = ะะฒั‚ะพั€ะธั‚ะต ะฝะฐ ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต ะฝะต ะผะพะณะฐั‚ ะดะฐ ะฟะพะธัะบะฒะฐั‚ ะฟั€ะพะผะตะฝะธ ะฒ ัะพะฑัั‚ะฒะตะฝะธั‚ะต ัะธ ะทะฐัะฒะบะธ diff.review.self_approve = ะะฒั‚ะพั€ะธั‚ะต ะฝะฐ ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต ะฝะต ะผะพะณะฐั‚ ะดะฐ ะพะดะพะฑั€ัะฒะฐั‚ ัะพะฑัั‚ะฒะตะฝะธั‚ะต ัะธ ะทะฐัะฒะบะธ -diff.image.side_by_side = ะ•ะดะฝะพ ะดะพ ะดั€ัƒะณะพ -diff.image.swipe = ะŸะปัŠะทะณะฐะฝะต -diff.image.overlay = ะะฐัะปะฐะณะฒะฐะฝะต -diff.has_escaped = ะขะพะทะธ ั€ะตะด ะธะผะฐ ัะบั€ะธั‚ะธ ะฃะฝะธะบะพะด ะทะฝะฐั†ะธ release.tag_name_protected = ะ˜ะผะตั‚ะพ ะฝะฐ ะผะฐั€ะบะตั€ะฐ ะต ะทะฐั‰ะธั‚ะตะฝะพ. -release.add_tag_msg = ะ˜ะทะฟะพะปะทะฒะฐะฝะต ะฝะฐ ะทะฐะณะปะฐะฒะธะตั‚ะพ ะธ ััŠะดัŠั€ะถะฐะฝะธะตั‚ะพ ะฝะฐ ะธะทะดะฐะฝะธะตั‚ะพ ะบะฐั‚ะพ ััŠะพะฑั‰ะตะฝะธะต ะฝะฐ ะผะฐั€ะบะตั€ะฐ. -release.hide_archive_links = ะกะบั€ะธะฒะฐะฝะต ะฝะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะณะตะฝะตั€ะธั€ะฐะฝะธั‚ะต ะฐั€ั…ะธะฒะธ -release.hide_archive_links_helper = ะกะบั€ะธะนั‚ะต ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะณะตะฝะตั€ะธั€ะฐะฝะธั‚ะต ะฐั€ั…ะธะฒะธ ั ะฟั€ะพะณั€ะฐะผะตะฝ ะบะพะด ะทะฐ ั‚ะพะฒะฐ ะธะทะดะฐะฝะธะต. ะะฐะฟั€ะธะผะตั€, ะฐะบะพ ะบะฐั‡ะฒะฐั‚ะต ัะฒะพะธ ัะพะฑัั‚ะฒะตะฝะธ. -release.asset_external_url = ะ’ัŠะฝัˆะตะฝ URL ะฐะดั€ะตั -release.summary_card_alt = ะšะฐั€ั‚ะฐ ั ะพะฑะพะฑั‰ะตะฝะธะต ะฝะฐ ะธะทะดะฐะฝะธะต ััŠั ะทะฐะณะปะฐะฒะธะต โ€ž%sโ€œ ะฒ ั…ั€ะฐะฝะธะปะธั‰ะต %s -branch.protected_deletion_failed = ะšะปะพะฝัŠั‚ โ€ž%sโ€œ ะต ะทะฐั‰ะธั‚ะตะฝ. ะะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ะธะทั‚ั€ะธั‚. -branch.default_deletion_failed = ะšะปะพะฝัŠั‚ โ€ž%sโ€œ ะต ัั‚ะฐะฝะดะฐั€ั‚ะฝะธัั‚ ะบะปะพะฝ. ะะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ะธะทั‚ั€ะธั‚. -branch.included_desc = ะขะพะทะธ ะบะปะพะฝ ะต ั‡ะฐัั‚ ะพั‚ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธั ะบะปะพะฝ -branch.included = ะ’ะบะปัŽั‡ะตะฝ branch.warning_rename_default_branch = ะŸั€ะตะธะผะตะฝัƒะฒะฐั‚ะต ัั‚ะฐะฝะดะฐั€ั‚ะฝะธั ะบะปะพะฝ. -topic.count_prompt = ะะต ะผะพะถะตั‚ะต ะดะฐ ะธะทะฑะตั€ะตั‚ะต ะฟะพะฒะตั‡ะต ะพั‚ 25 ั‚ะตะผะธ find_file.no_matching = ะะต ะต ะฝะฐะผะตั€ะตะฝ ััŠะฒะฟะฐะดะฐั‰ ั„ะฐะนะป -error.csv.too_large = ะะต ะผะพะถะต ะดะฐ ัะต ะฒะธะทัƒะฐะปะธะทะธั€ะฐ ั‚ะพะทะธ ั„ะฐะนะป, ะทะฐั‰ะพั‚ะพ ะต ั‚ะฒัŠั€ะดะต ะณะพะปัะผ. +issues.role.member_helper = ะขะพะทะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะป ะต ัƒั‡ะฐัั‚ะฝะธะบ ะฒ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ, ะฟั€ะธั‚ะตะถะฐะฒะฐั‰ะฐ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต. +diff.image.overlay = ะะฐัะปะฐะณะฒะฐะฝะต +diff.image.swipe = ะŸะปัŠะทะณะฐะฝะต +branch.included = ะ’ะบะปัŽั‡ะตะฝ +diff.file_suppressed_line_too_long = ะ ะฐะทะปะธะบะธั‚ะต ะฒัŠะฒ ั„ะฐะนะปะฐ ัะฐ ะฟะพั‚ะธัะฝะฐั‚ะธ, ะทะฐั‰ะพั‚ะพ ะตะดะธะฝ ะธะปะธ ะฟะพะฒะตั‡ะต ั€ะตะดะพะฒะต ัะฐ ั‚ะฒัŠั€ะดะต ะดัŠะปะณะธ error.csv.unexpected = ะะต ะผะพะถะต ะดะฐ ัะต ะฒะธะทัƒะฐะปะธะทะธั€ะฐ ั‚ะพะทะธ ั„ะฐะนะป, ะทะฐั‰ะพั‚ะพ ััŠะดัŠั€ะถะฐ ะฝะตะพั‡ะฐะบะฒะฐะฝ ะทะฝะฐะบ ะฝะฐ ั€ะตะด %d ะธ ะบะพะปะพะฝะฐ %d. -error.csv.invalid_field_count = ะะต ะผะพะถะต ะดะฐ ัะต ะฒะธะทัƒะฐะปะธะทะธั€ะฐ ั‚ะพะทะธ ั„ะฐะนะป, ะทะฐั‰ะพั‚ะพ ะธะผะฐ ะณั€ะตัˆะตะฝ ะฑั€ะพะน ะฟะพะปะตั‚ะฐ ะฝะฐ ั€ะตะด %d. -error.broken_git_hook = Git ะบัƒะบะธั‚ะต ะฝะฐ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต ะธะทะณะปะตะถะดะฐั‚ ะฟะพะฒั€ะตะดะตะฝะธ. ะœะพะปั, ะฟะพัะปะตะดะฒะฐะนั‚ะต ะดะพะบัƒะผะตะฝั‚ะฐั†ะธัั‚ะฐ, ะทะฐ ะดะฐ ะณะธ ะฟะพะฟั€ะฐะฒะธั‚ะต, ัะปะตะด ะบะพะตั‚ะพ ะธะทั‚ะปะฐัะบะฐะนั‚ะต ะฟะพะดะฐะฒะฐะฝะธั, ะทะฐ ะดะฐ ะพะฑะฝะพะฒะธั‚ะต ัั‚ะฐั‚ัƒัะฐ. +topic.count_prompt = ะะต ะผะพะถะตั‚ะต ะดะฐ ะธะทะฑะตั€ะตั‚ะต ะฟะพะฒะตั‡ะต ะพั‚ 25 ั‚ะตะผะธ +release.hide_archive_links = ะกะบั€ะธะฒะฐะฝะต ะฝะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะณะตะฝะตั€ะธั€ะฐะฝะธั‚ะต ะฐั€ั…ะธะฒะธ +diff.show_more = ะŸะพะบะฐะทะฒะฐะฝะต ะฝะฐ ะพั‰ะต +diff.too_many_files = ะัะบะพะธ ั„ะฐะนะปะพะฒะต ะฝะต ะฑัั…ะฐ ะฟะพะบะฐะทะฐะฝะธ, ะทะฐั‰ะพั‚ะพ ั‚ะฒัŠั€ะดะต ะผะฝะพะณะพ ั„ะฐะนะปะพะฒะต ะธะผะฐั‚ ะฟั€ะพะผะตะฝะธ ะฒ ั‚ะตะทะธ ั€ะฐะทะปะธะบะธ +diff.review.self_reject = ะะฒั‚ะพั€ะธั‚ะต ะฝะฐ ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต ะฝะต ะผะพะณะฐั‚ ะดะฐ ะฟะพะธัะบะฒะฐั‚ ะฟั€ะพะผะตะฝะธ ะฒ ัะพะฑัั‚ะฒะตะฝะธั‚ะต ัะธ ะทะฐัะฒะบะธ +branch.included_desc = ะขะพะทะธ ะบะปะพะฝ ะต ั‡ะฐัั‚ ะพั‚ ัั‚ะฐะฝะดะฐั€ั‚ะฝะธั ะบะปะพะฝ +diff.image.side_by_side = ะ•ะดะฝะพ ะดะพ ะดั€ัƒะณะพ +release.summary_card_alt = ะšะฐั€ั‚ะฐ ั ะพะฑะพะฑั‰ะตะฝะธะต ะฝะฐ ะธะทะดะฐะฝะธะต ััŠั ะทะฐะณะปะฐะฒะธะต โ€ž%sโ€œ ะฒ ั…ั€ะฐะฝะธะปะธั‰ะต %s +release.asset_external_url = ะ’ัŠะฝัˆะตะฝ URL ะฐะดั€ะตั +error.csv.too_large = ะะต ะผะพะถะต ะดะฐ ัะต ะฒะธะทัƒะฐะปะธะทะธั€ะฐ ั‚ะพะทะธ ั„ะฐะนะป, ะทะฐั‰ะพั‚ะพ ะต ั‚ะฒัŠั€ะดะต ะณะพะปัะผ. [modal] confirm = ะŸะพั‚ะฒัŠั€ะถะดะฐะฒะฐะฝะต @@ -1880,7 +1825,7 @@ yes = ะ”ะฐ [editor] buttons.list.ordered.tooltip = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะฝะพะผะตั€ะธั€ะฐะฝ ัะฟะธััŠะบ -buttons.bold.tooltip = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ัƒะดะตะฑะตะปะตะฝ ั‚ะตะบัั‚ +buttons.bold.tooltip = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ัƒะดะตะฑะตะปะตะฝ ั‚ะตะบัั‚ (Ctrl+B / โŒ˜B) buttons.quote.tooltip = ะฆะธั‚ะธั€ะฐะฝ ั‚ะตะบัั‚ buttons.code.tooltip = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะบะพะด buttons.list.unordered.tooltip = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะฝะตะฝะพะผะตั€ะธั€ะฐะฝ ัะฟะธััŠะบ @@ -1889,7 +1834,7 @@ buttons.switch_to_legacy.tooltip = ะ˜ะทะฟะพะปะทะฒะฐะฝะต ะฝะฐ ัั‚ะฐั€ะธั ั€ะตะด buttons.list.task.tooltip = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ัะฟะธััŠะบ ััŠั ะทะฐะดะฐั‡ะธ buttons.enable_monospace_font = ะ’ะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ั€ะฐะฒะฝะพัˆะธั€ะพะบ ัˆั€ะธั„ั‚ buttons.mention.tooltip = ะกะฟะพะผะตะฝะฐะฒะฐะฝะต ะฝะฐ ะฟะพั‚ั€ะตะฑะธั‚ะตะป ะธะปะธ ะตะบะธะฟ -buttons.italic.tooltip = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะบัƒั€ัะธะฒ ั‚ะตะบัั‚ +buttons.italic.tooltip = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะบัƒั€ัะธะฒ ั‚ะตะบัั‚ (Ctrl+I / โŒ˜I) buttons.link.tooltip = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะฒั€ัŠะทะบะฐ buttons.disable_monospace_font = ะ˜ะทะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ั€ะฐะฒะฝะพัˆะธั€ะพะบะธั ัˆั€ะธั„ั‚ buttons.ref.tooltip = ะŸั€ะตะฟั€ะฐั‚ะบะฐ ะบัŠะผ ะทะฐะดะฐั‡ะฐ ะธะปะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต @@ -1961,10 +1906,8 @@ teams.none_access = ะ‘ะตะท ะดะพัั‚ัŠะฟ teams.members.none = ะัะผะฐ ัƒั‡ะฐัั‚ะฝะธั†ะธ ะฒ ั‚ะพะทะธ ะตะบะธะฟ. repo_updated = ะžะฑะฝะพะฒะตะฝะพ %s teams.delete_team_success = ะ•ะบะธะฟัŠั‚ ะต ะธะทั‚ั€ะธั‚. -teams.search_repo_placeholder = ะŸะพั‚ัŠั€ัะตั‚ะต ั…ั€ะฐะฝะธะปะธั‰ะตโ€ฆ teams.delete_team_title = ะ˜ะทั‚ั€ะธะฒะฐะฝะต ะฝะฐ ะตะบะธะฟะฐ teams.add_team_member = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ัƒั‡ะฐัั‚ะฝะธะบ ะฒ ะตะบะธะฟะฐ -teams.read_access_helper = ะงะปะตะฝะพะฒะตั‚ะต ะผะพะณะฐั‚ ะดะฐ ะฟั€ะตะณะปะตะถะดะฐั‚ ะธ ะบะปะพะฝะธั€ะฐั‚ ั…ั€ะฐะฝะธะปะธั‰ะฐั‚ะฐ ะฝะฐ ะตะบะธะฟะฐ. teams.invite.description = ะœะพะปั, ั‰ั€ะฐะบะฝะตั‚ะต ะฒัŠั€ั…ัƒ ะฑัƒั‚ะพะฝะฐ ะฟะพ-ะดะพะปัƒ, ะทะฐ ะดะฐ ัะต ะฟั€ะธััŠะตะดะธะฝะธั‚ะต ะบัŠะผ ะตะบะธะฟะฐ. teams.invite.title = ะŸะพะบะฐะฝะตะฝะธ ัั‚ะต ะดะฐ ัะต ะฟั€ะธััŠะตะดะธะฝะธั‚ะต ะบัŠะผ ะตะบะธะฟ %s ะฒ ะพั€ะณะฐะฝะธะทะฐั†ะธั %s. team_permission_desc = ะ ะฐะทั€ะตัˆะตะฝะธะต @@ -1979,37 +1922,36 @@ teams.no_desc = ะขะพะทะธ ะตะบะธะฟ ะฝัะผะฐ ะพะฟะธัะฐะฝะธะต settings.delete_org_desc = ะขะฐะทะธ ะพั€ะณะฐะฝะธะทะฐั†ะธั ั‰ะต ะฑัŠะดะต ะธะทั‚ั€ะธั‚ะฐ ะฟะตั€ะผะฐะฝะตะฝั‚ะฝะพ. ะŸั€ะพะดัŠะปะถะฐะฒะฐะฝะต? open_dashboard = ะžั‚ะฒะฐั€ัะฝะต ะฝะฐ ั‚ะฐะฑะปะพั‚ะพ settings.change_orgname_prompt = ะ‘ะตะปะตะถะบะฐ: ะŸั€ะพะผัะฝะฐั‚ะฐ ะฝะฐ ะธะผะตั‚ะพ ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ ั‰ะต ะฟั€ะพะผะตะฝะธ ะธ URL ะฐะดั€ะตัะฐ ะฝะฐ ะฒะฐัˆะฐั‚ะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธั ะธ ั‰ะต ะพัะฒะพะฑะพะดะธ ัั‚ะฐั€ะพั‚ะพ ะธะผะต. - -team_access_desc = ะ”ะพัั‚ัŠะฟ ะดะพ ั…ั€ะฐะฝะธะปะธั‰ะต -team_unit_desc = ะ ะฐะทั€ะตัˆะฐะฒะฐะฝะต ะฝะฐ ะดะพัั‚ัŠะฟ ะดะพ ัะตะบั†ะธะธ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ +teams.add_duplicate_users = ะŸะพั‚ั€ะตะฑะธั‚ะตะปัั‚ ะฒะตั‡ะต ะต ัƒั‡ะฐัั‚ะฝะธะบ ะฒ ะตะบะธะฟะฐ. team_unit_disabled = (ะ˜ะทะบะปัŽั‡ะตะฝะพ) form.name_reserved = ะ˜ะผะตั‚ะพ ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ โ€ž%sโ€œ ะต ั€ะตะทะตั€ะฒะธั€ะฐะฝะพ. -form.name_pattern_not_allowed = ะจะฐะฑะปะพะฝัŠั‚ โ€ž%sโ€œ ะฝะต ะต ั€ะฐะทั€ะตัˆะตะฝ ะฒ ะธะผะต ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธั. -form.create_org_not_allowed = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะดะฐ ััŠะทะดะฐะฒะฐั‚ะต ะพั€ะณะฐะฝะธะทะฐั†ะธั. -settings.update_setting_success = ะะฐัั‚ั€ะพะนะบะธั‚ะต ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ ัะฐ ะพะฑะฝะพะฒะตะฝะธ. -settings.change_orgname_redirect_prompt = ะกั‚ะฐั€ะพั‚ะพ ะธะผะต ั‰ะต ัะต ะฟั€ะตะฝะฐัะพั‡ะฒะฐ, ะดะพะบะฐั‚ะพ ะฝะต ะฑัŠะดะต ะฒะทะตั‚ะพ. -settings.change_orgname_redirect_prompt.with_cooldown.one = ะกั‚ะฐั€ะพั‚ะพ ะธะผะต ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ ั‰ะต ะฑัŠะดะต ะดะพัั‚ัŠะฟะฝะพ ะทะฐ ะฒัะธั‡ะบะธ ัะปะตะด ะฟะตั€ะธะพะด ะฝะฐ ะธะทั‡ะฐะบะฒะฐะฝะต ะพั‚ %[1]d ะดะตะฝ. ะ’ัะต ะพั‰ะต ะผะพะถะตั‚ะต ะดะฐ ัะธ ะฒัŠั€ะฝะตั‚ะต ัั‚ะฐั€ะพั‚ะพ ะธะผะต ะฟะพ ะฒั€ะตะผะต ะฝะฐ ะฟะตั€ะธะพะดะฐ ะฝะฐ ะธะทั‡ะฐะบะฒะฐะฝะต. -settings.change_orgname_redirect_prompt.with_cooldown.few = ะกั‚ะฐั€ะพั‚ะพ ะธะผะต ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ ั‰ะต ะฑัŠะดะต ะดะพัั‚ัŠะฟะฝะพ ะทะฐ ะฒัะธั‡ะบะธ ัะปะตะด ะฟะตั€ะธะพะด ะฝะฐ ะธะทั‡ะฐะบะฒะฐะฝะต ะพั‚ %[1]d ะดะฝะธ. ะ’ัะต ะพั‰ะต ะผะพะถะตั‚ะต ะดะฐ ัะธ ะฒัŠั€ะฝะตั‚ะต ัั‚ะฐั€ะพั‚ะพ ะธะผะต ะฟะพ ะฒั€ะตะผะต ะฝะฐ ะฟะตั€ะธะพะดะฐ ะฝะฐ ะธะทั‡ะฐะบะฒะฐะฝะต. settings.update_avatar_success = ะŸั€ะพั„ะธะปะฝะฐั‚ะฐ ัะฝะธะผะบะฐ ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ ะต ะพะฑะฝะพะฒะตะฝะฐ. +teams.invite_team_member.list = ะงะฐะบะฐั‰ะธ ะฟะพะบะฐะฝะธ +teams.remove_all_repos_desc = ะขะพะฒะฐ ั‰ะต ะฟั€ะตะผะฐั…ะฝะต ะฒัะธั‡ะบะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ ะพั‚ ะตะบะธะฟะฐ. +form.create_org_not_allowed = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะดะฐ ััŠะทะดะฐะฒะฐั‚ะต ะพั€ะณะฐะฝะธะทะฐั†ะธั. +form.name_pattern_not_allowed = ะจะฐะฑะปะพะฝัŠั‚ โ€ž%sโ€œ ะฝะต ะต ั€ะฐะทั€ะตัˆะตะฝ ะฒ ะธะผะต ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธั. +members.invite_now = ะŸะพะบะฐะฝะฒะฐะฝะต ัะตะณะฐ +teams.specific_repositories = ะšะพะฝะบั€ะตั‚ะฝะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ +teams.repos.none = ะัะผะฐ ั…ั€ะฐะฝะธะปะธั‰ะฐ, ะดะพ ะบะพะธั‚ะพ ั‚ะพะทะธ ะตะบะธะฟ ะดะฐ ะธะผะฐ ะดะพัั‚ัŠะฟ. +teams.add_all_repos_title = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะฒัะธั‡ะบะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ settings.hooks_desc = ะ”ะพะฑะฐะฒะตั‚ะต ัƒะตะฑ-ะบัƒะบะธ, ะบะพะธั‚ะพ ั‰ะต ัะต ะทะฐะดะตะนัั‚ะฒะฐั‚ ะทะฐ ะฒัะธั‡ะบะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ ะฒ ั‚ะฐะทะธ ะพั€ะณะฐะฝะธะทะฐั†ะธั. +teams.add_all_repos_desc = ะขะพะฒะฐ ั‰ะต ะดะพะฑะฐะฒะธ ะฒัะธั‡ะบะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ ะบัŠะผ ะตะบะธะฟะฐ. +members.invite_desc = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะฝะพะฒ ัƒั‡ะฐัั‚ะฝะธะบ ะบัŠะผ %s: +members.private = ะกะบั€ะธั‚ +settings.change_orgname_redirect_prompt.with_cooldown.few = ะกั‚ะฐั€ะพั‚ะพ ะธะผะต ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ ั‰ะต ะฑัŠะดะต ะดะพัั‚ัŠะฟะฝะพ ะทะฐ ะฒัะธั‡ะบะธ ัะปะตะด ะฟะตั€ะธะพะด ะฝะฐ ะธะทั‡ะฐะบะฒะฐะฝะต ะพั‚ %[1]d ะดะฝะธ. ะ’ัะต ะพั‰ะต ะผะพะถะตั‚ะต ะดะฐ ัะธ ะฒัŠั€ะฝะตั‚ะต ัั‚ะฐั€ะพั‚ะพ ะธะผะต ะฟะพ ะฒั€ะตะผะต ะฝะฐ ะฟะตั€ะธะพะดะฐ ะฝะฐ ะธะทั‡ะฐะบะฒะฐะฝะต. +team_access_desc = ะ”ะพัั‚ัŠะฟ ะดะพ ั…ั€ะฐะฝะธะปะธั‰ะต +teams.specific_repositories_helper = ะฃั‡ะฐัั‚ะฝะธั†ะธั‚ะต ั‰ะต ะธะผะฐั‚ ะดะพัั‚ัŠะฟ ัะฐะผะพ ะดะพ ั…ั€ะฐะฝะธะปะธั‰ะฐ, ะธะทั€ะธั‡ะฝะพ ะดะพะฑะฐะฒะตะฝะธ ะบัŠะผ ะตะบะธะฟะฐ. ะ˜ะทะฑะธั€ะฐะฝะตั‚ะพ ะฝะฐ ั‚ะพะฒะฐ ะฝัะผะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะดะฐ ะฟั€ะตะผะฐั…ะฝะต ั…ั€ะฐะฝะธะปะธั‰ะฐ, ะฒะตั‡ะต ะดะพะฑะฐะฒะตะฝะธ ั ะ’ัะธั‡ะบะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ. +teams.delete_team_desc = ะ˜ะทั‚ั€ะธะฒะฐะฝะตั‚ะพ ะฝะฐ ะตะบะธะฟ ะพั‚ะฝะตะผะฐ ะดะพัั‚ัŠะฟะฐ ะดะพ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ะพั‚ ะฝะตะณะพะฒะธั‚ะต ัƒั‡ะฐัั‚ะฝะธั†ะธ. ะŸั€ะพะดัŠะปะถะฐะฒะฐะฝะต? members.membership_visibility = ะ’ะธะดะธะผะพัั‚ ะฝะฐ ัƒั‡ะฐัั‚ะฝะธั‡ะตัั‚ะฒะพั‚ะพ: members.public = ะ’ะธะดะธะผ -members.private = ะกะบั€ะธั‚ -members.invite_desc = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะฝะพะฒ ัƒั‡ะฐัั‚ะฝะธะบ ะบัŠะผ %s: -members.invite_now = ะŸะพะบะฐะฝะฒะฐะฝะต ัะตะณะฐ -teams.admin_access = ะะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ัะบะธ ะดะพัั‚ัŠะฟ -teams.invite_team_member = ะŸะพะบะฐะฝะฒะฐะฝะต ะฒ %s -teams.invite_team_member.list = ะงะฐะบะฐั‰ะธ ะฟะพะบะฐะฝะธ -teams.delete_team_desc = ะ˜ะทั‚ั€ะธะฒะฐะฝะตั‚ะพ ะฝะฐ ะตะบะธะฟ ะพั‚ะฝะตะผะฐ ะดะพัั‚ัŠะฟะฐ ะดะพ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ะพั‚ ะฝะตะณะพะฒะธั‚ะต ัƒั‡ะฐัั‚ะฝะธั†ะธ. ะŸั€ะพะดัŠะปะถะฐะฒะฐะฝะต? -teams.remove_all_repos_desc = ะขะพะฒะฐ ั‰ะต ะฟั€ะตะผะฐั…ะฝะต ะฒัะธั‡ะบะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ ะพั‚ ะตะบะธะฟะฐ. -teams.add_all_repos_title = ะ”ะพะฑะฐะฒัะฝะต ะฝะฐ ะฒัะธั‡ะบะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ -teams.add_all_repos_desc = ะขะพะฒะฐ ั‰ะต ะดะพะฑะฐะฒะธ ะฒัะธั‡ะบะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ ะบัŠะผ ะตะบะธะฟะฐ. -teams.add_nonexistent_repo = ะฅั€ะฐะฝะธะปะธั‰ะตั‚ะพ, ะบะพะตั‚ะพ ัะต ะพะฟะธั‚ะฒะฐั‚ะต ะดะฐ ะดะพะฑะฐะฒะธั‚ะต, ะฝะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ, ะผะพะปั, ะฟัŠั€ะฒะพ ะณะพ ััŠะทะดะฐะนั‚ะต. -teams.add_duplicate_users = ะŸะพั‚ั€ะตะฑะธั‚ะตะปัั‚ ะฒะตั‡ะต ะต ัƒั‡ะฐัั‚ะฝะธะบ ะฒ ะตะบะธะฟะฐ. -teams.repos.none = ะัะผะฐ ั…ั€ะฐะฝะธะปะธั‰ะฐ, ะดะพ ะบะพะธั‚ะพ ั‚ะพะทะธ ะตะบะธะฟ ะดะฐ ะธะผะฐ ะดะพัั‚ัŠะฟ. -teams.specific_repositories = ะšะพะฝะบั€ะตั‚ะฝะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ -teams.specific_repositories_helper = ะฃั‡ะฐัั‚ะฝะธั†ะธั‚ะต ั‰ะต ะธะผะฐั‚ ะดะพัั‚ัŠะฟ ัะฐะผะพ ะดะพ ั…ั€ะฐะฝะธะปะธั‰ะฐ, ะธะทั€ะธั‡ะฝะพ ะดะพะฑะฐะฒะตะฝะธ ะบัŠะผ ะตะบะธะฟะฐ. ะ˜ะทะฑะธั€ะฐะฝะตั‚ะพ ะฝะฐ ั‚ะพะฒะฐ ะฝัะผะฐ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะดะฐ ะฟั€ะตะผะฐั…ะฝะต ั…ั€ะฐะฝะธะปะธั‰ะฐ, ะฒะตั‡ะต ะดะพะฑะฐะฒะตะฝะธ ั ะ’ัะธั‡ะบะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ. teams.all_repositories_helper = ะ•ะบะธะฟัŠั‚ ะธะผะฐ ะดะพัั‚ัŠะฟ ะดะพ ะฒัะธั‡ะบะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ. ะ˜ะทะฑะธั€ะฐะฝะตั‚ะพ ะฝะฐ ั‚ะพะฒะฐ ั‰ะต ะดะพะฑะฐะฒะธ ะฒัะธั‡ะบะธ ััŠั‰ะตัั‚ะฒัƒะฒะฐั‰ะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ ะบัŠะผ ะตะบะธะฟะฐ. +team_unit_desc = ะ ะฐะทั€ะตัˆะฐะฒะฐะฝะต ะฝะฐ ะดะพัั‚ัŠะฟ ะดะพ ัะตะบั†ะธะธ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ +settings.update_setting_success = ะะฐัั‚ั€ะพะนะบะธั‚ะต ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ ัะฐ ะพะฑะฝะพะฒะตะฝะธ. +settings.change_orgname_redirect_prompt = ะกั‚ะฐั€ะพั‚ะพ ะธะผะต ั‰ะต ัะต ะฟั€ะตะฝะฐัะพั‡ะฒะฐ, ะดะพะบะฐั‚ะพ ะฝะต ะฑัŠะดะต ะฒะทะตั‚ะพ. +teams.invite_team_member = ะŸะพะบะฐะฝะฒะฐะฝะต ะฒ %s +teams.admin_access = ะะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ัะบะธ ะดะพัั‚ัŠะฟ +settings.change_orgname_redirect_prompt.with_cooldown.one = ะกั‚ะฐั€ะพั‚ะพ ะธะผะต ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ ั‰ะต ะฑัŠะดะต ะดะพัั‚ัŠะฟะฝะพ ะทะฐ ะฒัะธั‡ะบะธ ัะปะตะด ะฟะตั€ะธะพะด ะฝะฐ ะธะทั‡ะฐะบะฒะฐะฝะต ะพั‚ %[1]d ะดะตะฝ. ะ’ัะต ะพั‰ะต ะผะพะถะตั‚ะต ะดะฐ ัะธ ะฒัŠั€ะฝะตั‚ะต ัั‚ะฐั€ะพั‚ะพ ะธะผะต ะฟะพ ะฒั€ะตะผะต ะฝะฐ ะฟะตั€ะธะพะดะฐ ะฝะฐ ะธะทั‡ะฐะบะฒะฐะฝะต. +teams.add_nonexistent_repo = ะฅั€ะฐะฝะธะปะธั‰ะตั‚ะพ, ะบะพะตั‚ะพ ัะต ะพะฟะธั‚ะฒะฐั‚ะต ะดะฐ ะดะพะฑะฐะฒะธั‚ะต, ะฝะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ, ะผะพะปั, ะฟัŠั€ะฒะพ ะณะพ ััŠะทะดะฐะนั‚ะต. teams.invite.by = ะŸะพะบะฐะฝะตะฝ ะพั‚ %s [install] @@ -2079,7 +2021,6 @@ issue.action.review = @%[1]s ะบะพะผะตะฝั‚ะธั€ะฐ ะฒ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ issue.action.reopen = @%[1]s ะพั‚ะฒะพั€ะธ ะฝะฐะฝะพะฒะพ #%[2]d. issue.action.approve = @%[1]s ะพะดะพะฑั€ะธ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต. issue.action.reject = @%[1]s ะฟะพะธัะบะฐ ะฟั€ะพะผะตะฝะธ ะฒ ั‚ะฐะทะธ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต. -register_notify.title = %[1]s, ะดะพะฑั€ะต ะดะพัˆะปะธ ะฒ %[2]s link_not_working_do_paste = ะะบะพ ะฒั€ัŠะทะบะฐั‚ะฐ ะฝะต ั€ะฐะฑะพั‚ะธ, ะพะฟะธั‚ะฐะนั‚ะต ะดะฐ ั ะบะพะฟะธั€ะฐั‚ะต ะธ ะฟะพัั‚ะฐะฒะธั‚ะต ะฒ URL ะปะตะฝั‚ะฐั‚ะฐ ะฝะฐ ะฒะฐัˆะธั ะฑั€ะฐัƒะทัŠั€. activate_account = ะœะพะปั, ะฐะบั‚ะธะฒะธั€ะฐะนั‚ะต ัะฒะพั ะฐะบะฐัƒะฝั‚ admin.new_user.subject = ะะพะฒ ะฟะพั‚ั€ะตะฑะธั‚ะตะป %s ั‚ะพะบัƒ-ั‰ะพ ัะต ั€ะตะณะธัั‚ั€ะธั€ะฐ @@ -2139,7 +2080,6 @@ block_user = ะ‘ะปะพะบะธั€ะฐะฝะต ะฝะฐ ะฟะพั‚ั€ะตะฑะธั‚ะตะปั change_avatar = ะŸั€ะพะผะตะฝะตั‚ะต ะฟั€ะพั„ะธะปะฝะฐั‚ะฐ ัะธ ัะฝะธะผะบะฐโ€ฆ email_visibility.limited = ะ’ะฐัˆะธัั‚ ะฐะดั€ะตั ะทะฐ ะตะป. ะฟะพั‰ะฐ ะต ะฒะธะดะธะผ ะทะฐ ะฒัะธั‡ะบะธ ัƒะดะพัั‚ะพะฒะตั€ะตะฝะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะปะธ disabled_public_activity = ะขะพะทะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะป ะต ะธะทะบะปัŽั‡ะธะป ะฟัƒะฑะปะธั‡ะฝะฐั‚ะฐ ะฒะธะดะธะผะพัั‚ ะฝะฐ ะดะตะนะฝะพัั‚ั‚ะฐ. -email_visibility.private = ะ’ะฐัˆะธัั‚ ะฐะดั€ะตั ะฝะฐ ะตะป. ะฟะพั‰ะฐ ะต ะฒะธะดะธะผ ัะฐะผะพ ะทะฐ ะฒะฐั ะธ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ะธั‚ะต show_on_map = ะŸะพะบะฐะทะฒะฐะฝะต ะฝะฐ ั‚ะพะฒะฐ ะผััั‚ะพ ะฝะฐ ะบะฐั€ั‚ะฐั‚ะฐ followers_one = %d ะฟะพัะปะตะดะพะฒะฐั‚ะตะป following_one = %d ัะปะตะดะฒะฐะฝ @@ -2164,7 +2104,6 @@ block_user.detail_1 = ะฉะต ัะฟั€ะตั‚ะต ะดะฐ ัะต ัะปะตะดะฒะฐั‚ะต ะตะดะธะฝ ะดั€ [home] filter = ะ”ั€ัƒะณะธ ั„ะธะปั‚ั€ะธ show_archived = ะั€ั…ะธะฒะธั€ะฐะฝะธ -search_repos = ะะฐะผะธั€ะฐะฝะต ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตโ€ฆ my_orgs = ะžั€ะณะฐะฝะธะทะฐั†ะธะธ uname_holder = ะŸะพั‚ั€ะตะฑะธั‚ะตะปัะบะพ ะธะผะต ะธะปะธ ะตะป. ะฟะพั‰ะฐ my_repos = ะฅั€ะฐะฝะธะปะธั‰ะฐ @@ -2174,13 +2113,9 @@ issues.in_your_repos = ะ’ัŠะฒ ะฒะฐัˆะธั‚ะต ั…ั€ะฐะฝะธะปะธั‰ะฐ show_both_private_public = ะŸะพะบะฐะทะฒะฐะฝะต ะฝะฐ ะธ ะฟัƒะฑะปะธั‡ะฝะธ ะธ ั‡ะฐัั‚ะฝะธ show_only_private = ะŸะพะบะฐะทะฒะฐะฝะต ัะฐะผะพ ะฝะฐ ั‡ะฐัั‚ะฝะธ show_private = ะงะฐัั‚ะฝะธ -password_holder = ะŸะฐั€ะพะปะฐ -show_more_repos = ะŸะพะบะฐะทะฒะฐะฝะต ะฝะฐ ะฟะพะฒะตั‡ะต ั…ั€ะฐะฝะธะปะธั‰ะฐโ€ฆ show_only_unarchived = ะŸะพะบะฐะทะฒะฐะฝะต ัะฐะผะพ ะฝะฐ ะฝะตะฐั€ั…ะธะฒะธั€ะฐะฝะธ -my_mirrors = ะœะพะธั‚ะต ะพะณะปะตะดะฐะปะฐ show_only_archived = ะŸะพะบะฐะทะฒะฐะฝะต ัะฐะผะพ ะฝะฐ ะฐั€ั…ะธะฒะธั€ะฐะฝะธ view_home = ะŸั€ะตะณะปะตะด ะฝะฐ %s -collaborative_repos = ะกัŠะฒะผะตัั‚ะฝะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ switch_dashboard_context = ะŸั€ะตะฒะบะปัŽั‡ะฒะฐะฝะต ะฝะฐ ะบะพะฝั‚ะตะบัั‚ะฐ ะฝะฐ ั‚ะฐะฑะปะพั‚ะพ show_only_public = ะŸะพะบะฐะทะฒะฐะฝะต ัะฐะผะพ ะฝะฐ ะฟัƒะฑะปะธั‡ะฝะธ filter_by_team_repositories = ะคะธะปั‚ั€ะธั€ะฐะฝะต ะฟะพ ั…ั€ะฐะฝะธะปะธั‰ะฐ ะฝะฐ ะตะบะธะฟะฐ @@ -2193,8 +2128,6 @@ dashboard = ะขะฐะฑะปะพ repositories = ะฅั€ะฐะฝะธะปะธั‰ะฐ users.name = ะŸะพั‚ั€ะตะฑะธั‚ะตะปัะบะพ ะธะผะต organizations = ะžั€ะณะฐะฝะธะทะฐั†ะธะธ -repos.forks = ะ ะฐะทะบะปะพะฝะตะฝะธั -repos.stars = ะ—ะฒะตะทะดะธ config.mailer_name = ะ˜ะผะต repos.name = ะ˜ะผะต orgs.name = ะ˜ะผะต @@ -2208,7 +2141,6 @@ notices.type_1 = ะฅั€ะฐะฝะธะปะธั‰ะต config.domain = ะ”ะพะผะตะนะฝ ะฝะฐ ััŠั€ะฒัŠั€ะฐ users.max_repo_creation = ะœะฐะบัะธะผะฐะปะตะฝ ะฑั€ะพะน ั…ั€ะฐะฝะธะปะธั‰ะฐ defaulthooks = ะฃะตะฑ-ะบัƒะบะธ ะฟะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต -auths.sspi_default_language = ะŸะพั‚ั€ะตะฑะธั‚ะตะปัะบะธ ะตะทะธะบ ะฟะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต hooks = ะฃะตะฑ-ะบัƒะบะธ systemhooks = ะกะธัั‚ะตะผะฝะธ ัƒะตะฑ-ะบัƒะบะธ orgs.new_orga = ะะพะฒะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธั @@ -2277,14 +2209,12 @@ Email = ะะดั€ะตั ะทะฐ ะตะป. ะฟะพั‰ะฐ Password = ะŸะฐั€ะพะปะฐ RepoName = ะ˜ะผะต ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ username_been_taken = ะŸะพั‚ั€ะตะฑะธั‚ะตะปัะบะพั‚ะพ ะธะผะต ะฒะตั‡ะต ะต ะทะฐะตั‚ะพ. -SSPIDefaultLanguage = ะ•ะทะธะบ ะฟะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต password_not_match = ะŸะฐั€ะพะปะธั‚ะต ะฝะต ััŠะฒะฟะฐะดะฐั‚. captcha_incorrect = CAPTCHA ะบะพะดัŠั‚ ะต ะฝะตะฟั€ะฐะฒะธะปะตะฝ. username_change_not_local_user = ะŸะพั‚ั€ะตะฑะธั‚ะตะปะธ, ะบะพะธั‚ะพ ะฝะต ัะฐ ะปะพะบะฐะปะฝะธ ะฝะต ะผะพะณะฐั‚ ะดะฐ ะฟั€ะพะผะตะฝัั‚ ะฟะพั‚ั€ะตะฑะธั‚ะตะปัะบะพั‚ะพ ัะธ ะธะผะต. username_password_incorrect = ะะตะฟั€ะฐะฒะธะปะฝะพ ะฟะพั‚ั€ะตะฑะธั‚ะตะปัะบะพ ะธะผะต ะธะปะธ ะฟะฐั€ะพะปะฐ. user_not_exist = ะŸะพั‚ั€ะตะฑะธั‚ะตะปัั‚ ะฝะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ. lang_select_error = ะ˜ะทะฑะตั€ะตั‚ะต ะตะทะธะบ ะพั‚ ัะฟะธััŠะบะฐ. -HttpsUrl = HTTPS URL require_error = ` ะฝะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ะฟั€ะฐะทะฝะพ.` Retype = ะŸะพั‚ะฒัŠั€ะดะตั‚ะต ะฟะฐั€ะพะปะฐั‚ะฐ url_error = `โ€ž%sโ€œ ะฝะต ะต ะฒะฐะปะธะดะตะฝ URL.` @@ -2293,7 +2223,6 @@ team_not_exist = ะ•ะบะธะฟัŠั‚ ะฝะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ. TeamName = ะ˜ะผะต ะฝะฐ ะตะบะธะฟะฐ email_error = ` ะฝะต ะต ะฒะฐะปะธะดะตะฝ ะฐะดั€ะตั ะทะฐ ะตะป. ะฟะพั‰ะฐ.` email_invalid = ะะดั€ะตััŠั‚ ะทะฐ ะตะป. ะฟะพั‰ะฐ ะต ะฝะตะฒะฐะปะธะดะตะฝ. -SSHTitle = ะ˜ะผะต ะฝะฐ SSH ะบะปัŽั‡ repo_name_been_taken = ะ˜ะผะตั‚ะพ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ ะฒะตั‡ะต ะต ะธะทะฟะพะปะทะฒะฐะฝะพ. team_name_been_taken = ะ˜ะผะตั‚ะพ ะฝะฐ ะตะบะธะฟะฐ ะฒะตั‡ะต ะต ะทะฐะตั‚ะพ. org_name_been_taken = ะ˜ะผะตั‚ะพ ะฝะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธัั‚ะฐ ะฒะตั‡ะต ะต ะทะฐะตั‚ะพ. @@ -2343,18 +2272,17 @@ TreeName = ะŸัŠั‚ ะดะพ ั„ะฐะนะปะฐ AdminEmail = ะะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ัะบะธ ะฐะดั€ะตั ะทะฐ ะตะป. ะฟะพั‰ะฐ email_domain_is_not_allowed = ะ”ะพะผะตะนะฝัŠั‚ ะฝะฐ ะฐะดั€ะตัะฐ ะทะฐ ะตะป. ะฟะพั‰ะฐ ะฝะฐ ะฟะพั‚ั€ะตะฑะธั‚ะตะปั %s ะต ะฒ ะบะพะฝั„ะปะธะบั‚ ั EMAIL_DOMAIN_ALLOWLIST ะธะปะธ EMAIL_DOMAIN_BLOCKLIST. ะฃะฒะตั€ะตั‚ะต ัะต, ั‡ะต ัั‚ะต ะฒัŠะฒะตะปะธ ะฟั€ะฐะฒะธะปะฝะพ ะฐะดั€ะตัะฐ ะทะฐ ะตะป. ะฟะพั‰ะฐ. email_been_used = ะะดั€ะตััŠั‚ ะทะฐ ะตะป. ะฟะพั‰ะฐ ะฒะตั‡ะต ัะต ะธะทะฟะพะปะทะฒะฐ. - +unable_verify_ssh_key = ะะต ะผะพะถะต ะดะฐ ัะต ะฟะพั‚ะฒัŠั€ะดะธ SSH ะบะปัŽั‡ัŠั‚, ะฟั€ะพะฒะตั€ะตั‚ะต ะณะพ ะพั‚ะฝะพะฒะพ ะทะฐ ะณั€ะตัˆะบะธ. +enterred_invalid_owner_name = ะ˜ะผะตั‚ะพ ะฝะฐ ะฝะพะฒะธั ะฟั€ะธั‚ะตะถะฐั‚ะตะป ะฝะต ะต ะฒะฐะปะธะดะฝะพ. NewBranchName = ะ˜ะผะต ะฝะฐ ะฝะพะฒะธั ะบะปะพะฝ -git_ref_name_error = ` ั‚ั€ัะฑะฒะฐ ะดะฐ ะต ะฟั€ะฐะฒะธะปะฝะพ ั„ะพั€ะผะฐั‚ะธั€ะฐะฝะพ ะธะผะต ะฝะฐ Git ะฟั€ะตะฟั€ะฐั‚ะบะฐ.` +invalid_ssh_key = ะะต ะผะพะถะต ะดะฐ ัะต ะฟะพั‚ะฒัŠั€ะดะธ ะฒะฐัˆะธัั‚ SSH ะบะปัŽั‡: %s +required_prefix = ะ’ัŠะฒะตะดะตะฝะธัั‚ ั‚ะตะบัั‚ ั‚ั€ัะฑะฒะฐ ะดะฐ ะทะฐะฟะพั‡ะฒะฐ ั โ€ž%sโ€œ regex_pattern_error = ` ัˆะฐะฑะปะพะฝัŠั‚ ะฝะฐ ั€ะตะณัƒะปัั€ะฝะธั ะธะทั€ะฐะท ะต ะฝะตะฒะฐะปะธะดะตะฝ: %s.` repository_files_already_exist = ะ’ะตั‡ะต ััŠั‰ะตัั‚ะฒัƒะฒะฐั‚ ั„ะฐะนะปะพะฒะต ะทะฐ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต. ะกะฒัŠั€ะถะตั‚ะต ัะต ััŠั ัะธัั‚ะตะผะฝะธั ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€. repository_files_already_exist.delete = ะ’ะตั‡ะต ััŠั‰ะตัั‚ะฒัƒะฒะฐั‚ ั„ะฐะนะปะพะฒะต ะทะฐ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต. ะขั€ัะฑะฒะฐ ะดะฐ ะณะธ ะธะทั‚ั€ะธะตั‚ะต. -enterred_invalid_owner_name = ะ˜ะผะตั‚ะพ ะฝะฐ ะฝะพะฒะธั ะฟั€ะธั‚ะตะถะฐั‚ะตะป ะฝะต ะต ะฒะฐะปะธะดะฝะพ. -last_org_owner = ะะต ะผะพะถะตั‚ะต ะดะฐ ะฟั€ะตะผะฐั…ะฝะตั‚ะต ะฟะพัะปะตะดะฝะธั ะฟะพั‚ั€ะตะฑะธั‚ะตะป ะพั‚ ะตะบะธะฟะฐ ะฝะฐ โ€žะฟั€ะธั‚ะตะถะฐั‚ะตะปะธั‚ะตโ€œ. ะขั€ัะฑะฒะฐ ะดะฐ ะธะผะฐ ะฟะพะฝะต ะตะดะธะฝ ะฟั€ะธั‚ะตะถะฐั‚ะตะป ะทะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธั. -invalid_ssh_key = ะะต ะผะพะถะต ะดะฐ ัะต ะฟะพั‚ะฒัŠั€ะดะธ ะฒะฐัˆะธัั‚ SSH ะบะปัŽั‡: %s invalid_gpg_key = ะะต ะผะพะถะต ะดะฐ ัะต ะฟะพั‚ะฒัŠั€ะดะธ ะฒะฐัˆะธัั‚ GPG ะบะปัŽั‡: %s -unable_verify_ssh_key = ะะต ะผะพะถะต ะดะฐ ัะต ะฟะพั‚ะฒัŠั€ะดะธ SSH ะบะปัŽั‡ัŠั‚, ะฟั€ะพะฒะตั€ะตั‚ะต ะณะพ ะพั‚ะฝะพะฒะพ ะทะฐ ะณั€ะตัˆะบะธ. -required_prefix = ะ’ัŠะฒะตะดะตะฝะธัั‚ ั‚ะตะบัั‚ ั‚ั€ัะฑะฒะฐ ะดะฐ ะทะฐะฟะพั‡ะฒะฐ ั โ€ž%sโ€œ +git_ref_name_error = ` ั‚ั€ัะฑะฒะฐ ะดะฐ ะต ะฟั€ะฐะฒะธะปะฝะพ ั„ะพั€ะผะฐั‚ะธั€ะฐะฝะพ ะธะผะต ะฝะฐ Git ะฟั€ะตะฟั€ะฐั‚ะบะฐ.` +last_org_owner = ะะต ะผะพะถะตั‚ะต ะดะฐ ะฟั€ะตะผะฐั…ะฝะตั‚ะต ะฟะพัะปะตะดะฝะธั ะฟะพั‚ั€ะตะฑะธั‚ะตะป ะพั‚ ะตะบะธะฟะฐ ะฝะฐ โ€žะฟั€ะธั‚ะตะถะฐั‚ะตะปะธั‚ะตโ€œ. ะขั€ัะฑะฒะฐ ะดะฐ ะธะผะฐ ะฟะพะฝะต ะตะดะธะฝ ะฟั€ะธั‚ะตะถะฐั‚ะตะป ะทะฐ ะพั€ะณะฐะฝะธะทะฐั†ะธั. [action] close_issue = `ะทะฐั‚ะฒะพั€ะธ ะทะฐะดะฐั‡ะฐ %[3]s#%[2]s` @@ -2374,7 +2302,7 @@ merge_pull_request = `ัะปั ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต %[ auto_merge_pull_request = `ัะปั ะฐะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะทะฐัะฒะบะฐ ะทะฐ ัะปะธะฒะฐะฝะต %[3]s#%[2]s` watched_repo = ะทะฐะฟะพั‡ะฝะฐ ะดะฐ ะฝะฐะฑะปัŽะดะฐะฒะฐ %[2]s delete_tag = ะธะทั‚ั€ะธ ะผะฐั€ะบะตั€ะฐ %[2]s ะพั‚ %[3]s -delete_branch = ะธะทั‚ั€ะธ ะบะปะพะฝะฐ %[2]s ะพั‚ %[3]s +delete_branch = ะธะทั‚ั€ะธ ะบะปะพะฝะฐ %[2]s ะพั‚ %[3]s create_branch = ััŠะทะดะฐะดะต ะบะปะพะฝ %[3]s ะฝะฐ %[4]s publish_release = `ะฟัƒะฑะปะธะบัƒะฒะฐ ะธะทะดะฐะฝะธะต %[4]s ะฝะฐ %[3]s` push_tag = ะธะทั‚ะปะฐัะบะฐ ะผะฐั€ะบะตั€ %[3]s ะบัŠะผ %[4]s @@ -2383,28 +2311,23 @@ reject_pull_request = `ะฟั€ะตะดะปะพะถะธ ะฟั€ะพะผะตะฝะธ ะทะฐ %[3] compare_branch = ะกั€ะฐะฒะฝัะฒะฐะฝะต compare_commits_general = ะกั€ะฐะฒะฝัะฒะฐะฝะต ะฝะฐ ะฟะพะดะฐะฒะฐะฝะธั compare_commits = ะกั€ะฐะฒะฝะตั‚ะต %d ะฟะพะดะฐะฒะฐะฝะธั - transfer_repo = ะฟั€ะตั…ะฒัŠั€ะปะธ ั…ั€ะฐะฝะธะปะธั‰ะต %s ะบัŠะผ %s mirror_sync_push = ัะธะฝั…ั€ะพะฝะธะทะธั€ะฐ ะฟะพะดะฐะฒะฐะฝะธั ะบัŠะผ %[3]s ะฝะฐ %[4]s ะพั‚ ะพะณะปะตะดะฐะปะพ mirror_sync_create = ัะธะฝั…ั€ะพะฝะธะทะธั€ะฐ ะฝะพะฒะฐ ะฟั€ะตะฟั€ะฐั‚ะบะฐ %[3]s ะบัŠะผ %[4]s ะพั‚ ะพะณะปะตะดะฐะปะพ -mirror_sync_delete = ัะธะฝั…ั€ะพะฝะธะทะธั€ะฐ ะธ ะธะทั‚ั€ะธ ะฟั€ะตะฟั€ะฐั‚ะบะฐ %[2]s ะฝะฐ %[3]s ะพั‚ ะพะณะปะตะดะฐะปะพ review_dismissed = `ะพั‚ั…ะฒัŠั€ะปะธ ั€ะตั†ะตะฝะทะธั ะพั‚ %[4]s ะทะฐ %[3]s#%[2]s` +mirror_sync_delete = ัะธะฝั…ั€ะพะฝะธะทะธั€ะฐ ะธ ะธะทั‚ั€ะธ ะฟั€ะตะฟั€ะฐั‚ะบะฐ %[2]s ะฝะฐ %[3]s ะพั‚ ะพะณะปะตะดะฐะปะพ [auth] -tab_openid = OpenID openid_connect_submit = ะกะฒัŠั€ะทะฒะฐะฝะต sign_up_successful = ะะบะฐัƒะฝั‚ัŠั‚ ะต ััŠะทะดะฐะดะตะฝ ัƒัะฟะตัˆะฝะพ. ะ”ะพะฑั€ะต ะดะพัˆะปะธ! login_userpass = ะ’ะปะธะทะฐะฝะต forgot_password = ะ—ะฐะฑั€ะฐะฒะตะฝะฐ ะฟะฐั€ะพะปะฐ? -sign_up_now = ะัƒะถะดะฐะตั‚ะต ัะต ะพั‚ ะฐะบะฐัƒะฝั‚? ะ ะตะณะธัั‚ั€ะธั€ะฐะนั‚ะต ัะต ัะตะณะฐ. forgot_password_title = ะ—ะฐะฑั€ะฐะฒะตะฝะฐ ะฟะฐั€ะพะปะฐ openid_register_title = ะกัŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะฝะพะฒ ะฐะบะฐัƒะฝั‚ account_activated = ะะบะฐัƒะฝั‚ัŠั‚ ะต ะฐะบั‚ะธะฒะธั€ะฐะฝ -social_register_helper_msg = ะ’ะตั‡ะต ะธะผะฐั‚ะต ะฐะบะฐัƒะฝั‚? ะกะฒัŠั€ะถะตั‚ะต ะณะพ ัะตะณะฐ! verify = ะŸะพั‚ะฒัŠั€ะถะดะฐะฒะฐะฝะต create_new_account = ะ ะตะณะธัั‚ั€ะธั€ะฐะฝะต ะฝะฐ ะฐะบะฐัƒะฝั‚ active_your_account = ะะบั‚ะธะฒะธั€ะฐะนั‚ะต ะฐะบะฐัƒะฝั‚ะฐ ัะธ -register_helper_msg = ะ’ะตั‡ะต ะธะผะฐั‚ะต ะฐะบะฐัƒะฝั‚? ะ’ะปะตะทั‚ะต ัะตะณะฐ! reset_password = ะ’ัŠะทัั‚ะฐะฝะพะฒัะฒะฐะฝะต ะฝะฐ ะฐะบะฐัƒะฝั‚ะฐ disable_register_prompt = ะ ะตะณะธัั‚ั€ะธั€ะฐะฝะตั‚ะพ ะต ะธะทะบะปัŽั‡ะตะฝะพ. ะœะพะปั, ัะฒัŠั€ะถะตั‚ะต ัะต ั ะฒะฐัˆะธั ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ ะฝะฐ ัะฐะนั‚ะฐ. remember_me = ะ—ะฐะฟะพะผะฝะธ ะผะต @@ -2413,8 +2336,6 @@ disable_register_mail = ะŸะพั‚ะฒัŠั€ะถะดะตะฝะธะตั‚ะพ ะฟะพ ะตะป. ะฟะพั‰ะฐ ะทะฐ ั€ manual_activation_only = ะกะฒัŠั€ะถะตั‚ะต ัะต ั ะฒะฐัˆะธั ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ ะฝะฐ ัะฐะนั‚ะฐ, ะทะฐ ะดะฐ ะทะฐะฒัŠั€ัˆะธั‚ะต ะฐะบั‚ะธะฒะธั€ะฐะฝะตั‚ะพ. must_change_password = ะžะฑะฝะพะฒะตั‚ะต ะฟะฐั€ะพะปะฐั‚ะฐ ัะธ password_too_short = ะ”ัŠะปะถะธะฝะฐั‚ะฐ ะฝะฐ ะฟะฐั€ะพะปะฐั‚ะฐ ะฝะต ะผะพะถะต ะดะฐ ะฑัŠะดะต ะฟะพ-ะผะฐะปะบะฐ ะพั‚ %d ะทะฝะฐะบะฐ. -tab_signin = ะ’ะปะธะทะฐะฝะต -tab_signup = ะ ะตะณะธัั‚ั€ะธั€ะฐะฝะต password_pwned = ะŸะฐั€ะพะปะฐั‚ะฐ, ะบะพัั‚ะพ ัั‚ะต ะธะทะฑั€ะฐะปะธ, ะต ะฒ ัะฟะธััŠะบ ั ะพั‚ะบั€ะฐะดะฝะฐั‚ะธ ะฟะฐั€ะพะปะธ, ั€ะฐะทะบั€ะธั‚ะธ ะฟั€ะตะดะธ ั‚ะพะฒะฐ ะฟั€ะธ ะฟัƒะฑะปะธั‡ะฝะธ ะฟั€ะพะฑะธะฒะธ ะฝะฐ ะดะฐะฝะฝะธ. ะœะพะปั, ะพะฟะธั‚ะฐะนั‚ะต ะพั‚ะฝะพะฒะพ ั ั€ะฐะทะปะธั‡ะฝะฐ ะฟะฐั€ะพะปะฐ. confirmation_mail_sent_prompt = ะะพะฒะพ ะตะป. ะฟะธัะผะพ ะทะฐ ะฟะพั‚ะฒัŠั€ะถะดะตะฝะธะต ะต ะธะทะฟั€ะฐั‚ะตะฝะพ ะดะพ %s. ะ—ะฐ ะดะฐ ะทะฐะฒัŠั€ัˆะธั‚ะต ะฟั€ะพั†ะตัะฐ ะฝะฐ ั€ะตะณะธัั‚ั€ะฐั†ะธั, ะผะพะปั, ะฟั€ะพะฒะตั€ะตั‚ะต ะฒั…ะพะดัั‰ะฐั‚ะฐ ัะธ ะบัƒั‚ะธั ะธ ะฟะพัะปะตะดะฒะฐะนั‚ะต ะฟั€ะตะดะพัั‚ะฐะฒะตะฝะฐั‚ะฐ ะฒั€ัŠะทะบะฐ ะฒ ั€ะฐะผะบะธั‚ะต ะฝะฐ ัะปะตะดะฒะฐั‰ะธั‚ะต %s. ะะบะพ ะฐะดั€ะตััŠั‚ ะทะฐ ะตะป. ะฟะพั‰ะฐ ะต ะฝะตะฟั€ะฐะฒะธะปะตะฝ, ะผะพะถะตั‚ะต ะดะฐ ะฒะปะตะทะตั‚ะต ะธ ะดะฐ ะฟะพะธัะบะฐั‚ะต ะดั€ัƒะณะพ ะตะป. ะฟะธัะผะพ ะทะฐ ะฟะพั‚ะฒัŠั€ะถะดะตะฝะธะต ะดะฐ ะฑัŠะดะต ะธะทะฟั€ะฐั‚ะตะฝะพ ะฝะฐ ั€ะฐะทะปะธั‡ะตะฝ ะฐะดั€ะตั. hint_login = ะ’ะตั‡ะต ะธะผะฐั‚ะต ะฐะบะฐัƒะฝั‚? ะ’ะปะตะทั‚ะต! @@ -2482,17 +2403,12 @@ mark_all_as_read = ะžั‚ะฑะตะปัะทะฒะฐะฝะต ะฝะฐ ะฒัะธั‡ะบะธ ะบะฐั‚ะพ ะฟั€ะพั‡ะต pin = ะ—ะฐะบะฐั‡ะฒะฐะฝะต ะฝะฐ ะธะทะฒะตัั‚ะธะตั‚ะพ [explore] -code_search_results = ะ ะตะทัƒะปั‚ะฐั‚ะธ ะพั‚ ั‚ัŠั€ัะตะฝะตั‚ะพ ะฝะฐ "%s" go_to = ะžั‚ะธะฒะฐะฝะต ะบัŠะผ repos = ะฅั€ะฐะฝะธะปะธั‰ะฐ users = ะŸะพั‚ั€ะตะฑะธั‚ะตะปะธ -search = ะขัŠั€ัะตะฝะต code = ะšะพะด organizations = ะžั€ะณะฐะฝะธะทะฐั†ะธะธ code_last_indexed_at = ะŸะพัะปะตะดะฝะพ ะธะฝะดะตะบัะธั€ะฐะฝ %s -repo_no_results = ะัะผะฐ ะฝะฐะผะตั€ะตะฝะธ ััŠะพั‚ะฒะตั‚ัั‚ะฒะฐั‰ะธ ั…ั€ะฐะฝะธะปะธั‰ะฐ. -user_no_results = ะัะผะฐ ะฝะฐะผะตั€ะตะฝะธ ััŠะพั‚ะฒะตั‚ัั‚ะฒะฐั‰ะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะปะธ. -org_no_results = ะัะผะฐ ะฝะฐะผะตั€ะตะฝะธ ััŠะพั‚ะฒะตั‚ัั‚ะฒะฐั‰ะธ ะพั€ะณะฐะฝะธะทะฐั†ะธะธ. stars_one = %d ะทะฒะตะทะดะฐ stars_few = %d ะทะฒะตะทะดะธ forks_one = %d ั€ะฐะทะบะปะพะฝะตะฝะธะต @@ -2522,15 +2438,14 @@ variables.management = ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ ะฟั€ะพะผะตะฝะปะธะฒะธ variables.not_found = ะŸั€ะพะผะตะฝะปะธะฒะฐั‚ะฐ ะฝะต ะต ะพั‚ะบั€ะธั‚ะฐ. variables.id_not_exist = ะŸั€ะพะผะตะฝะปะธะฒะฐ ั ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ %d ะฝะต ััŠั‰ะตัั‚ะฒัƒะฒะฐ. runners.owner_type = ะขะธะฟ - -unit.desc = ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ ะธะฝั‚ะตะณั€ะธั€ะฐะฝะธ CI/CD pipelines ั Forgejo Actions. -status.unknown = ะะตะธะทะฒะตัั‚ะฝะพ -status.waiting = ะ˜ะทั‡ะฐะบะฒะฐ ัะต +status.cancelled = ะžั‚ะผะตะฝะตะฝะพ status.running = ะ˜ะทะฟัŠะปะฝัะฒะฐ ัะต status.success = ะฃัะฟะตัˆะฝะพ +status.waiting = ะ˜ะทั‡ะฐะบะฒะฐ ัะต +status.unknown = ะะตะธะทะฒะตัั‚ะฝะพ status.failure = ะะตัƒัะฟะตัˆะฝะพ -status.cancelled = ะžั‚ะผะตะฝะตะฝะพ status.skipped = ะŸั€ะพะฟัƒัะฝะฐั‚ะพ +unit.desc = ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ ะธะฝั‚ะตะณั€ะธั€ะฐะฝะธ CI/CD pipelines ั Forgejo Actions. [heatmap] less = ะŸะพ-ะผะฐะปะบะพ @@ -2613,31 +2528,32 @@ eib = ะ•ะธะ‘ [translation_meta] test = ะพะบะตะน -[repo.permissions] -code.read = ะงะตั‚ะตะฝะต: ะ”ะพัั‚ัŠะฟ ะธ ะบะปะพะฝะธั€ะฐะฝะต ะฝะฐ ะบะพะดะฐ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. -code.write = ะŸะธัะฐะฝะต: ะ˜ะทั‚ะปะฐัะบะฒะฐะฝะต ะบัŠะผ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ, ััŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะบะปะพะฝะพะฒะต ะธ ะผะฐั€ะบะตั€ะธ. -issues.read = ะงะตั‚ะตะฝะต: ะงะตั‚ะตะฝะต ะธ ััŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะทะฐะดะฐั‡ะธ ะธ ะบะพะผะตะฝั‚ะฐั€ะธ. -issues.write = ะŸะธัะฐะฝะต: ะ—ะฐั‚ะฒะฐั€ัะฝะต ะฝะฐ ะทะฐะดะฐั‡ะธ ะธ ัƒะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ ะผะตั‚ะฐะดะฐะฝะฝะธ ะบะฐั‚ะพ ะตั‚ะธะบะตั‚ะธ, ะตั‚ะฐะฟะธ, ะธะทะฟัŠะปะฝะธั‚ะตะปะธ, ะบั€ะฐะนะฝะธ ัั€ะพะบะพะฒะต ะธ ะทะฐะฒะธัะธะผะพัั‚ะธ. -pulls.read = ะงะตั‚ะตะฝะต: ะงะตั‚ะตะฝะต ะธ ััŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต. -pulls.write = ะŸะธัะฐะฝะต: ะ—ะฐั‚ะฒะฐั€ัะฝะต ะฝะฐ ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต ะธ ัƒะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ ะผะตั‚ะฐะดะฐะฝะฝะธ ะบะฐั‚ะพ ะตั‚ะธะบะตั‚ะธ, ะตั‚ะฐะฟะธ, ะธะทะฟัŠะปะฝะธั‚ะตะปะธ, ะบั€ะฐะนะฝะธ ัั€ะพะบะพะฒะต ะธ ะทะฐะฒะธัะธะผะพัั‚ะธ. -releases.read = ะงะตั‚ะตะฝะต: ะŸั€ะตะณะปะตะด ะธ ะธะทั‚ะตะณะปัะฝะต ะฝะฐ ะธะทะดะฐะฝะธั. -wiki.read = ะงะตั‚ะตะฝะต: ะงะตั‚ะตะฝะต ะฝะฐ ะธะฝั‚ะตะณั€ะธั€ะฐะฝะพั‚ะพ ัƒะธะบะธ ะธ ะฝะตะณะพะฒะฐั‚ะฐ ะธัั‚ะพั€ะธั. -wiki.write = ะŸะธัะฐะฝะต: ะกัŠะทะดะฐะฒะฐะฝะต, ะพะฑะฝะพะฒัะฒะฐะฝะต ะธ ะธะทั‚ั€ะธะฒะฐะฝะต ะฝะฐ ัั‚ั€ะฐะฝะธั†ะธ ะฒ ะธะฝั‚ะตะณั€ะธั€ะฐะฝะพั‚ะพ ัƒะธะบะธ. -projects.read = ะงะตั‚ะตะฝะต: ะ”ะพัั‚ัŠะฟ ะดะพ ะฟั€ะพะตะบั‚ะฝะธั‚ะต ั‚ะฐะฑะปะฐ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. -projects.write = ะŸะธัะฐะฝะต: ะกัŠะทะดะฐะฒะฐะฝะต ะธ ั€ะตะดะฐะบั‚ะธั€ะฐะฝะต ะฝะฐ ะฟั€ะพะตะบั‚ะธ ะธ ะบะพะปะพะฝะธ. [gpg] default_key = ะŸะพะดะฟะธัะฐะฝะพ ั ะบะปัŽั‡ ะฟะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต -error.extract_sign = ะะตัƒัะฟะตัˆะฝะพ ะธะทะฒะปะธั‡ะฐะฝะต ะฝะฐ ะฟะพะดะฟะธั -error.generate_hash = ะะตัƒัะฟะตัˆะฝะพ ะณะตะฝะตั€ะธั€ะฐะฝะต ะฝะฐ ั…ะตัˆ ะฝะฐ ะฟะพะดะฐะฒะฐะฝะตั‚ะพ -error.no_committer_account = ะัะผะฐ ะฐะบะฐัƒะฝั‚, ัะฒัŠั€ะทะฐะฝ ั ะฐะดั€ะตัะฐ ะทะฐ ะตะป. ะฟะพั‰ะฐ ะฝะฐ ะฟะพะดะฐะฒะฐั‰ะธั error.no_gpg_keys_found = ะะต ะต ะฝะฐะผะตั€ะตะฝ ะธะทะฒะตัั‚ะตะฝ ะบะปัŽั‡ ะทะฐ ั‚ะพะทะธ ะฟะพะดะฟะธั ะฒ ะฑะฐะทะฐั‚ะฐ ะดะฐะฝะฝะธ error.not_signed_commit = ะะต ะต ะฟะพะดะฟะธัะฐะฝะพ ะฟะพะดะฐะฒะฐะฝะต -error.failed_retrieval_gpg_keys = ะะตัƒัะฟะตัˆะฝะพ ะธะทะฒะปะธั‡ะฐะฝะต ะฝะฐ ะบะปัŽั‡, ัะฒัŠั€ะทะฐะฝ ั ะฐะบะฐัƒะฝั‚ะฐ ะฝะฐ ะฟะพะดะฐะฒะฐั‰ะธั +error.generate_hash = ะะตัƒัะฟะตัˆะฝะพ ะณะตะฝะตั€ะธั€ะฐะฝะต ะฝะฐ ั…ะตัˆ ะฝะฐ ะฟะพะดะฐะฒะฐะฝะตั‚ะพ +error.extract_sign = ะะตัƒัะฟะตัˆะฝะพ ะธะทะฒะปะธั‡ะฐะฝะต ะฝะฐ ะฟะพะดะฟะธั error.probable_bad_signature = ะ’ะะ˜ะœะะะ˜ะ•! ะ’ัŠะฟั€ะตะบะธ ั‡ะต ะธะผะฐ ะบะปัŽั‡ ั ั‚ะพะฒะฐ ID ะฒ ะฑะฐะทะฐั‚ะฐ ะดะฐะฝะฝะธ, ั‚ะพะน ะฝะต ะฟะพั‚ะฒัŠั€ะถะดะฐะฒะฐ ั‚ะพะฒะฐ ะฟะพะดะฐะฒะฐะฝะต! ะขะพะฒะฐ ะฟะพะดะฐะฒะฐะฝะต ะต ะŸะžะ”ะžะ—ะ ะ˜ะขะ•ะ›ะะž. +error.failed_retrieval_gpg_keys = ะะตัƒัะฟะตัˆะฝะพ ะธะทะฒะปะธั‡ะฐะฝะต ะฝะฐ ะบะปัŽั‡, ัะฒัŠั€ะทะฐะฝ ั ะฐะบะฐัƒะฝั‚ะฐ ะฝะฐ ะฟะพะดะฐะฒะฐั‰ะธั error.probable_bad_default_signature = ะ’ะะ˜ะœะะะ˜ะ•! ะ’ัŠะฟั€ะตะบะธ ั‡ะต ะบะปัŽั‡ัŠั‚ ะฟะพ ะฟะพะดั€ะฐะทะฑะธั€ะฐะฝะต ะธะผะฐ ั‚ะพะฒะฐ ID, ั‚ะพะน ะฝะต ะฟะพั‚ะฒัŠั€ะถะดะฐะฒะฐ ั‚ะพะฒะฐ ะฟะพะดะฐะฒะฐะฝะต! ะขะพะฒะฐ ะฟะพะดะฐะฒะฐะฝะต ะต ะŸะžะ”ะžะ—ะ ะ˜ะขะ•ะ›ะะž. +error.no_committer_account = ะัะผะฐ ะฐะบะฐัƒะฝั‚, ัะฒัŠั€ะทะฐะฝ ั ะฐะดั€ะตัะฐ ะทะฐ ะตะป. ะฟะพั‰ะฐ ะฝะฐ ะฟะพะดะฐะฒะฐั‰ะธั + +[repo.permissions] +projects.read = ะงะตั‚ะตะฝะต: ะ”ะพัั‚ัŠะฟ ะดะพ ะฟั€ะพะตะบั‚ะฝะธั‚ะต ั‚ะฐะฑะปะฐ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. +wiki.write = ะŸะธัะฐะฝะต: ะกัŠะทะดะฐะฒะฐะฝะต, ะพะฑะฝะพะฒัะฒะฐะฝะต ะธ ะธะทั‚ั€ะธะฒะฐะฝะต ะฝะฐ ัั‚ั€ะฐะฝะธั†ะธ ะฒ ะธะฝั‚ะตะณั€ะธั€ะฐะฝะพั‚ะพ ัƒะธะบะธ. +issues.read = ะงะตั‚ะตะฝะต: ะงะตั‚ะตะฝะต ะธ ััŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะทะฐะดะฐั‡ะธ ะธ ะบะพะผะตะฝั‚ะฐั€ะธ. +pulls.read = ะงะตั‚ะตะฝะต: ะงะตั‚ะตะฝะต ะธ ััŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต. +pulls.write = ะŸะธัะฐะฝะต: ะ—ะฐั‚ะฒะฐั€ัะฝะต ะฝะฐ ะทะฐัะฒะบะธ ะทะฐ ัะปะธะฒะฐะฝะต ะธ ัƒะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ ะผะตั‚ะฐะดะฐะฝะฝะธ ะบะฐั‚ะพ ะตั‚ะธะบะตั‚ะธ, ะตั‚ะฐะฟะธ, ะธะทะฟัŠะปะฝะธั‚ะตะปะธ, ะบั€ะฐะนะฝะธ ัั€ะพะบะพะฒะต ะธ ะทะฐะฒะธัะธะผะพัั‚ะธ. +projects.write = ะŸะธัะฐะฝะต: ะกัŠะทะดะฐะฒะฐะฝะต ะธ ั€ะตะดะฐะบั‚ะธั€ะฐะฝะต ะฝะฐ ะฟั€ะพะตะบั‚ะธ ะธ ะบะพะปะพะฝะธ. +releases.read = ะงะตั‚ะตะฝะต: ะŸั€ะตะณะปะตะด ะธ ะธะทั‚ะตะณะปัะฝะต ะฝะฐ ะธะทะดะฐะฝะธั. +wiki.read = ะงะตั‚ะตะฝะต: ะงะตั‚ะตะฝะต ะฝะฐ ะธะฝั‚ะตะณั€ะธั€ะฐะฝะพั‚ะพ ัƒะธะบะธ ะธ ะฝะตะณะพะฒะฐั‚ะฐ ะธัั‚ะพั€ะธั. +code.read = ะงะตั‚ะตะฝะต: ะ”ะพัั‚ัŠะฟ ะธ ะบะปะพะฝะธั€ะฐะฝะต ะฝะฐ ะบะพะดะฐ ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. +code.write = ะŸะธัะฐะฝะต: ะ˜ะทั‚ะปะฐัะบะฒะฐะฝะต ะบัŠะผ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ, ััŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะบะปะพะฝะพะฒะต ะธ ะผะฐั€ะบะตั€ะธ. +issues.write = ะŸะธัะฐะฝะต: ะ—ะฐั‚ะฒะฐั€ัะฝะต ะฝะฐ ะทะฐะดะฐั‡ะธ ะธ ัƒะฟั€ะฐะฒะปะตะฝะธะต ะฝะฐ ะผะตั‚ะฐะดะฐะฝะฝะธ ะบะฐั‚ะพ ะตั‚ะธะบะตั‚ะธ, ะตั‚ะฐะฟะธ, ะธะทะฟัŠะปะฝะธั‚ะตะปะธ, ะบั€ะฐะนะฝะธ ัั€ะพะบะพะฒะต ะธ ะทะฐะฒะธัะธะผะพัั‚ะธ. [units] -unit = ะ•ะปะตะผะตะฝั‚ error.no_unit_allowed_repo = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะทะฐ ะดะพัั‚ัŠะฟ ะดะพ ะฝะธะบะพั ัะตะบั†ะธั ะฝะฐ ั‚ะพะฒะฐ ั…ั€ะฐะฝะธะปะธั‰ะต. -error.unit_not_allowed = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะทะฐ ะดะพัั‚ัŠะฟ ะดะพ ั‚ะฐะทะธ ัะตะบั†ะธั ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. +unit = ะ•ะปะตะผะตะฝั‚ +error.unit_not_allowed = ะัะผะฐั‚ะต ั€ะฐะทั€ะตัˆะตะฝะธะต ะทะฐ ะดะพัั‚ัŠะฟ ะดะพ ั‚ะฐะทะธ ัะตะบั†ะธั ะฝะฐ ั…ั€ะฐะฝะธะปะธั‰ะตั‚ะพ. \ No newline at end of file diff --git a/options/locale/locale_ca.ini b/options/locale/locale_ca.ini index ea2af3b645..6a7e074618 100644 --- a/options/locale/locale_ca.ini +++ b/options/locale/locale_ca.ini @@ -39,7 +39,7 @@ twofa_scratch = Codi de rascar de doble-factor passcode = Codi de pas webauthn_insert_key = Inseriu la vostra clau de seguretat webauthn_sign_in = Premeu el botรณ a la vostra clau de seguretat. Si no en tรฉ, torneu-la a inserir. -webauthn_press_button = Siusplau, premeu el botรณ a la vostra clau de seguretatโ€ฆ +webauthn_press_button = Si us plau, premeu el botรณ a la vostra clau de seguretatโ€ฆ webauthn_use_twofa = Utilitza un codi de doble factor des del teu mรฒbil webauthn_error = No s'ha pogut llegir la clau de seguretat. webauthn_unsupported_browser = El teu navegador no suprta WebAuthn. @@ -48,15 +48,11 @@ webauthn_error_insecure = WebAuthn nomรฉs suporta connexions segures. Per provar webauthn_error_unable_to_process = El servidor no ha pogut processar la vostra peticiรณ. webauthn_error_duplicated = La clau de seguretat no รฉs permesa per aquesta peticiรณ. Si us plau, assegureu-vos que la clau encara no ha estat registrada. webauthn_error_empty = S'ha d'anomenar aquesta clau. -webauthn_reload = Recarrega repository = Repositori organization = Organitzaciรณ mirror = Mirall -new_repo = Nou repositori -new_migrate = Nova migraciรณ new_mirror = Nou mirall new_fork = Nou fork d'un repositori -new_org = Nova organitzaciรณ new_project = Nou projecte new_project_column = Nova columna admin_panel = Administraciรณ del lloc @@ -148,7 +144,6 @@ new_migrate.link = Nova migraciรณ new_org.link = Nova organitzaciรณ [search] -milestone_kind = Cerca fites... fuzzy = Difusa search = Cerca... type_tooltip = Tipus de cerca @@ -156,7 +151,6 @@ fuzzy_tooltip = Inclou resultats que s'assemblen al terme de la cerca repo_kind = Cerca reposโ€ฆ user_kind = Cerca usuarisโ€ฆ code_search_unavailable = La cerca de codi no estร  disponible actualment. Si us plau concteu amb l'administrador del lloc. -code_search_by_git_grep = Els resultats actuals de la cerca de codi sรณn proporcionats per "git grep". Podrรญen haver-hi millors resultats si l'administrador del lloc habilita l'indexador de codi. package_kind = Cerca paquetsโ€ฆ project_kind = Cerca projectesโ€ฆ branch_kind = Cerca branquesโ€ฆ @@ -194,8 +188,6 @@ occurred = Hi ha hagut un error report_message = Si creus que aixรฒ es un bug de Forgejo, si us plau cerca problemes a Codeberg i obre'n un de nou si cal. not_found = L'objectiu no s'ha pogut trobar. server_internal = Error intern del servidor -missing_csrf = Peticiรณ Dolenta: falta el testimoni CSRF -invalid_csrf = Peticiรณ Dolenta: testimoni CSRF invร lid network_error = Error de xarxa [install] @@ -444,7 +436,6 @@ link_modal.paste_reminder = Pista: Amb un enllaรง en el teu porta-retalls, pots [home] my_orgs = Organitzacions -show_more_repos = Mostra mรฉs repositorisโ€ฆ show_both_archived_unarchived = Mostrant ambdรณs arxivats i no-arxivats show_only_public = Mostrant nomรฉs publics issues.in_your_repos = En els teus repositoris @@ -454,10 +445,8 @@ show_both_private_public = Mostrant amdรณs publics i privats show_only_private = Mostrant nomรฉs privats filter_by_team_repositories = Filtra per respostirois d'equip feed_of = Canal de "%s" -collaborative_repos = Respositoris coล€laboratius show_archived = Arxivat view_home = Veure %s -password_holder = Contrasenya switch_dashboard_context = Commuta el contexte del tauler my_repos = Repositoris show_only_archived = Mostrant nomรฉs arxivats @@ -483,4 +472,290 @@ admin.new_user.user_info = Informaciรณ d'usuari admin.new_user.subject = Nou usuari %s s'acaba d'enregistrar activate_email.text = Si us plau, cliqueu el segรผent enllaรง per verificar la vostra adreรงa de correu electrรฒnic en %s activate_email = Verifica la teva adreรงa de correu electrรฒnic -activate_account.text_2 = Si us plau, cliqueu l'enllaรง segรผent per activar el vostre compte en %s: \ No newline at end of file +activate_account.text_2 = Si us plau, cliqueu l'enllaรง segรผent per activar el vostre compte en %s: +issue.action.merge = @%[1]s ha fusionat #%[2]d a %[3]s. +release.download.targz = Codi font (TAR.GZ) +repo.transfer.body = Per acceptar-ho o rebutjar-ho, aneu a %s o simplement ignoreu-ho. +team_invite.text_1 = %[1]s us convida a unir-vos a l'equip %[2]s de l'organitzaciรณ %[3]s. +team_invite.text_2 = Si us plau, feu clic al segรผent enllaรง per unir-vos a l'equip: +issue.action.close = @%[1]s ha tancat #%[2]d. +team_invite.text_3 = Nota: aquesta invitaciรณ estava destinada a %[1]s. Si no estร veu esperant-la, podeu ignorar aquest correu electrรฒnic. +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.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 + +[modal] +yes = Sรญ +no = No +cancel = Cancelยทlar +confirm = Confirmar + +[form] +FullName = Nom complet +Description = Descripciรณ +Pronouns = Pronoms +UserName = Nom d'usuari +username_change_not_local_user = No es permet canviar el nom d'usuari als usuaris no locals. +repo_name_been_taken = El nom del repositori ja estร  en รบs. +repository_files_already_exist.adopt = Aquest repositori ja tรฉ fitxers i nomรฉs pot ser Adoptat. +Password = ๏ปฟContrasenya +Retype = Confirma la contrasenya +captcha_incorrect = El codi CAPTCHA รฉs incorrecte. +email_error = ` no รฉs una adreรงa de correu electrรฒnic vร lida.` +unable_verify_ssh_key = No es pot verificar la clau SSH, reviseu si contรฉ errors. +auth_failed = Ha fallat l'autenticaciรณ: %v +still_own_packages = El vostre compte tรฉ un o mรฉs paquets, elimineu-los abans. +org_still_own_repo = Aquesta organitzaciรณ encara tรฉ un o mรฉs repositoris, elimineu-los o transferiu-los abans. +admin_cannot_delete_self = No us podeu eliminar si sou administradors. Si us plau, elimineu els vostres privilegis d'administrador abans. +repository_files_already_exist = Aquest repositori ja tรฉ fitxers. Contacteu l'administrador del sistema. +password_uppercase_one = Com a mรญnim un carร cter en majรบscules +team_not_exist = L'equip no existeix. +team_name_been_taken = El nom de l'equip ja estร  en รบs. +last_org_owner = No podeu eliminar l'รบltim usuari de l'equip "propietaris". Una organitzaciรณ ha de tenir un propietari com a mรญnim. +duplicate_invite_to_team = L'usuari ja ha estat convidat com a membre de l'equip. +Biography = Biografia +RepoName = Nom del repositori +TeamName = Nom de l' equip +To = Nom de la branca +NewBranchName = Nom de la nova branca +Content = Contingut +url_error = `"%s" no รฉs una URL vร lida.` +unknown_error = Error desconegut: +password_not_match = Les contrasenyes no coincideixen. +lang_select_error = Seleccioneu un idioma de la llista. +username_been_taken = El nom d'usuari ja estร  en รบs. +repository_files_already_exist.adopt_or_delete = Aquest repositori ja tรฉ fitxers. Adopteu-los o elimineu-los. +org_name_been_taken = El nom de l'organitzaciรณ ja estร  en รบs. +email_been_used = L'adreรงa de correu electrรฒnic ja estร  en รบs. +email_invalid = L'adreรงa de correu electrรฒnic no es vร lida. +password_complexity = La contrasenya no supera els requisits de complexitat: +password_digit_one = Com a mรญnim un dรญgit +password_special_one = Com a mรญnim un carร cter especial (puntuaciรณ, claudร tors, cometes, etc.) +enterred_invalid_repo_name = El nom del repositori que heu introduรฏt รฉs incorrecte. +enterred_invalid_owner_name = El nom del nou propietari no รฉs vร lid. +enterred_invalid_password = La contrasenya que heu introduรฏt no รฉs correcta. +user_not_exist = L'usuari no existeix. +cannot_add_org_to_team = No es pot afegir una organitzaciรณ com a membre d'un equip. +invalid_ssh_key = No es pot verificar la vostra clau SSH: %s +invalid_gpg_key = No es pot verificar la vostra clau GPG: %s +still_has_org = El vostre compte รฉs membre d'una o mรฉs organitzacions, abandoneu-les abans. +still_own_repo = El vostre compte tรฉ un o mรฉs repositoris, elimineu-los o transferiu-los abans. +target_branch_not_exist = La branca de destรญ no existeix. +AuthName = Nom d'autoritzaciรณ +enterred_invalid_org_name = El nom d'organitzaciรณ que heu introduรฏt รฉs incorrecte. +repository_files_already_exist.delete = Aquest repositori ja tรฉ fitxers. Els heu d'eliminar. +organization_leave_success = Heu deixat l'organitzaciรณ %s amb รจxit. +openid_been_used = L'adreรงa OpenID "%s" ja estร  en รบs. +username_password_incorrect = Nom d'usuari o contrasenya incorrecte. +password_lowercase_one = Com a mรญnim un carร cter en minรบscules +must_use_public_key = La clau que heu proporcionat รฉs una clau privada. Si us plau, no pugueu la vostra clau privada enlloc. Feu servir la vostra clau pรบblica. +org_still_own_packages = Aquesta organitzaciรณ encara tรฉ un o mรฉs paquets, elimineu-los abans. + +[settings] +pronouns = Pronoms +change_username_prompt = Nota: canviar el vostre nom d'usuari tambรฉ canvia l'URL del vostre compte. +comment_type_group_time_tracking = Seguiment del temps +comment_type_group_deadline = Data lรญmit +keep_activity_private = Oculta l'activitat de la pร gina de perfil +repos = Repositoris +applications = Aplicacions +location_placeholder = Compartiu la vostra ubicaciรณ aproximada amb altres +security = Seguretat +profile = Perfil +account = Compte +appearance = Aparenรงa +blocked_users = Usuaris bloquejats +cancel = Cancelยทlar +language = Idioma +comment_type_group_reference = Referรจncia +comment_type_group_dependency = Dependรจncia +saved_successfully = La vostra configuraciรณ s'ha desat amb รจxit. +ssh_gpg_keys = Claus SSH / GPG +public_profile = Perfil pรบblic +ui = Tema +hints = Consells +hidden_comment_types.issue_ref_tooltip = Comentaris on l'usuari canvia la branca/etiqueta associada amb aquesta incidรจncia +hidden_comment_types = Tipus de comentaris ocults +orgs = Organitzacions +organization = Organitzacions +full_name = Nom complet +website = Pร gina web +location = Ubicaciรณ +pronouns_unspecified = Sense especificar +update_theme = Canviar el tema +update_language_not_found = L'idioma "%s" no estร  disponible. +update_language_success = S'ha actualitzat l'idioma. +update_profile_success = El vostre perfil s'ha actualitzat. +change_username_redirect_prompt = El nom d'usuari antic redirigirร  fins que algรบ altre el reclami. +additional_repo_units_hint = Suggereix habilitar unitats de repositori addicionals +update_hints = Actualitza els suggeriments +update_hints_success = S'han actualitzat els suggeriments. +comment_type_group_label = Etiqueta +comment_type_group_title = Tรญtol +comment_type_group_branch = Branca +comment_type_group_issue_ref = Referรจncia d'incidรจncia +hidden_comment_types_description = Els tipus de comentaris marcats aquรญ no es mostraran a les pร gines de les incidรจncies. Marcar "Etiqueta", per exemple, eliminarร  tots els comentaris del tipus "