diff --git a/.changelog.yml b/.changelog.yml index 657dfa1c0e..bfdee0c0ca 100644 --- a/.changelog.yml +++ b/.changelog.yml @@ -13,46 +13,42 @@ groups: - name: BREAKING labels: - - kind/breaking + - pr/breaking - name: SECURITY labels: - - kind/security + - topic/security - name: FEATURES labels: - - kind/feature + - type/feature - name: API labels: - - kind/api + - modifies/api - name: ENHANCEMENTS labels: - - kind/enhancement - - kind/refactor - - kind/ui + - type/enhancement + - type/refactoring + - topic/ui - name: BUGFIXES labels: - - kind/bug + - type/bug - name: TESTING labels: - - kind/testing - - - name: TRANSLATION - labels: - - kind/translation + - type/testing - name: BUILD labels: - - kind/build - - kind/lint + - topic/build + - topic/code-linting - name: DOCS labels: - - kind/docs + - type/docs - name: MISC default: true diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 3084351770..0000000000 --- a/.drone.yml +++ /dev/null @@ -1,425 +0,0 @@ ---- -kind: pipeline -name: release-version - -platform: - os: linux - arch: amd64 - -workspace: - base: /source - path: / - -trigger: - event: - - tag - -volumes: - - name: deps - temp: {} - -steps: - - name: fetch-tags - image: docker:git - pull: always - commands: - - git fetch --tags --force - - - name: deps-frontend - image: node:20 - pull: always - commands: - - make deps-frontend - - - name: deps-backend - image: gitea/test_env:linux-1.20-amd64 - pull: always - commands: - - make deps-backend - volumes: - - name: deps - path: /go - - - name: static - image: techknowlogick/xgo:go-1.21.x - pull: always - commands: - - curl -sL https://deb.nodesource.com/setup_20.x | bash - && apt-get -qqy install nodejs - - export PATH=$PATH:$GOPATH/bin - - make release - environment: - GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not - TAGS: bindata sqlite sqlite_unlock_notify - DEBIAN_FRONTEND: noninteractive - depends_on: [fetch-tags] - volumes: - - name: deps - path: /go - - - name: gpg-sign - image: plugins/gpgsign:1 - pull: always - settings: - detach_sign: true - excludes: - - "dist/release/*.sha256" - files: - - "dist/release/*" - environment: - GPGSIGN_KEY: - from_secret: gpgsign_key - GPGSIGN_PASSPHRASE: - from_secret: gpgsign_passphrase - depends_on: [static] - - - name: release-tag - image: woodpeckerci/plugin-s3:latest - pull: always - settings: - acl: - from_secret: aws_s3_acl - region: - from_secret: aws_s3_region - bucket: - from_secret: aws_s3_bucket - endpoint: - from_secret: aws_s3_endpoint - path_style: - from_secret: aws_s3_path_style - source: "dist/release/*" - strip_prefix: dist/release/ - target: "/gitea/${DRONE_TAG##v}" - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: [gpg-sign] - - - name: github - image: plugins/github-release:latest - pull: always - settings: - files: - - "dist/release/*" - file_exists: overwrite - environment: - GITHUB_TOKEN: - from_secret: github_token - depends_on: [gpg-sign] - ---- -kind: pipeline -type: docker -name: docker-linux-amd64-release-version - -platform: - os: linux - arch: amd64 - -trigger: - ref: - include: - - "refs/tags/**" - exclude: - - "refs/tags/**-rc*" - paths: - exclude: - - "docs/**" - -steps: - - name: fetch-tags - image: docker:git - pull: always - commands: - - git fetch --tags --force - - - name: publish - image: plugins/docker:latest - pull: always - settings: - auto_tag: true - auto_tag_suffix: linux-amd64 - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - - - name: publish-rootless - image: plugins/docker:latest - settings: - dockerfile: Dockerfile.rootless - auto_tag: true - auto_tag_suffix: linux-amd64-rootless - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request ---- - -kind: pipeline -type: docker -name: docker-linux-amd64-release-candidate-version - -platform: - os: linux - arch: amd64 - -trigger: - ref: - - "refs/tags/**-rc*" - paths: - exclude: - - "docs/**" - -steps: - - name: fetch-tags - image: docker:git - pull: always - commands: - - git fetch --tags --force - - - name: publish - image: plugins/docker:latest - pull: always - settings: - tags: ${DRONE_TAG##v}-linux-amd64 - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - - - name: publish-rootless - image: plugins/docker:latest - settings: - dockerfile: Dockerfile.rootless - tags: ${DRONE_TAG##v}-linux-amd64-rootless - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - ---- -kind: pipeline -type: docker -name: docker-linux-arm64-release-version - -platform: - os: linux - arch: arm64 - -trigger: - ref: - include: - - "refs/tags/**" - exclude: - - "refs/tags/**-rc*" - paths: - exclude: - - "docs/**" - -steps: - - name: fetch-tags - image: docker:git - pull: always - commands: - - git fetch --tags --force - - - name: publish - image: plugins/docker:latest - pull: always - settings: - auto_tag: true - auto_tag_suffix: linux-arm64 - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - - - name: publish-rootless - image: plugins/docker:latest - settings: - dockerfile: Dockerfile.rootless - auto_tag: true - auto_tag_suffix: linux-arm64-rootless - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - ---- -kind: pipeline -type: docker -name: docker-linux-arm64-release-candidate-version - -platform: - os: linux - arch: arm64 - -trigger: - ref: - - "refs/tags/**-rc*" - paths: - exclude: - - "docs/**" - -steps: - - name: fetch-tags - image: docker:git - pull: always - commands: - - git fetch --tags --force - - - name: publish - image: plugins/docker:latest - pull: always - settings: - tags: ${DRONE_TAG##v}-linux-arm64 - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - - - name: publish-rootless - image: plugins/docker:latest - settings: - dockerfile: Dockerfile.rootless - tags: ${DRONE_TAG##v}-linux-arm64-rootless - repo: gitea/gitea - build_args: - - GOPROXY=https://goproxy.io - password: - from_secret: docker_password - username: - from_secret: docker_username - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - exclude: - - pull_request - ---- -kind: pipeline -type: docker -name: docker-manifest-version - -platform: - os: linux - arch: amd64 - -steps: - - name: manifest-rootless - image: plugins/manifest - pull: always - settings: - auto_tag: true - ignore_missing: true - spec: docker/manifest.rootless.tmpl - password: - from_secret: docker_password - username: - from_secret: docker_username - - - name: manifest - image: plugins/manifest - settings: - auto_tag: true - ignore_missing: true - spec: docker/manifest.tmpl - password: - from_secret: docker_password - username: - from_secret: docker_username - -trigger: - ref: - - "refs/tags/**" - paths: - exclude: - - "docs/**" - -depends_on: - - docker-linux-amd64-release-version - - docker-linux-amd64-release-candidate-version - - docker-linux-arm64-release-version - - docker-linux-arm64-release-candidate-version diff --git a/.eslintrc.yaml b/.eslintrc.yaml index c9ea481af4..f6a7fb3ed0 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -11,7 +11,6 @@ parserOptions: plugins: - "@eslint-community/eslint-plugin-eslint-comments" - eslint-plugin-array-func - - eslint-plugin-custom-elements - eslint-plugin-import - eslint-plugin-jquery - eslint-plugin-no-jquery @@ -19,6 +18,7 @@ plugins: - eslint-plugin-regexp - eslint-plugin-sonarjs - eslint-plugin-unicorn + - eslint-plugin-vitest-globals - eslint-plugin-wc env: @@ -46,6 +46,9 @@ overrides: - files: ["*.config.*"] rules: import/no-unused-modules: [0] + - files: ["**/*.test.*", "web_src/js/test/setup.js"] + env: + vitest-globals/env: true - files: ["web_src/js/modules/fetch.js", "web_src/js/standalone/**/*"] rules: no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression] @@ -88,19 +91,6 @@ rules: consistent-this: [0] constructor-super: [2] curly: [0] - custom-elements/expose-class-on-global: [0] - custom-elements/extends-correct-class: [2] - custom-elements/file-name-matches-element: [2] - custom-elements/no-constructor: [2] - custom-elements/no-customized-built-in-elements: [2] - custom-elements/no-dom-traversal-in-attributechangedcallback: [2] - custom-elements/no-dom-traversal-in-connectedcallback: [2] - custom-elements/no-exports-with-element: [2] - custom-elements/no-method-prefixed-with-on: [2] - custom-elements/no-unchecked-define: [0] - custom-elements/one-element-per-file: [0] - custom-elements/tag-name-matches-class: [2] - custom-elements/valid-tag-name: [2] default-case-last: [2] default-case: [0] default-param-last: [0] @@ -486,7 +476,7 @@ rules: prefer-exponentiation-operator: [2] prefer-named-capture-group: [0] prefer-numeric-literals: [2] - prefer-object-has-own: [0] + prefer-object-has-own: [2] prefer-object-spread: [2] prefer-promise-reject-errors: [2, {allowEmptyReject: false}] prefer-regex-literals: [2] @@ -740,14 +730,27 @@ rules: valid-typeof: [2, {requireStringLiterals: true}] vars-on-top: [0] wc/attach-shadow-constructor: [2] + wc/define-tag-after-class-definition: [0] + wc/expose-class-on-global: [0] + wc/file-name-matches-element: [2] + wc/guard-define-call: [0] wc/guard-super-call: [2] + wc/max-elements-per-file: [0] + wc/no-child-traversal-in-attributechangedcallback: [2] + wc/no-child-traversal-in-connectedcallback: [2] wc/no-closed-shadow-root: [2] wc/no-constructor-attributes: [2] wc/no-constructor-params: [2] - wc/no-invalid-element-name: [0] # covered by custom-elements/valid-tag-name + wc/no-constructor: [2] + wc/no-customized-built-in-elements: [2] + wc/no-exports-with-element: [0] + wc/no-invalid-element-name: [2] + wc/no-invalid-extends: [2] + wc/no-method-prefixed-with-on: [2] wc/no-self-class: [2] wc/no-typos: [2] wc/require-listener-teardown: [2] + wc/tag-name-matches-class: [2] wrap-iife: [2, inside] wrap-regex: [0] yield-star-spacing: [2, after] diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 24c80bc60a..023fb05a29 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -2,3 +2,4 @@ self-hosted-runner: labels: - actuated-4cpu-8gb - actuated-4cpu-16gb + - nscloud diff --git a/.github/labeler.yml b/.github/labeler.yml index 06a5cd99d1..a1209c77b8 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,35 +1,84 @@ -kind/docs: - - "**/*.md" - - "docs/**" +modifies/docs: + - changed-files: + - any-glob-to-any-file: + - "**/*.md" + - "docs/**" -kind/ui: - - "web_src/**/*" - - all: ["templates/**", "!templates/swagger/v1_json.tmpl"] +modifies/frontend: + - changed-files: + - any-glob-to-any-file: + - "web_src/**" + - "tailwind.config.js" + - "webpack.config.js" -kind/api: - - "templates/swagger/v1_json.tmpl" - - "routers/api/**" +modifies/templates: + - changed-files: + - all-globs-to-any-file: + - "templates/**" + - "!templates/swagger/v1_json.tmpl" -kind/build: - - "Makefile" - - "Dockerfile" - - "Dockerfile.rootless" - - "docker/**" - - "webpack.config.js" +modifies/api: + - changed-files: + - any-glob-to-any-file: + - "routers/api/**" + - "templates/swagger/v1_json.tmpl" -theme/package-registry: - - "modules/packages/**" - - "services/packages/**" - - "routers/api/packages/**" - - "routers/web/shared/packages/**" +modifies/cli: + - changed-files: + - any-glob-to-any-file: + - "cmd/**" -kind/cli: - - "cmd/**" +modifies/translation: + - changed-files: + - any-glob-to-any-file: + - "options/locale/*.ini" -kind/lint: - - ".eslintrc.yaml" - - ".golangci.yml" - - ".markdownlint.yaml" - - ".spectral.yaml" - - ".stylelintrc.yaml" - - ".yamllint.yaml" +modifies/migrations: + - changed-files: + - any-glob-to-any-file: + - "models/migrations/**" + +modifies/internal: + - changed-files: + - any-glob-to-any-file: + - ".air.toml" + - "Makefile" + - "Dockerfile" + - "Dockerfile.rootless" + - ".dockerignore" + - "docker/**" + - ".editorconfig" + - ".eslintrc.yaml" + - ".golangci.yml" + - ".gitpod.yml" + - ".markdownlint.yaml" + - ".spectral.yaml" + - ".stylelintrc.yaml" + - ".yamllint.yaml" + - ".github/**" + - ".gitea/" + - ".devcontainer/**" + - "build.go" + - "build/**" + - "contrib/**" + +modifies/dependencies: + - changed-files: + - any-glob-to-any-file: + - "package.json" + - "package-lock.json" + - "poetry.toml" + - "poetry.lock" + - "go.mod" + - "go.sum" + - "pyproject.toml" + +modifies/go: + - changed-files: + - any-glob-to-any-file: + - "**/*.go" + +modifies/js: + - changed-files: + - any-glob-to-any-file: + - "**/*.js" diff --git a/.github/workflows/disk-clean.yml b/.github/workflows/disk-clean.yml new file mode 100644 index 0000000000..24a9d9216f --- /dev/null +++ b/.github/workflows/disk-clean.yml @@ -0,0 +1,36 @@ +name: disk-clean + +on: + workflow_call: + +jobs: + triage: + runs-on: ubuntu-latest + steps: + # FIXME: https://github.com/jlumbroso/free-disk-space/issues/17 + - name: same as 'large-packages' but without 'google-cloud-sdk' + shell: bash + run: | + sudo apt-get remove -y '^dotnet-.*' + sudo apt-get remove -y '^llvm-.*' + sudo apt-get remove -y 'php.*' + sudo apt-get remove -y '^mongodb-.*' + sudo apt-get remove -y '^mysql-.*' + sudo apt-get remove -y azure-cli google-chrome-stable firefox powershell mono-devel libgl1-mesa-dri + sudo apt-get autoremove -y + sudo apt-get clean + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@main + with: + # this might remove tools that are actually needed, + # if set to "true" but frees about 6 GB + tool-cache: false + + # all of these default to true, but feel free to set to + # "false" if necessary for your workflow + android: true + dotnet: true + haskell: true + large-packages: false + docker-images: false + swap-storage: true diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index d19fa16024..c15983661b 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -64,6 +64,18 @@ jobs: - run: make deps-frontend - run: make lint-swagger + lint-spell: + if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.templates == 'true' + needs: files-changed + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + check-latest: true + - run: make lint-spell + lint-go-windows: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml index a6fb85937c..ae7c762da9 100644 --- a/.github/workflows/pull-db-tests.yml +++ b/.github/workflows/pull-db-tests.yml @@ -49,7 +49,10 @@ jobs: - run: make backend env: TAGS: bindata - - run: make test-pgsql-migration test-pgsql + - name: run migration tests + run: make test-pgsql-migration + - name: run tests + run: make test-pgsql timeout-minutes: 50 env: TAGS: bindata gogit @@ -72,7 +75,10 @@ jobs: - run: make backend env: TAGS: bindata gogit sqlite sqlite_unlock_notify - - run: make test-sqlite-migration test-sqlite + - name: run migration tests + run: make test-sqlite-migration + - name: run tests + run: make test-sqlite timeout-minutes: 50 env: TAGS: bindata gogit sqlite sqlite_unlock_notify @@ -189,8 +195,10 @@ jobs: - run: make backend env: TAGS: bindata + - name: run migration tests + run: make test-mysql-migration - name: run tests - run: make test-mysql-migration integration-test-coverage + run: make integration-test-coverage env: TAGS: bindata RACE_ENABLED: true @@ -252,7 +260,9 @@ jobs: - run: make backend env: TAGS: bindata - - run: make test-mssql-migration test-mssql + - run: make test-mssql-migration + - name: run tests + run: make test-mssql timeout-minutes: 50 env: TAGS: bindata diff --git a/.github/workflows/pull-labeler.yml b/.github/workflows/pull-labeler.yml index c62142b9d2..812819b599 100644 --- a/.github/workflows/pull-labeler.yml +++ b/.github/workflows/pull-labeler.yml @@ -9,13 +9,12 @@ concurrency: cancel-in-progress: true jobs: - label: + labeler: runs-on: ubuntu-latest permissions: contents: read pull-requests: write steps: - - uses: actions/labeler@v4 + - uses: actions/labeler@v5 with: - dot: true sync-labels: true diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index ef3db2db73..d12b14572f 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -1,4 +1,4 @@ -name: release-nightly-assets +name: release-nightly on: push: @@ -10,7 +10,7 @@ concurrency: jobs: nightly-binary: - runs-on: actuated-4cpu-16gb + runs-on: nscloud steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -46,17 +46,17 @@ jobs: REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//') echo "Cleaned name is ${REF_NAME}" echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" + - name: configure aws + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ secrets.AWS_REGION }} + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - name: upload binaries to s3 - uses: jakejarvis/s3-sync-action@master - env: - AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} - SOURCE_DIR: dist/release - DEST_DIR: gitea/${{ steps.clean_name.outputs.branch }} + run: | + aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress nightly-docker-rootful: - runs-on: actuated-4cpu-16gb + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -93,7 +93,7 @@ jobs: push: true tags: gitea/gitea:${{ steps.clean_name.outputs.branch }} nightly-docker-rootless: - runs-on: actuated-4cpu-16gb + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml new file mode 100644 index 0000000000..c6472073e4 --- /dev/null +++ b/.github/workflows/release-tag-rc.yml @@ -0,0 +1,132 @@ +name: release-tag-rc + +on: + push: + tags: + - 'v1*-rc*' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + binary: + runs-on: nscloud + steps: + - uses: actions/checkout@v4 + # fetch all commits instead of only the last as some branches are long lived and could have many between versions + # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 + - run: git fetch --unshallow --quiet --tags --force + - uses: actions/setup-go@v4 + with: + go-version: "~1.21" + check-latest: true + - uses: actions/setup-node@v3 + with: + node-version: 20 + - run: make deps-frontend deps-backend + # xgo build + - run: make release + env: + TAGS: bindata sqlite sqlite_unlock_notify + - name: import gpg key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v5 + with: + gpg_private_key: ${{ secrets.GPGSIGN_KEY }} + passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} + - name: sign binaries + run: | + for f in dist/release/*; do + echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f" + done + # clean branch name to get the folder name in S3 + - name: Get cleaned branch name + id: clean_name + run: | + REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\/v//' -e 's/release\/v//') + echo "Cleaned name is ${REF_NAME}" + echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" + - name: configure aws + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ secrets.AWS_REGION }} + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - name: upload binaries to s3 + run: | + aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress + - name: Install GH CLI + uses: dev-hanz-ops/install-gh-cli-action@v0.1.0 + with: + gh-cli-version: 2.39.1 + - name: create github release + run: | + gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/* + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + docker-rootful: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # fetch all commits instead of only the last as some branches are long lived and could have many between versions + # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 + - run: git fetch --unshallow --quiet --tags --force + - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-buildx-action@v2 + - uses: docker/metadata-action@v5 + id: meta + with: + images: gitea/gitea + flavor: | + latest=false + # 1.2.3-rc0 + tags: | + type=semver,pattern={{version}} + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: build rootful docker image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + docker-rootless: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # fetch all commits instead of only the last as some branches are long lived and could have many between versions + # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 + - run: git fetch --unshallow --quiet --tags --force + - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-buildx-action@v2 + - uses: docker/metadata-action@v5 + id: meta + with: + images: gitea/gitea + # each tag below will have the suffix of -rootless + flavor: | + latest=false + suffix=-rootless + # 1.2.3-rc0 + tags: | + type=semver,pattern={{version}} + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: build rootless docker image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + file: Dockerfile.rootless + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml new file mode 100644 index 0000000000..a18af78a10 --- /dev/null +++ b/.github/workflows/release-tag-version.yml @@ -0,0 +1,143 @@ +name: release-tag-version + +on: + push: + tags: + - 'v1.*' + - '!v1*-rc*' + - '!v1*-dev' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + binary: + runs-on: nscloud + steps: + - uses: actions/checkout@v4 + # fetch all commits instead of only the last as some branches are long lived and could have many between versions + # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 + - run: git fetch --unshallow --quiet --tags --force + - uses: actions/setup-go@v4 + with: + go-version: "~1.21" + check-latest: true + - uses: actions/setup-node@v3 + with: + node-version: 20 + - run: make deps-frontend deps-backend + # xgo build + - run: make release + env: + TAGS: bindata sqlite sqlite_unlock_notify + - name: import gpg key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v5 + with: + gpg_private_key: ${{ secrets.GPGSIGN_KEY }} + passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} + - name: sign binaries + run: | + for f in dist/release/*; do + echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f" + done + # clean branch name to get the folder name in S3 + - name: Get cleaned branch name + id: clean_name + run: | + REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\/v//' -e 's/release\/v//') + echo "Cleaned name is ${REF_NAME}" + echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" + - name: configure aws + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ secrets.AWS_REGION }} + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - name: upload binaries to s3 + run: | + aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress + - name: Install GH CLI + uses: dev-hanz-ops/install-gh-cli-action@v0.1.0 + with: + gh-cli-version: 2.39.1 + - name: create github release + run: | + gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/* + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + docker-rootful: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # fetch all commits instead of only the last as some branches are long lived and could have many between versions + # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 + - run: git fetch --unshallow --quiet --tags --force + - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-buildx-action@v2 + - uses: docker/metadata-action@v5 + id: meta + with: + images: gitea/gitea + # this will generate tags in the following format: + # latest + # 1 + # 1.2 + # 1.2.3 + tags: | + type=semver,pattern={{major}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{version}} + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: build rootful docker image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + docker-rootless: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # fetch all commits instead of only the last as some branches are long lived and could have many between versions + # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 + - run: git fetch --unshallow --quiet --tags --force + - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-buildx-action@v2 + - uses: docker/metadata-action@v5 + id: meta + with: + images: gitea/gitea + # each tag below will have the suffix of -rootless + flavor: | + suffix=-rootless,onlatest=true + # this will generate tags in the following format (with -rootless suffix added): + # latest + # 1 + # 1.2 + # 1.2.3 + tags: | + type=semver,pattern={{major}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{version}} + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: build rootless docker image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + file: Dockerfile.rootless + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 7ccdd53e89..0af8bcd6fe 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -6,7 +6,6 @@ line-length: {code_blocks: false, tables: false, stern: true, line_length: -1} no-alt-text: false no-bare-urls: false no-blanks-blockquote: false -no-duplicate-header: {allow_different_nesting: true} no-emphasis-as-header: false no-empty-links: false no-hard-tabs: {code_blocks: false} diff --git a/.yamllint.yaml b/.yamllint.yaml index c0fce7c301..5a1e1e8751 100644 --- a/.yamllint.yaml +++ b/.yamllint.yaml @@ -24,8 +24,6 @@ rules: document-start: level: error present: false - ignore: | - /.drone.yml document-end: present: false @@ -44,5 +42,3 @@ rules: ignore: | .venv node_modules - /models/fixtures - /models/migrations/fixtures diff --git a/BSDmakefile b/BSDmakefile index 13174f8fd2..79696eadcf 100644 --- a/BSDmakefile +++ b/BSDmakefile @@ -42,13 +42,13 @@ GARGS = "--no-print-directory" # The GNU convention is to use the lowercased `prefix` variable/macro to # specify the installation directory. Humor them. -GPREFIX = "" +GPREFIX = .if defined(PREFIX) && ! defined(prefix) GPREFIX = 'prefix = "$(PREFIX)"' .endif .BEGIN: .SILENT - which $(GMAKE) || printf "Error: GNU Make is required!\n\n" 1>&2 && false + which $(GMAKE) || (printf "Error: GNU Make is required!\n\n" 1>&2 && false) .PHONY: FRC $(.TARGETS): FRC diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ef677b88c..36869d5cae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,808 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.com). +## [1.21.9](https://github.com/go-gitea/gitea/releases/tag/1.21.9) - 2024-03-21 + +* PERFORMANCE + * Only do counting when count_only=true for repo dashboard (#29884) (#29905) + * Add cache for dashboard commit status (#29932) +* ENHANCEMENT + * Make runs-on support variable expression (#29468) (#29782) + * Show Actions post step when it's running (#29926) (#29928) +* BUGFIXES + * Fix PR creation via API between branches of the same repo with head field namespaced (#26986) (#29857) + * Fix and rewrite markup anchor processing (#29931) (#29946) + * Notify reviewers added via CODEOWNERS (#29842) (#29902) + * Fix template error when comment review doesn't exist (#29888) (#29889) + * Fix user id column case (#29863) (#29867) + * Make meilisearch do exact search for issues (#29740 & #29671) (#29846) + * Fix the `for` attribute not pointing to the ID of the color picker (#29813) (#29815) + * Fix codeowner detected diff base branch to mergebase (#29783) (#29807) + * Fix Safari spinner rendering (#29801) (#29802) + * Fix missing translation on milestones (#29785) (#29789) + * Fix user router possible panic (#29751) (#29786) + * Fix possible NPE in ToPullReviewList (#29759) (#29775) + * Fix the wrong default value of ENABLE_OPENID_SIGNIN on docs (#29925) (#29927) + * Solving the issue of UI disruption when the review is deleted without refreshing (#29951) (#29968) + * Fix loadOneBranch panic (#29938) (#29939) + * Fix invalid link of the commit status when ref is tagged (#29752) (#29908) + * Editor error message misleading due to re-used key. (#29859) (#29876) + * Fix double border and border-radius on empty action steps (#29845) (#29850) + * Use `Temporal.PlainDate` for absolute dates (#29804) (#29808) + * Fix incorrect package link method calls in templates (#29580) (#29764) + * Fix the bug that the user may log out if GetUserByID returns unknown error (#29962) (#29964) + * Performance improvements for pull request list page (#29900) (#29972) + * Fix bugs in rerunning jobs (#29983) (#29955) + +## [1.21.8](https://github.com/go-gitea/gitea/releases/tag/1.21.8) - 2024-03-12 + +* SECURITY + * Only use supported sort orders for "/explore/users" page (#29430) (#29443) +* ENHANCEMENTS + * Fix wrong line number in code search result (#29260) (#29623) +* BUGFIXES + * Use Get but not Post to get actions artifacts (#29734) (#29737) + * Fix inconsistent rendering of block mathematical expressions (#29677) (#29711) + * Fix rendering internal file links in org (#29669) (#29705) + * Don't show AbortErrors on logout (#29639) (#29667) + * Fix user-defined markup links targets (#29305) (#29666) + * Fix incorrect rendering csv file when file size is larger than UI.CSV.MaxFileSize (#29653) (#29663) + * Fix hidden test's failure (#29254) (#29662) + * Add empty repo check-in DetectAndHandleSchedules (#29606) (#29659) + * Fix 500 when deleting an account with an incorrect password or unsupported login type (#29579) (#29656) + * Use strict protocol check when redirect (#29642) (#29644) + * Avoid issue info panic (#29625) (#29632) + * Avoid unexpected panic in graceful manager (#29629) (#29630) + * Make "/user/login" page redirect if the current user has signed in (#29583) (#29599) + * Fix workflow trigger event IssueChangeXXX bug (#29559) (#29565) + * Fix incorrect cookie path for AppSubURL (#29534) (#29552) + * Fix queue worker incorrectly stopped when there are still more items in the queue (#29532) (#29546) + * Fix incorrect redirection when creating a PR fails (#29537) (#29543) + * Fix incorrect subpath in links (#29535) (#29541) + * Fix issue link does not support quotes (#29484) (#29487) (#29536) + * Fix issue & comment history bugs (#29525) (#29527) + * Set pre-step status to `skipped` if the job is skipped (#29489) (#29523) + * Fix/Improve `processWindowErrorEvent` (#29407) (#29480) + * Fix counter display number incorrectly displayed on the page (#29448) (#29478) + * Fix workflow trigger event bugs (#29467) (#29475) + * Fix URL calculation in the clone input box (#29470) (#29473) + * The job should always run when `if` is `always()` (#29464) (#29469) + * Fix template bug (#27581) (#29446) + * Not trigger all jobs anymore when re-running the first job (#29439) (#29441) + * Ignore empty repo for CreateRepository in action notifier (#29416) (#29424) + * Fix incorrect tree path value for patch editor (#29377) (#29421) + * Add missing database transaction for new issues (#29490) (#29607) + * Fix 500 when pushing release to an empty repo (#29554) (#29564) + * Fix incorrect relative/absolute URL usages (#29531) (#29547) + * Fix wrong test usage of `AppSubURL` (#29459) (#29488) + * Fix missed return (#29450) (#29453) + * Fixing the issue when status checks per rule matches multiple actions (#29631) (#29655) + * Improve contrast on blame timestamp, fix double border (#29482) (#29485) + +## [1.21.7](https://github.com/go-gitea/gitea/releases/tag/1.21.7) - 2024-02-26 + +* ENHANCEMENTS + * Users with `read` permission of pull requests can be assigned too (#27263) (#29372) +* BUGFIXES + * Do not double close reader (#29354) (#29370) + * Display friendly error message (#29105) (#29363) + * Fix project counter in organization/individual profile (#28068) (#29361) + * Fix validity of the FROM email address not being checked (#29347) (#29360) + * Fix tarball/zipball download bug (#29342) (#29352) +* DOCS + * Docker Tag Information in Docs (#29047) (#29362) +* MISC + * Enforce maxlength in frontend (#29389) (#29396) + +## [1.21.6](https://github.com/go-gitea/gitea/releases/tag/v1.21.6) - 2024-02-22 + +* SECURITY + * Fix XSS vulnerabilities (#29336) + * Use general token signing secret (#29205) (#29325) +* ENHANCEMENTS + * Refactor git version functions and check compatibility (#29155) (#29157) + * Improve user experience for outdated comments (#29050) (#29086) + * Hide code links on release page if user cannot read code (#29064) (#29066) + * Wrap contained tags and branches again (#29021) (#29026) + * Fix incorrect button CSS usages (#29015) (#29023) + * Strip trailing newline in markdown code copy (#29019) (#29022) + * Implement some action notifier functions (#29173) (#29308) + * Load outdated comments when (un)resolving conversation on PR timeline (#29203) (#29221) +* BUGFIXES + * Refactor issue template parsing and fix API endpoint (#29069) (#29140) + * Fix swift packages not resolving (#29095) (#29102) + * Remove SSH workaround (#27893) (#29332) + * Only log error when tag sync fails (#29295) (#29327) + * Fix SSPI user creation (#28948) (#29323) + * Improve the `issue_comment` workflow trigger event (#29277) (#29322) + * Discard unread data of `git cat-file` (#29297) (#29310) + * Fix error display when merging PRs (#29288) (#29309) + * Prevent double use of `git cat-file` session. (#29298) (#29301) + * Fix missing link on outgoing new release notifications (#29079) (#29300) + * Fix debian InRelease Acquire-By-Hash newline (#29204) (#29299) + * Always write proc-receive hook for all git versions (#29287) (#29291) + * Do not show delete button when time tracker is disabled (#29257) (#29279) + * Workaround to clean up old reviews on creating a new one (#28554) (#29264) + * Fix bug when the linked account was disactived and list the linked accounts (#29263) + * Do not use lower tag names to find releases/tags (#29261) (#29262) + * Fix missed edit issues event for actions (#29237) (#29251) + * Only delete scheduled workflows when needed (#29091) (#29235) + * Make submit event code work with both jQuery event and native event (#29223) (#29234) + * Fix push to create with capitalize repo name (#29090) (#29206) + * Use ghost user if user was not found (#29161) (#29169) + * Dont load Review if Comment is CommentTypeReviewRequest (#28551) (#29160) + * Refactor parseSignatureFromCommitLine (#29054) (#29108) + * Avoid showing unnecessary JS errors when there are elements with different origin on the page (#29081) (#29089) + * Fix gitea-origin-url with default ports (#29085) (#29088) + * Fix orgmode link resolving (#29024) (#29076) + * Fix Elasticsearh Request Entity Too Large #28117 (#29062) (#29075) + * Do not render empty comments (#29039) (#29049) + * Avoid sending update/delete release notice when it is draft (#29008) (#29025) + * Fix gitea-action user avatar broken on edited menu (#29190) (#29307) + * Disallow merge when required checked are missing (#29143) (#29268) + * Fix incorrect link to swift doc and swift package-registry login command (#29096) (#29103) + * Convert visibility to number (#29226) (#29244) +* DOCS + * Remove outdated docs from some languages (#27530) (#29208) + * Fix typos in the documentation (#29048) (#29056) + * Explained where create issue/PR template (#29035) + +## [1.21.5](https://github.com/go-gitea/gitea/releases/tag/v1.21.5) - 2024-01-31 + +* SECURITY + * Prevent anonymous container access if `RequireSignInView` is enabled (#28877) (#28882) + * Update go dependencies and fix go-git (#28893) (#28934) +* BUGFIXES + * Revert "Speed up loading the dashboard on mysql/mariadb (#28546)" (#29006) (#29007) + * Fix an actions schedule bug (#28942) (#28999) + * Fix update enable_prune even if mirror_interval is not provided (#28905) (#28929) + * Fix uploaded artifacts should be overwritten (#28726) backport v1.21 (#28832) + * Preserve BOM in web editor (#28935) (#28959) + * Strip `/` from relative links (#28932) (#28952) + * Don't remove all mirror repository's releases when mirroring (#28817) (#28939) + * Implement `MigrateRepository` for the actions notifier (#28920) (#28923) + * Respect branch info for relative links (#28909) (#28922) + * Don't reload timeline page when (un)resolving or replying conversation (#28654) (#28917) + * Only migrate the first 255 chars of a Github issue title (#28902) (#28912) + * Fix sort bug on repository issues list (#28897) (#28901) + * Fix `DeleteCollaboration` transaction behaviour (#28886) (#28889) + * Fix schedule not trigger bug because matching full ref name with short ref name (#28874) (#28888) + * Fix migrate storage bug (#28830) (#28867) + * Fix archive creating LFS hooks and breaking pull requests (#28848) (#28851) + * Fix reverting a merge commit failing (#28794) (#28825) + * Upgrade xorm to v1.3.7 to fix a resource leak problem caused by Iterate (#28891) (#28895) + * Fix incorrect PostgreSQL connection string for Unix sockets (#28865) (#28870) +* ENHANCEMENTS + * Make loading animation less aggressive (#28955) (#28956) + * Avoid duplicate JS error messages on UI (#28873) (#28881) + * Bump `@github/relative-time-element` to 4.3.1 (#28819) (#28826) +* MISC + * Warn that `DISABLE_QUERY_AUTH_TOKEN` is false only if it's explicitly defined (#28783) (#28868) + * Remove duplicated checkinit on git module (#28824) (#28831) + +## [1.21.4](https://github.com/go-gitea/gitea/releases/tag/v1.21.4) - 2024-01-16 + +* SECURITY + * Update github.com/cloudflare/circl (#28789) (#28790) + * Require token for GET subscription endpoint (#28765) (#28768) +* BUGFIXES + * Use refname:strip-2 instead of refname:short when syncing tags (#28797) (#28811) + * Fix links in issue card (#28806) (#28807) + * Fix nil pointer panic when exec some gitea cli command (#28791) (#28795) + * Require token for GET subscription endpoint (#28765) (#28778) + * Fix button size in "attached header right" (#28770) (#28774) + * Fix `convert.ToTeams` on empty input (#28426) (#28767) + * Hide code related setting options in repository when code unit is disabled (#28631) (#28749) + * Fix incorrect URL for "Reference in New Issue" (#28716) (#28723) + * Fix panic when parsing empty pgsql host (#28708) (#28709) + * Upgrade xorm to new version which supported update join for all supported databases (#28590) (#28668) + * Fix alpine package files are not rebuilt (#28638) (#28665) + * Avoid cycle-redirecting user/login page (#28636) (#28658) + * Fix empty ref for cron workflow runs (#28640) (#28647) + * Remove unnecessary syncbranchToDB with tests (#28624) (#28629) + * Use known issue IID to generate new PR index number when migrating from GitLab (#28616) (#28618) + * Fix flex container width (#28603) (#28605) + * Fix the scroll behavior for emoji/mention list (#28597) (#28601) + * Fix wrong due date rendering in issue list page (#28588) (#28591) + * Fix `status_check_contexts` matching bug (#28582) (#28589) + * Fix 500 error of searching commits (#28576) (#28579) + * Use information from previous blame parts (#28572) (#28577) + * Update mermaid for 1.21 (#28571) + * Fix 405 method not allowed CORS / OIDC (#28583) (#28586) (#28587) (#28611) + * Fix `GetCommitStatuses` (#28787) (#28804) + * Forbid removing the last admin user (#28337) (#28793) + * Fix schedule tasks bugs (#28691) (#28780) + * Fix issue dependencies (#27736) (#28776) + * Fix system webhooks API bug (#28531) (#28666) + * Fix when private user following user, private user will not be counted in his own view (#28037) (#28792) + * Render code block in activity tab (#28816) (#28818) +* ENHANCEMENTS + * Rework markup link rendering (#26745) (#28803) + * Modernize merge button (#28140) (#28786) + * Speed up loading the dashboard on mysql/mariadb (#28546) (#28784) + * Assign pull request to project during creation (#28227) (#28775) + * Show description as tooltip instead of title for labels (#28754) (#28766) + * Make template `DateTime` show proper tooltip (#28677) (#28683) + * Switch destination directory for apt signing keys (#28639) (#28642) + * Include heap pprof in diagnosis report to help debugging memory leaks (#28596) (#28599) +* DOCS + * Suggest to use Type=simple for systemd service (#28717) (#28722) + * Extend description for ARTIFACT_RETENTION_DAYS (#28626) (#28630) +* MISC + * Add -F to commit search to treat keywords as strings (#28744) (#28748) + * Add download attribute to release attachments (#28739) (#28740) + * Concatenate error in `checkIfPRContentChanged` (#28731) (#28737) + * Improve 1.21 document for Database Preparation (#28643) (#28644) + +## [1.21.3](https://github.com/go-gitea/gitea/releases/tag/v1.21.3) - 2023-12-21 + +* SECURITY + * Update golang.org/x/crypto (#28519) +* API + * chore(api): support ignore password if login source type is LDAP for creating user API (#28491) (#28525) + * Add endpoint for not implemented Docker auth (#28457) (#28462) +* ENHANCEMENTS + * Add option to disable ambiguous unicode characters detection (#28454) (#28499) + * Refactor SSH clone URL generation code (#28421) (#28480) + * Polyfill SubmitEvent for PaleMoon (#28441) (#28478) +* BUGFIXES + * Fix the issue ref rendering for wiki (#28556) (#28559) + * Fix duplicate ID when deleting repo (#28520) (#28528) + * Only check online runner when detecting matching runners in workflows (#28286) (#28512) + * Initalize stroage for orphaned repository doctor (#28487) (#28490) + * Fix possible nil pointer access (#28428) (#28440) + * Don't show unnecessary citation JS error on UI (#28433) (#28437) +* DOCS + * Update actions document about comparsion as Github Actions (#28560) (#28564) + * Fix documents for "custom/public/assets/" (#28465) (#28467) +* MISC + * Fix inperformant query on retrifing review from database. (#28552) (#28562) + * Improve the prompt for "ssh-keygen sign" (#28509) (#28510) + * Update docs for DISABLE_QUERY_AUTH_TOKEN (#28485) (#28488) + * Fix Chinese translation of config cheat sheet[API] (#28472) (#28473) + * Retry SSH key verification with additional CRLF if it failed (#28392) (#28464) + +## [1.21.2](https://github.com/go-gitea/gitea/releases/tag/v1.21.2) - 2023-12-12 + +* SECURITY + * Rebuild with recently released golang version + * Fix missing check (#28406) (#28411) + * Do some missing checks (#28423) (#28432) +* BUGFIXES + * Fix margin in server signed signature verification view (#28379) (#28381) + * Fix object does not exist error when checking citation file (#28314) (#28369) + * Use `filepath` instead of `path` to create SQLite3 database file (#28374) (#28378) + * Fix the runs will not be displayed bug when the main branch have no workflows but other branches have (#28359) (#28365) + * Handle repository.size column being NULL in migration v263 (#28336) (#28363) + * Convert git commit summary to valid UTF8. (#28356) (#28358) + * Fix migration panic due to an empty review comment diff (#28334) (#28362) + * Add `HEAD` support for rpm repo files (#28309) (#28360) + * Fix RPM/Debian signature key creation (#28352) (#28353) + * Keep profile tab when clicking on Language (#28320) (#28331) + * Fix missing issue search index update when changing status (#28325) (#28330) + * Fix wrong link in `protect_branch_name_pattern_desc` (#28313) (#28315) + * Read `previous` info from git blame (#28306) (#28310) + * Ignore "non-existing" errors when getDirectorySize calculates the size (#28276) (#28285) + * Use appSubUrl for OAuth2 callback URL tip (#28266) (#28275) + * Meilisearch: require all query terms to be matched (#28293) (#28296) + * Fix required error for token name (#28267) (#28284) + * Fix issue will be detected as pull request when checking `First-time contributor` (#28237) (#28271) + * Use full width for project boards (#28225) (#28245) + * Increase "version" when update the setting value to a same value as before (#28243) (#28244) + * Also sync DB branches on push if necessary (#28361) (#28403) + * Make gogit Repository.GetBranchNames consistent (#28348) (#28386) + * Recover from panic in cron task (#28409) (#28425) + * Deprecate query string auth tokens (#28390) (#28430) +* ENHANCEMENTS + * Improve doctor cli behavior (#28422) (#28424) + * Fix margin in server signed signature verification view (#28379) (#28381) + * Refactor template empty checks (#28351) (#28354) + * Read `previous` info from git blame (#28306) (#28310) + * Use full width for project boards (#28225) (#28245) + * Enable system users search via the API (#28013) (#28018) + +## [1.21.1](https://github.com/go-gitea/gitea/releases/tag/v1.21.1) - 2023-11-26 + +* SECURITY + * Fix comment permissions (#28213) (#28216) +* BUGFIXES + * Fix delete-orphaned-repos (#28200) (#28202) + * Make CORS work for oauth2 handlers (#28184) (#28185) + * Fix missing buttons (#28179) (#28181) + * Fix no ActionTaskOutput table waring (#28149) (#28152) + * Fix empty action run title (#28113) (#28148) + * Use "is-loading" to avoid duplicate form submit for code comment (#28143) (#28147) + * Fix Matrix and MSTeams nil dereference (#28089) (#28105) + * Fix incorrect pgsql conn builder behavior (#28085) (#28098) + * Fix system config cache expiration timing (#28072) (#28090) + * Restricted users only see repos in orgs which their team was assigned to (#28025) (#28051) +* API + * Fix permissions for Token DELETE endpoint to match GET and POST (#27610) (#28099) +* ENHANCEMENTS + * Do not display search box when there's no packages yet (#28146) (#28159) + * Add missing `packages.cleanup.success` (#28129) (#28132) +* DOCS + * Docs: Replace deprecated IS_TLS_ENABLED mailer setting in email setup (#28205) (#28208) + * Fix the description about the default setting for action in quick start document (#28160) (#28168) + * Add guide page to actions when there's no workflows (#28145) (#28153) +* MISC + * Use full width for PR comparison (#28182) (#28186) + +## [1.21.0](https://github.com/go-gitea/gitea/releases/tag/v1.21.0) - 2023-11-14 + +* BREAKING + * Restrict certificate type for builtin SSH server (#26789) + * Refactor to use urfave/cli/v2 (#25959) + * Move public asset files to the proper directory (#25907) + * Remove commit status running and warning to align GitHub (#25839) (partially reverted: Restore warning commit status (#27504) (#27529)) + * Remove "CHARSET" config option for MySQL, always use "utf8mb4" (#25413) + * Set SSH_AUTHORIZED_KEYS_BACKUP to false (#25412) +* FEATURES + * User details page (#26713) + * Chore(actions): support cron schedule task (#26655) + * Support rebuilding issue indexer manually (#26546) + * Allow to archive labels (#26478) + * Add disable workflow feature (#26413) + * Support `.git-blame-ignore-revs` file (#26395) + * Pre-register OAuth2 applications for git credential helpers (#26291) + * Add `Retry` button when creating a mirror-repo fails (#26228) + * Artifacts retention and auto clean up (#26131) + * Serve pre-defined files in "public", add "security.txt", add CORS header for ".well-known" (#25974) + * Implement auto-cancellation of concurrent jobs if the event is push (#25716) + * Newly pushed branches hints on repository home page (#25715) + * Display branch commit status (#25608) + * Add direct serving of package content (#25543) + * Add commits dropdown in PR files view and allow commit by commit review (#25528) + * Allow package cleanup from admin page (#25307) + * Batch delete issue and improve tippy opts (#25253) + * Show branches and tags that contain a commit (#25180) + * Add actor and status dropdowns to run list (#25118) + * Allow Organisations to have a E-Mail (#25082) + * Add codeowners feature (#24910) + * Actions Artifacts support uploading multiple files and directories (#24874) + * Support configuration variables on Gitea Actions (#24724) + * Support downloading raw task logs (#24451) +* API + * Unify two factor check (#27915) (#27929) + * Fix package webhook (#27839) (#27855) + * Fix/upload artifact error windows (#27802) (#27840) + * Fix bad method call when deleting user secrets via API (#27829) (#27831) + * Do not force creation of _cargo-index repo on publish (#27266) (#27765) + * Delete repos of org when purge delete user (#27273) (#27728) + * Fix org team endpoint (#27721) (#27727) + * Api: GetPullRequestCommits: return file list (#27483) (#27539) + * Don't let API add 2 exclusive labels from same scope (#27433) (#27460) + * Redefine the meaning of column is_active to make Actions Registration Token generation easier (#27143) (#27304) + * Fix PushEvent NullPointerException jenkinsci/github-plugin (#27203) (#27251) + * Fix organization field being null in POST /orgs/{orgid}/teams (#27150) (#27163) + * Allow empty Conan files (#27092) + * Fix token endpoints ignore specified account (#27080) + * Reduce usage of `db.DefaultContext` (#27073) (#27083) (#27089) (#27103) (#27262) (#27265) (#27347) (#26076) + * Make SSPI auth mockable (#27036) + * Extract auth middleware from service (#27028) + * Add `RemoteAddress` to mirrors (#26952) + * Feat(API): add routes and functions for managing user's secrets (#26909) + * Feat(API): add secret deletion functionality for repository (#26808) + * Feat(API): add route and implementation for creating/updating repository secret (#26766) + * Add Upload URL to release API (#26663) + * Feat(API): update and delete secret for managing organization secrets (#26660) + * Feat: implement organization secret creation API (#26566) + * Add API route to list org secrets (#26485) + * Set commit id when ref used explicitly (#26447) + * PATCH branch-protection updates check list even when checks are disabled (#26351) + * Add file status for API "Get a single commit from a repository" (#16205) (#25831) + * Add API for changing Avatars (#25369) +* BUGFIXES + * Fix viewing wiki commit on empty repo (#28040) (#28044) + * Enable system users for comment.LoadPoster (#28014) (#28032) + * Fixed duplicate attachments on dump on windows (#28019) (#28031) + * Fix wrong xorm Delete usage(backport for 1.21) (#28002) + * Add word-break to repo description in home page (#27924) (#27957) + * Fix rendering assignee changed comments without assignee (#27927) (#27952) + * Add word break to release title (#27942) (#27947) + * Fix JS NPE when viewing specific range of PR commits (#27912) (#27923) + * Show correct commit sha when viewing single commit diff (#27916) (#27921) + * Fix 500 when deleting a dismissed review (#27903) (#27910) + * Fix DownloadFunc when migrating releases (#27887) (#27890) + * Fix http protocol auth (#27875) (#27876) + * Refactor postgres connection string building (#27723) (#27869) + * Close all hashed buffers (#27787) (#27790) + * Fix label render containing invalid HTML (#27752) (#27762) + * Fix duplicate project board when hitting `enter` key (#27746) (#27751) + * Fix `link-action` redirect network error (#27734) (#27749) + * Fix sticky diff header background (#27697) (#27712) + * Always delete existing scheduled action tasks (#27662) (#27688) + * Support allowed hosts for webhook to work with proxy (#27655) (#27675) + * Fix poster is not loaded in get default merge message (#27657) (#27666) + * Improve dropdown button alignment and fix hover bug (#27632) (#27637) + * Improve retrying index issues (#27554) (#27634) + * Fix 404 when deleting Docker package with an internal version (#27615) (#27630) + * Backport manually for a tmpl issue in v1.21 (#27612) + * Don't show Link to TOTP if not set up (#27585) (#27588) + * Fix data-race bug when accessing task.LastRun (#27584) (#27586) + * Fix attachment download bug (#27486) (#27571) + * Respect SSH.KeygenPath option when calculating ssh key fingerprints (#27536) (#27551) + * Improve dropdown's behavior when there is a search input in menu (#27526) (#27534) + * Fix panic in storageHandler (#27446) (#27479) + * When comparing with an non-exist repository, return 404 but 500 (#27437) (#27442) + * Fix pr template (#27436) (#27440) + * Fix git 2.11 error when checking IsEmpty (#27393) (#27397) + * Allow get release download files and lfs files with oauth2 token format (#26430) (#27379) + * Fix missing ctx for GetRepoLink in dashboard (#27372) (#27375) + * Absolute positioned checkboxes overlay floated elements (#26870) (#27366) + * Introduce fixes and more rigorous tests for 'Show on a map' feature (#26803) (#27365) + * Fix repo count in org action settings (#27245) (#27353) + * Add logs for data broken of comment review (#27326) (#27345) + * Fix the approval count of PR when there is no protection branch rule (#27272) (#27343) + * Fix Bug in Issue Config when only contact links are set (#26521) (#27334) + * Improve issue history dialog and make poster can delete their own history (#27323) (#27327) + * Fix orphan check for deleted branch (#27310) (#27321) + * Fix protected branch icon location (#26576) (#27317) + * Fix yaml test (#27297) (#27303) + * Fix some animation bugs (#27287) (#27294) + * Fix incorrect change from #27231 (#27275) (#27282) + * Add missing public user visibility in user details page (#27246) (#27250) + * Fix EOL handling in web editor (#27141) (#27234) + * Fix issues on action runners page (#27226) (#27233) + * Quote table `release` in sql queries (#27205) (#27218) + * Fix release URL in webhooks (#27182) (#27185) + * Fix review request number and add more tests (#27104) (#27168) + * Fix the variable regexp pattern on web page (#27161) (#27164) + * Fix: treat tab "overview" as "repositories" in user profiles without readme (#27124) + * Fix NPE when editing OAuth2 applications (#27078) + * Fix the incorrect route path in the user edit page. (#27007) + * Fix the secret regexp pattern on web page (#26910) + * Allow users with write permissions for issues to add attachments with API (#26837) + * Make "link-action" backend code respond correct JSON content (#26680) + * Use line-height: normal by default (#26635) + * Fix NPM packages name validation (#26595) + * Rewrite the DiffFileTreeItem and fix misalignment (#26565) + * Return empty when searching issues with no repos (#26545) + * Explain SearchOptions and fix ToSearchOptions (#26542) + * Add missing triggers to update issue indexer (#26539) + * Handle base64 decoding correctly to avoid panic (#26483) + * Avoiding accessing undefined mentionValues (#26461) + * Fix incorrect redirection in new issue using references (#26440) + * Fix the bug when getting files changed for `pull_request_target` event (#26320) + * Remove IsWarning in tmpl (#26120) + * Fix loading `LFS_JWT_SECRET` from wrong section (#26109) + * Fixing redirection issue for logged-in users (#26105) + * Improve "gitea doctor" sub-command and fix "help" commands (#26072) + * Fix the truncate and alignment problem for some admin tables (#26042) + * Update minimum password length requirements (#25946) + * Do not "guess" the file encoding/BOM when using API to upload files (#25828) + * Restructure issue list template, styles (#25750) + * Fix `ref` for workflows triggered by `pull_request_target` (#25743) + * Fix issues indexer document mapping (#25619) + * Use JSON response for "user/logout" (#25522) + * Fix migrate page layout on mobile (#25507) + * Link to existing PR when trying to open a new PR on the same branches (#25494) + * Do not publish docker release images on `-dev` tags (#25471) + * Support `pull_request_target` event (#25229) + * Modify the content format of the Feishu webhook (#25106) +* ENHANCEMENTS + * Render email addresses as such if followed by punctuation (#27987) (#27992) + * Show error toast when file size exceeds the limits (#27985) (#27986) + * Fix citation error when the file size is larger than 1024 bytes (#27958) (#27965) + * Remove action runners on user deletion (#27902) (#27908) + * Remove set tabindex on view issue (#27892) (#27896) + * Reduce margin/padding on flex-list items and divider (#27872) (#27874) + * Change katex limits (#27823) (#27868) + * Clean up template locale usage (#27856) (#27857) + * Add dedicated class for empty placeholders (#27788) (#27792) + * Add gap between diff boxes (#27776) (#27781) + * Fix incorrect "tab" parameter for repo search sub-template (#27755) (#27764) + * Enable followCursor for language stats bar (#27713) (#27739) + * Improve diff tree spacing (#27714) (#27719) + * Feed UI Improvements (#27356) (#27717) + * Improve feed icons and feed merge text color (#27498) (#27716) + * [FIX] resolve confusing colors in languages stats by insert a gap (#27704) (#27715) + * Add doctor dbconsistency fix to delete repos with no owner (#27290) (#27693) + * Fix required checkboxes in issue forms (#27592) (#27692) + * Hide archived labels by default from the suggestions when assigning labels for an issue (#27451) (#27661) + * Cleanup repo details icons/labels (#27644) (#27654) + * Keep filter when showing unfiltered results on explore page (#27192) (#27589) + * Show manual cron run's last time (#27544) (#27577) + * Revert "Fix pr template (#27436)" (#27567) + * Increase queue length (#27555) (#27562) + * Avoid run change title process when the title is same (#27467) (#27558) + * Remove max-width and add hide text overflow (#27359) (#27550) + * Add hover background to wiki list page (#27507) (#27521) + * Fix mermaid flowchart margin issue (#27503) (#27516) + * Refactor system setting (#27000) (#27452) + * Fix missing `ctx` in new_form.tmpl (#27434) (#27438) + * Add Index to `action.user_id` (#27403) (#27425) + * Don't use subselect in `DeleteIssuesByRepoID` (#27332) (#27408) + * Add support for HEAD ref in /src/branch and /src/commit routes (#27384) (#27407) + * Make Actions tasks/jobs timeouts configurable by the user (#27400) (#27402) + * Hide archived labels when filtering by labels on the issue list (#27115) (#27381) + * Highlight user details link (#26998) (#27376) + * Add protected branch name description (#27257) (#27351) + * Improve tree not found page (#26570) (#27346) + * Add Index to `comment.dependent_issue_id` (#27325) (#27340) + * Improve branch list UI (#27319) (#27324) + * Fix divider in subscription page (#27298) (#27301) + * Add missed return to actions view fetch (#27289) (#27293) + * Backport ctx locale refactoring manually (#27231) (#27259) (#27260) + * Disable `Test Delivery` and `Replay` webhook buttons when webhook is inactive (#27211) (#27253) + * Use mask-based fade-out effect for `.new-menu` (#27181) (#27243) + * Cleanup locale function usage (#27227) (#27240) + * Fix z-index on markdown completion (#27237) (#27239) + * Fix Fomantic UI dropdown icon bug when there is a search input in menu (#27225) (#27228) + * Allow copying issue comment link on archived repos and when not logged in (#27193) (#27210) + * Fix: text decorator on issue sidebar menu label (#27206) (#27209) + * Fix dropdown icon position (#27175) (#27177) + * Add index to `issue_user.issue_id` (#27154) (#27158) + * Increase auth provider icon size on login page (#27122) + * Remove a `gt-float-right` and some unnecessary helpers (#27110) + * Change green buttons to primary color (#27099) + * Use db.WithTx for AddTeamMember to avoid ctx abuse (#27095) + * Use `print` instead of `printf` (#27093) + * Remove the useless function `GetUserIssueStats` and move relevant tests to `indexer_test.go` (#27067) + * Search branches (#27055) + * Display all user types and org types on admin management UI (#27050) + * Ui correction in mobile view nav bar left aligned items. (#27046) + * Chroma color tweaks (#26978) + * Move some functions to service layer (#26969) + * Improve "language stats" UI (#26968) + * Replace `util.SliceXxx` with `slices.Xxx` (#26958) + * Refactor dashboard/feed.tmpl (#26956) + * Move repository deletion to service layer (#26948) + * Fix the missing repo count (#26942) + * Improve hint when uploading a too large avatar (#26935) + * Extract common code to new template (#26933) + * Move createrepository from module to service layer (#26927) + * Move notification interface to services layer (#26915) + * Move feed notification service layer (#26908) + * Move ui notification to service layer (#26907) + * Move indexer notification to service layer (#26906) + * Move mail notification logic to service layer (#26905) + * Extract common code to new template (#26903) + * Show queue's active worker number (#26896) + * Fix media description render for orgmode (#26895) + * Remove CSS `has` selector and improve various styles (#26891) + * Relocate the `RSS user feed` button (#26882) + * Refactor "shortsha" (#26877) + * Refactor `og:description` to limit the max length (#26876) + * Move web/api context related testing function into a separate package (#26859) + * Redable error on S3 storage connection failure (#26856) + * Improve opengraph previews (#26851) + * Add more descriptive error on forgot password page (#26848) + * Show always repo count in header (#26842) + * Remove "TODO" tasks from CSS file (#26835) + * Render code blocks in repo description (#26830) + * Minor dashboard tweaks, fix flex-list margins (#26829) + * Remove polluted `.ui.right` (#26825) + * Display archived labels specially when listing labels (#26820) + * Remove polluted ".ui.left" style (#26809) + * Make it posible to customize nav text color via css var (#26807) + * Refactor lfs requests (#26783) + * Improve flex list item padding (#26779) + * Remove fomantic `text` module (#26777) + * Remove fomantic `item` module (#26775) + * Remove redundant nil check in `WalkGitLog` (#26773) + * Reduce some allocations in type conversion (#26772) + * Refactor some CSS styles and simplify code (#26771) + * Unify `border-radius` behavior (#26770) + * Improve modal dialog UI (#26764) + * Allow "latest" to be used in release vTag when downloading file (#26748) + * Adding hint `Archived` to archive label. (#26741) + * Move `modules/mirror` to `services` (#26737) + * Add "dir=auto" for input/textarea elements by default (#26735) + * Add auth-required to config.json for Cargo http registry (#26729) + * Simplify helper CSS classes and avoid abuse (#26728) + * Make web context initialize correctly for different cases (#26726) + * Focus editor on "Write" tab click (#26714) + * Remove incorrect CSS helper classes (#26712) + * Fix review bar misalignment (#26711) + * Add reverseproxy auth for API back with default disabled (#26703) + * Add default label in branch select list (#26697) + * Improve Image Diff UI (#26696) + * Fixed text overflow in dropdown menu (#26694) + * [Refactor] getIssueStatsChunk to move inner function into own one (#26671) + * Remove fomantic loader module (#26670) + * Add `member`, `collaborator`, `contributor`, and `first-time contributor` roles and tooltips (#26658) + * Improve some flex layouts (#26649) + * Improve the branch selector tab UI (#26631) + * Improve show role (#26621) + * Remove avatarHTML from template helpers (#26598) + * Allow text selection in actions step header (#26588) + * Improve translation of milestone filters (#26569) + * Add optimistic lock to ActionRun table (#26563) + * Update team invitation email link (#26550) + * Differentiate better between user settings and admin settings (#26538) + * Check disabled workflow when rerun jobs (#26535) + * Improve deadline icon location in milestone list page (#26532) + * Improve repo sub menu (#26531) + * Fix the display of org level badges (#26504) + * Rename `Sync2` -> `Sync` (#26479) + * Fix stderr usages (#26477) + * Remove fomantic transition module (#26469) + * Refactor tests (#26464) + * Refactor project templates (#26448) + * Fall back to esbuild for css minify (#26445) + * Always show usernames in reaction tooltips (#26444) + * Use correct pull request commit link instead of a generic commit link (#26434) + * Refactor "editorconfig" (#26391) + * Make `user-content-* ` consistent with github (#26388) + * Remove unnecessary template helper repoAvatar (#26387) + * Remove unnecessary template helper DisableGravatar (#26386) + * Use template context function for avatar rendering (#26385) + * Rename code_langauge.go to code_language.go (#26377) + * Use more `IssueList` instead of `[]*Issue` (#26369) + * Do not highlight `#number` in documents (#26365) + * Fix display problems of members and teams unit (#26363) + * Fix 404 error when remove self from an organization (#26362) + * Improve CLI and messages (#26341) + * Refactor backend SVG package and add tests (#26335) + * Add link to job details and tooltip to commit status in repo list in dashboard (#26326) + * Use yellow if an approved review is stale (#26312) + * Remove commit load branches and tags in wiki repo (#26304) + * Add highlight to selected repos in milestone dashboard (#26300) + * Delete `issue_service.CreateComment` (#26298) + * Do not show Profile README when repository is private (#26295) + * Tweak actions menu (#26278) + * Start using template context function (#26254) + * Use calendar icon for `Joined on...` in profiles (#26215) + * Add 'Show on a map' button to Location in profile, fix layout (#26214) + * Render plaintext task list items for markdown files (#26186) + * Add tooltip to describe LFS table column and color `delete LFS file` button red (#26181) + * Release attachments duplicated check (#26176) + * De-emphasize issue sidebar buttons (#26171) + * Fixing the align of commit stats in commit_page template. (#26161) + * Allow editing push mirrors after creation (#26151) + * Move web JSON functions to web context and simplify code (#26132) + * Refactor improve NoBetterThan (#26126) + * Improve clickable area in repo action view page (#26115) + * Add context parameter to some database functions (#26055) + * Docusaurus-ify (#26051) + * Improve text for empty issue/pr description (#26047) + * Categorize admin settings sidebar panel (#26030) + * Remove redundant "RouteMethods" method (#26024) + * Refactor and enhance issue indexer to support both searching, filtering and paging (#26012) + * Add a link to OpenID Issuer URL in WebFinger response (#26000) + * Fix UI for release tag page / wiki page / subscription page (#25948) + * Support copy protected branch from template repository (#25889) + * Improve display of Labels/Projects/Assignees sort options (#25886) + * Fix margin on the new/edit project page. (#25885) + * Show image size on view page (#25884) + * Remove ref name in PR commits page (#25876) + * Allow the use of alternative net.Listener implementations by downstreams (#25855) + * Refactor "Content" for file uploading (#25851) + * Add error info if no user can fork the repo (#25820) + * Show edit title button on commits tab of PR, too (#25791) + * Introduce `flex-list` & `flex-item` elements for Gitea UI (#25790) + * Don't stack PR tab menu on small screens (#25789) + * Repository Archived text title center align (#25767) + * Make route middleware/handler mockable (#25766) + * Move issue filters to shared template (#25729) + * Use frontend fetch for branch dropdown component (#25719) + * Add open/closed field support for issue index (#25708) + * Some less naked returns (#25682) + * Fix inconsistent user profile layout across tabs (#25625) + * Get latest commit statuses from database instead of git data on dashboard for repositories (#25605) + * Adding branch-name copy to clipboard branches screen. (#25596) + * Update emoji set to Unicode 15 (#25595) + * Move some files under repo/setting (#25585) + * Add custom ansi colors and CSS variables for them (#25546) + * Add log line anchor for action logs (#25532) + * Use flex instead of float for sort button and search input (#25519) + * Update octicons and use `octicon-file-directory-symlink` (#25453) + * Add toasts to UI (#25449) + * Fine tune project board label colors and modal content background (#25419) + * Import additional secrets via file uri (#25408) + * Switch to ansi_up for ansi rendering in actions (#25401) + * Store and use seconds for timeline time comments (#25392) + * Support displaying diff stats in PR tab bar (#25387) + * Use fetch form action for lock/unlock/pin/unpin on sidebar (#25380) + * Refactor: TotalTimes return seconds (#25370) + * Navbar styling rework (#25343) + * Introduce shared template for search inputs (#25338) + * Only show 'Manage Account Links' when necessary (#25311) + * Improve 'Privacy' section in profile settings (#25309) + * Substitute variables in path names of template repos too (#25294) + * Fix tags line no margin see #25255 (#25280) + * Use fetch to send requests to create issues/comments (#25258) + * Change form actions to fetch for submit review box (#25219) + * Improve AJAX link and modal confirm dialog (#25210) + * Reduce unnecessary DB queries for Actions tasks (#25199) + * Disable `Create column` button while the column name is empty (#25192) + * Refactor indexer (#25174) + * Adjust style for action run list (align icons, adjust padding) (#25170) + * Remove duplicated functions when deleting a branch (#25128) + * Make confusable character warning less jarring (#25069) + * Highlight viewed files differently in the PR filetree (#24956) + * Support changing labels of Actions runner without re-registration (#24806) + * Fix duplicate Reviewed-by trailers (#24796) + * Resolve issue with sort icons on admin/users and admin/runners (#24360) + * Split lfs size from repository size (#22900) + * Sync branches into databases (#22743) + * Disable run user change in installation page (#22499) + * Add merge files files to GetCommitFileStatus (#20515) + * Show OpenID Connect and OAuth on signup page (#20242) +* SECURITY + * Dont leak private users via extensions (#28023) (#28029) + * Expanded minimum RSA Keylength to 3072 (#26604) +* TESTING + * Add user secrets API integration tests (#27832) (#27852) + * Add tests for db indexer in indexer_test.go (#27087) + * Speed up TestEventSourceManagerRun (#26262) + * Add unit test for user renaming (#26261) + * Add some Wiki unit tests (#26260) + * Improve unit test for caching (#26185) + * Add unit test for `HashAvatar` (#25662) +* TRANSLATION + * Backport translations to v1.21 (#27899) + * Fix issues in translation file (#27699) (#27737) + * Add locale for deleted head branch (#26296) + * Improve multiple strings in en-US locale (#26213) + * Fix broken translations for package documantion (#25742) + * Correct translation wrong format (#25643) +* BUILD + * Dockerfile small refactor (#27757) (#27826) + * Fix build errors on BSD (in BSDMakefile) (#27594) (#27608) + * Fully replace drone with actions (#27556) (#27575) + * Enable markdownlint `no-duplicate-header` (#27500) (#27506) + * Enable production source maps for index.js, fix CSS sourcemaps (#27291) (#27295) + * Update snap package (#27021) + * Bump go to 1.21 (#26608) + * Bump xgo to go-1.21.x and node to 20 in release-version (#26589) + * Add template linting via djlint (#25212) +* DOCS + * Change default size of issue/pr attachments and repo file (#27946) (#28017) + * Remove `known issue` section in Gitea Actions Doc (#27930) (#27938) + * Remove outdated paragraphs when comparing Gitea Actions to GitHub Actions (#27119) + * Update brew installation documentation since gitea moved to brew core package (#27070) + * Actions are no longer experimental, so enable them by default (#27054) + * Add a documentation note for Windows Service (#26938) + * Add sparse url in cargo package guide (#26937) + * Update nginx recommendations (#26924) + * Update backup instructions to align with archive structure (#26902) + * Expanding documentation in queue.go (#26889) + * Update info regarding internet connection for build (#26776) + * Docs: template variables (#26547) + * Update index doc (#26455) + * Update zh-cn documentation (#26406) + * Fix typos and grammer problems for actions documentation (#26328) + * Update documentation for 1.21 actions (#26317) + * Doc update swagger doc for POST /orgs/{org}/teams (#26155) + * Doc sync authentication.md to zh-cn (#26117) + * Doc guide the user to create the appropriate level runner (#26091) + * Make organization redirect warning more clear (#26077) + * Update blog links (#25843) + * Fix default value for LocalURL (#25426) + * Update `from-source.zh-cn.md` & `from-source.en-us.md` - Cross Compile Using Zig (#25194) +* MISC + * Replace deprecated `elliptic.Marshal` (#26800) + * Add elapsed time on debug for slow git commands (#25642) + +## [1.20.5](https://github.com/go-gitea/gitea/releases/tag/v1.20.5) - 2023-10-03 + +* ENHANCEMENTS + * Fix z-index on markdown completion (#27237) (#27242 & #27238) + * Use secure cookie for HTTPS sites (#26999) (#27013) +* BUGFIXES + * Fix git 2.11 error when checking IsEmpty (#27393) (#27396) + * Allow get release download files and lfs files with oauth2 token format (#26430) (#27378) + * Fix orphan check for deleted branch (#27310) (#27320) + * Quote table `release` in sql queries (#27205) (#27219) + * Fix release URL in webhooks (#27182) (#27184) + * Fix successful return value for `SyncAndGetUserSpecificDiff` (#27152) (#27156) + * fix pagination for followers and following (#27127) (#27138) + * Fix issue templates when blank isses are disabled (#27061) (#27082) + * Fix context cache bug & enable context cache for dashabord commits' authors(#26991) (#27017) + * Fix INI parsing for value with trailing slash (#26995) (#27001) + * Fix PushEvent NullPointerException jenkinsci/github-plugin (#27203) (#27249) + * Fix organization field being null in POST /orgs/{orgid}/teams (#27150) (#27167 & #27162) + * Fix bug of review request number (#27406) (#27104) +* TESTING + * services/wiki: Close() after error handling (#27129) (#27137) +* DOCS + * Improve actions docs related to `pull_request` event (#27126) (#27145) +* MISC + * Add logs for data broken of comment review (#27326) (#27344) + * Load reviewer before sending notification (#27063) (#27064) + ## [1.20.4](https://github.com/go-gitea/gitea/releases/tag/v1.20.4) - 2023-09-08 * SECURITY @@ -428,7 +1230,6 @@ been added to each release, please refer to the [blog](https://blog.gitea.com). * Add option to search for users is active join a team (#24093) * Add PDF rendering via PDFObject (#24086) * Refactor web route (#24080) - * Make more functions use ctx instead of db.DefaultContext (#24068) * Make HTML template functions support context (#24056) * Refactor rename user and rename organization (#24052) * Localize milestone related time strings (#24051) diff --git a/Dockerfile b/Dockerfile index b42b4daa5f..5fe8df9126 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -#Build stage +# Build stage FROM docker.io/library/golang:1.21-alpine3.18 AS build-env ARG GOPROXY @@ -9,20 +9,38 @@ ARG TAGS="sqlite sqlite_unlock_notify" ENV TAGS "bindata timetzdata $TAGS" ARG CGO_EXTRA_CFLAGS -#Build deps -RUN apk --no-cache add build-base git nodejs npm +# Build deps +RUN apk --no-cache add \ + build-base \ + git \ + nodejs \ + npm \ + && rm -rf /var/cache/apk/* -#Setup repo +# Setup repo COPY . ${GOPATH}/src/code.gitea.io/gitea WORKDIR ${GOPATH}/src/code.gitea.io/gitea -#Checkout version if set +# Checkout version if set RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \ && make clean-all build # Begin env-to-ini build RUN go build contrib/environment-to-ini/environment-to-ini.go +# Copy local files +COPY docker/root /tmp/local + +# Set permissions +RUN chmod 755 /tmp/local/usr/bin/entrypoint \ + /tmp/local/usr/local/bin/gitea \ + /tmp/local/etc/s6/gitea/* \ + /tmp/local/etc/s6/openssh/* \ + /tmp/local/etc/s6/.s6-svscan/* \ + /go/src/code.gitea.io/gitea/gitea \ + /go/src/code.gitea.io/gitea/environment-to-ini +RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete + FROM docker.io/library/alpine:3.18 LABEL maintainer="maintainers@gitea.io" @@ -39,7 +57,8 @@ RUN apk --no-cache add \ s6 \ sqlite \ su-exec \ - gnupg + gnupg \ + && rm -rf /var/cache/apk/* RUN addgroup \ -S -g 1000 \ @@ -61,10 +80,7 @@ VOLUME ["/data"] ENTRYPOINT ["/usr/bin/entrypoint"] CMD ["/bin/s6-svscan", "/etc/s6"] -COPY docker/root / +COPY --from=build-env /tmp/local / COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh -RUN chmod 755 /usr/bin/entrypoint /app/gitea/gitea /usr/local/bin/gitea /usr/local/bin/environment-to-ini -RUN chmod 755 /etc/s6/gitea/* /etc/s6/openssh/* /etc/s6/.s6-svscan/* -RUN chmod 644 /etc/profile.d/gitea_bash_autocomplete.sh diff --git a/Dockerfile.rootless b/Dockerfile.rootless index 449e630fad..5ea4d2fc75 100644 --- a/Dockerfile.rootless +++ b/Dockerfile.rootless @@ -1,4 +1,4 @@ -#Build stage +# Build stage FROM docker.io/library/golang:1.21-alpine3.18 AS build-env ARG GOPROXY @@ -10,19 +10,35 @@ ENV TAGS "bindata timetzdata $TAGS" ARG CGO_EXTRA_CFLAGS #Build deps -RUN apk --no-cache add build-base git nodejs npm +RUN apk --no-cache add \ + build-base \ + git \ + nodejs \ + npm \ + && rm -rf /var/cache/apk/* -#Setup repo +# Setup repo COPY . ${GOPATH}/src/code.gitea.io/gitea WORKDIR ${GOPATH}/src/code.gitea.io/gitea -#Checkout version if set +# Checkout version if set RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \ && make clean-all build # Begin env-to-ini build RUN go build contrib/environment-to-ini/environment-to-ini.go +# Copy local files +COPY docker/rootless /tmp/local + +# Set permissions +RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \ + /tmp/local/usr/local/bin/docker-setup.sh \ + /tmp/local/usr/local/bin/gitea \ + /go/src/code.gitea.io/gitea/gitea \ + /go/src/code.gitea.io/gitea/environment-to-ini +RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete + FROM docker.io/library/alpine:3.18 LABEL maintainer="maintainers@gitea.io" @@ -35,7 +51,8 @@ RUN apk --no-cache add \ gettext \ git \ curl \ - gnupg + gnupg \ + && rm -rf /var/cache/apk/* RUN addgroup \ -S -g 1000 \ @@ -51,21 +68,19 @@ RUN addgroup \ RUN mkdir -p /var/lib/gitea /etc/gitea RUN chown git:git /var/lib/gitea /etc/gitea -COPY docker/rootless / +COPY --from=build-env /tmp/local / COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini COPY --from=build-env /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete /etc/profile.d/gitea_bash_autocomplete.sh -RUN chmod 755 /usr/local/bin/docker-entrypoint.sh /usr/local/bin/docker-setup.sh /app/gitea/gitea /usr/local/bin/gitea /usr/local/bin/environment-to-ini -RUN chmod 644 /etc/profile.d/gitea_bash_autocomplete.sh -#git:git +# git:git USER 1000:1000 ENV GITEA_WORK_DIR /var/lib/gitea ENV GITEA_CUSTOM /var/lib/gitea/custom ENV GITEA_TEMP /tmp/gitea ENV TMPDIR /tmp/gitea -#TODO add to docs the ability to define the ini to load (useful to test and revert a config) +# TODO add to docs the ability to define the ini to load (useful to test and revert a config) ENV GITEA_APP_INI /etc/gitea/app.ini ENV HOME "/var/lib/gitea/git" VOLUME ["/var/lib/gitea", "/etc/gitea"] @@ -73,4 +88,3 @@ WORKDIR /var/lib/gitea ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"] CMD [] - diff --git a/Makefile b/Makefile index fd852cbaf9..ece95d2ad4 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-che GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.5.0 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.1 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 -MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4 +MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.5 XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 @@ -111,6 +111,7 @@ LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64 GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/)) GO_TEST_PACKAGES ?= $(filter-out $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) code.gitea.io/gitea/tests/integration/migration-test code.gitea.io/gitea/tests code.gitea.io/gitea/tests/integration code.gitea.io/gitea/tests/e2e,$(shell $(GO) list ./... | grep -v /vendor/)) +MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) FOMANTIC_WORK_DIR := web_src/fomantic @@ -142,6 +143,8 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN GO_DIRS := build cmd models modules routers services tests WEB_DIRS := web_src/js web_src/css +SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github + GO_SOURCES := $(wildcard *.go) GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go) GO_SOURCES += $(GENERATED_GO_DEST) @@ -219,6 +222,8 @@ help: @echo " - lint-swagger lint swagger files" @echo " - lint-templates lint template files" @echo " - lint-yaml lint yaml files" + @echo " - lint-spell lint spelling" + @echo " - lint-spell-fix lint spelling and fix issues" @echo " - checks run various consistency checks" @echo " - checks-frontend check frontend files" @echo " - checks-backend check backend files" @@ -226,6 +231,7 @@ help: @echo " - test-frontend test frontend files" @echo " - test-backend test backend files" @echo " - test-e2e[\#TestSpecificName] test end to end using playwright" + @echo " - update update js and py dependencies" @echo " - update-js update js dependencies" @echo " - update-py update py dependencies" @echo " - webpack build webpack files" @@ -308,10 +314,6 @@ fmt-check: fmt exit 1; \ fi -.PHONY: misspell-check -misspell-check: - go run $(MISSPELL_PACKAGE) -error $(GO_DIRS) $(WEB_DIRS) - .PHONY: $(TAGS_EVIDENCE) $(TAGS_EVIDENCE): @mkdir -p $(MAKE_EVIDENCE_DIR) @@ -351,13 +353,13 @@ checks: checks-frontend checks-backend checks-frontend: lockfile-check svg-check .PHONY: checks-backend -checks-backend: tidy-check swagger-check fmt-check misspell-check swagger-validate security-check +checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check .PHONY: lint -lint: lint-frontend lint-backend +lint: lint-frontend lint-backend lint-spell .PHONY: lint-fix -lint-fix: lint-frontend-fix lint-backend-fix +lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix .PHONY: lint-frontend lint-frontend: lint-js lint-css @@ -395,6 +397,14 @@ lint-swagger: node_modules lint-md: node_modules npx markdownlint docs *.md +.PHONY: lint-spell +lint-spell: + @go run $(MISSPELL_PACKAGE) -error $(SPELLCHECK_FILES) + +.PHONY: lint-spell-fix +lint-spell-fix: + @go run $(MISSPELL_PACKAGE) -w $(SPELLCHECK_FILES) + .PHONY: lint-go lint-go: $(GO) run $(GOLANGCI_LINT_PACKAGE) run @@ -739,9 +749,7 @@ migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite .PHONY: migrations.individual.mysql.test migrations.individual.mysql.test: $(GO_SOURCES) - for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \ - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \ - done + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES) .PHONY: migrations.individual.mysql8.test migrations.individual.mysql8.test: $(GO_SOURCES) @@ -755,20 +763,15 @@ migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite .PHONY: migrations.individual.pgsql.test migrations.individual.pgsql.test: $(GO_SOURCES) - for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \ - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \ - done + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES) .PHONY: migrations.individual.pgsql.test\#% migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$* - .PHONY: migrations.individual.mssql.test migrations.individual.mssql.test: $(GO_SOURCES) generate-ini-mssql - for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \ - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg -test.failfast; \ - done + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES) .PHONY: migrations.individual.mssql.test\#% migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql @@ -776,9 +779,7 @@ migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql .PHONY: migrations.individual.sqlite.test migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite - for pkg in $(shell $(GO) list code.gitea.io/gitea/models/migrations/...); do \ - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' $$pkg; \ - done + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini $(GO) test $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -p 1 $(MIGRATE_TEST_PACKAGES) .PHONY: migrations.individual.sqlite.test\#% migrations.individual.sqlite.test\#%: $(GO_SOURCES) generate-ini-sqlite @@ -921,9 +922,12 @@ node_modules: package-lock.json @touch node_modules .venv: poetry.lock - poetry install + poetry install --no-root @touch .venv +.PHONY: update +update: update-js update-py + .PHONY: update-js update-js: node-check | node_modules npx updates -u -f package.json diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 8a17148e1b..ee1e503f2a 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -289,6 +289,11 @@ "path": "github.com/cpuguy83/go-md2man/v2/md2man/LICENSE.md", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014 Brian Goff\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, + { + "name": "github.com/cyphar/filepath-securejoin", + "path": "github.com/cyphar/filepath-securejoin/LICENSE", + "licenseText": "Copyright (C) 2014-2015 Docker Inc \u0026 Go Authors. All rights reserved.\nCopyright (C) 2017 SUSE LLC. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + }, { "name": "github.com/davecgh/go-spew/spew", "path": "github.com/davecgh/go-spew/spew/LICENSE", @@ -589,21 +594,11 @@ "path": "github.com/gorilla/sessions/LICENSE", "licenseText": "Copyright (c) 2012-2018 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", @@ -904,11 +899,6 @@ "path": "github.com/rivo/uniseg/LICENSE.txt", "licenseText": "MIT License\n\nCopyright (c) 2019 Oliver Kuederle\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/robfig/cron", - "path": "github.com/robfig/cron/LICENSE", - "licenseText": "Copyright (C) 2012 Rob Figueiredo\nAll Rights Reserved.\n\nMIT LICENSE\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" - }, { "name": "github.com/robfig/cron/v3", "path": "github.com/robfig/cron/v3/LICENSE", diff --git a/cmd/admin.go b/cmd/admin.go index a4f48b0513..d49dcf13cb 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -389,7 +389,7 @@ func runRepoSyncReleases(_ *cli.Context) error { } log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum) - if err = repo_module.SyncReleasesWithTags(repo, gitRepo); err != nil { + if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { log.Warn(" SyncReleasesWithTags: %v", err) gitRepo.Close() continue @@ -438,7 +438,7 @@ func runRegenerateKeys(_ *cli.Context) error { if err := initDB(ctx); err != nil { return err } - return asymkey_model.RewriteAllPublicKeys() + return asymkey_model.RewriteAllPublicKeys(ctx) } func parseOAuth2Config(c *cli.Context) *oauth2.Source { diff --git a/cmd/doctor.go b/cmd/doctor.go index d040a3af1c..4085d37332 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/migrations" migrate_base "code.gitea.io/gitea/models/migrations/base" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/doctor" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -22,6 +23,19 @@ import ( "xorm.io/xorm" ) +// CmdDoctor represents the available doctor sub-command. +var CmdDoctor = &cli.Command{ + Name: "doctor", + Usage: "Diagnose and optionally fix problems", + Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", + + Subcommands: []*cli.Command{ + cmdDoctorCheck, + cmdRecreateTable, + cmdDoctorConvert, + }, +} + var cmdDoctorCheck = &cli.Command{ Name: "check", Usage: "Diagnose and optionally fix problems", @@ -60,19 +74,6 @@ var cmdDoctorCheck = &cli.Command{ }, } -// CmdDoctor represents the available doctor sub-command. -var CmdDoctor = &cli.Command{ - Name: "doctor", - Usage: "Diagnose and optionally fix problems", - Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", - - Subcommands: []*cli.Command{ - cmdDoctorCheck, - cmdRecreateTable, - cmdDoctorConvert, - }, -} - var cmdRecreateTable = &cli.Command{ Name: "recreate-table", Usage: "Recreate tables from XORM definitions and copy the data.", @@ -177,6 +178,7 @@ func runDoctorCheck(ctx *cli.Context) error { if ctx.IsSet("list") { w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) _, _ = w.Write([]byte("Default\tName\tTitle\n")) + doctor.SortChecks(doctor.Checks) for _, check := range doctor.Checks { if check.IsDefault { _, _ = w.Write([]byte{'*'}) @@ -192,26 +194,20 @@ func runDoctorCheck(ctx *cli.Context) error { var checks []*doctor.Check if ctx.Bool("all") { - checks = doctor.Checks + checks = make([]*doctor.Check, len(doctor.Checks)) + copy(checks, doctor.Checks) } else if ctx.IsSet("run") { addDefault := ctx.Bool("default") - names := ctx.StringSlice("run") - for i, name := range names { - names[i] = strings.ToLower(strings.TrimSpace(name)) - } - + runNamesSet := container.SetOf(ctx.StringSlice("run")...) for _, check := range doctor.Checks { - if addDefault && check.IsDefault { + if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) { checks = append(checks, check) - continue - } - for _, name := range names { - if name == check.Name { - checks = append(checks, check) - break - } + runNamesSet.Remove(check.Name) } } + if len(runNamesSet) > 0 { + return fmt.Errorf("unknown checks: %q", strings.Join(runNamesSet.Values(), ",")) + } } else { for _, check := range doctor.Checks { if check.IsDefault { @@ -219,6 +215,5 @@ func runDoctorCheck(ctx *cli.Context) error { } } } - return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks) } diff --git a/cmd/doctor_test.go b/cmd/doctor_test.go new file mode 100644 index 0000000000..75376a567e --- /dev/null +++ b/cmd/doctor_test.go @@ -0,0 +1,33 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "context" + "testing" + + "code.gitea.io/gitea/modules/doctor" + "code.gitea.io/gitea/modules/log" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" +) + +func TestDoctorRun(t *testing.T) { + doctor.Register(&doctor.Check{ + Title: "Test Check", + Name: "test-check", + Run: func(ctx context.Context, logger log.Logger, autofix bool) error { return nil }, + + SkipDatabaseInitialization: true, + }) + app := cli.NewApp() + app.Commands = []*cli.Command{cmdDoctorCheck} + err := app.Run([]string{"./gitea", "check", "--run", "test-check"}) + assert.NoError(t, err) + err = app.Run([]string{"./gitea", "check", "--run", "no-such"}) + assert.ErrorContains(t, err, `unknown checks: "no-such"`) + err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"}) + assert.ErrorContains(t, err, `unknown checks: "no-such"`) +} diff --git a/cmd/dump.go b/cmd/dump.go index 97f292ae09..69ecdcec12 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -452,7 +452,7 @@ func addRecursiveExclude(w archiver.Writer, insidePath, absPath string, excludeA return err } for _, file := range files { - currentAbsPath := path.Join(absPath, file.Name()) + currentAbsPath := filepath.Join(absPath, file.Name()) currentInsidePath := path.Join(insidePath, file.Name()) if file.IsDir() { if !util.SliceContainsString(excludeAbsPath, currentAbsPath) { diff --git a/cmd/generate.go b/cmd/generate.go index 5922617217..34145af24d 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -70,7 +70,7 @@ func runGenerateInternalToken(c *cli.Context) error { } func runGenerateLfsJwtSecret(c *cli.Context) error { - _, jwtSecretBase64, err := generate.NewJwtSecretBase64() + _, jwtSecretBase64, err := generate.NewJwtSecretWithBase64() if err != nil { return err } diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go index acc3ba16ba..aa49445a89 100644 --- a/cmd/migrate_storage.go +++ b/cmd/migrate_storage.go @@ -110,6 +110,9 @@ func migrateLFS(ctx context.Context, dstStorage storage.ObjectStorage) error { func migrateAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error { return db.Iterate(ctx, nil, func(ctx context.Context, user *user_model.User) error { + if user.CustomAvatarRelativePath() == "" { + return nil + } _, err := storage.Copy(dstStorage, user.CustomAvatarRelativePath(), storage.Avatars, user.CustomAvatarRelativePath()) return err }) @@ -117,6 +120,9 @@ func migrateAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error func migrateRepoAvatars(ctx context.Context, dstStorage storage.ObjectStorage) error { return db.Iterate(ctx, nil, func(ctx context.Context, repo *repo_model.Repository) error { + if repo.CustomAvatarRelativePath() == "" { + return nil + } _, err := storage.Copy(dstStorage, repo.CustomAvatarRelativePath(), storage.RepoAvatars, repo.CustomAvatarRelativePath()) return err }) diff --git a/cmd/migrate_storage_test.go b/cmd/migrate_storage_test.go index 644e0dc18b..5d8c867993 100644 --- a/cmd/migrate_storage_test.go +++ b/cmd/migrate_storage_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/packages" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -30,7 +31,7 @@ func TestMigratePackages(t *testing.T) { assert.NoError(t, err) defer buf.Close() - v, f, err := packages_service.CreatePackageAndAddFile(&packages_service.PackageCreationInfo{ + v, f, err := packages_service.CreatePackageAndAddFile(db.DefaultContext, &packages_service.PackageCreationInfo{ PackageInfo: packages_service.PackageInfo{ Owner: creator, PackageType: packages.TypeGeneric, diff --git a/cmd/serv.go b/cmd/serv.go index 26fc91a3b7..76421c200f 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -216,16 +216,18 @@ func runServ(c *cli.Context) error { } } - // LowerCase and trim the repoPath as that's how they are stored. - repoPath = strings.ToLower(strings.TrimSpace(repoPath)) - rr := strings.SplitN(repoPath, "/", 2) if len(rr) != 2 { return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath) } - username := strings.ToLower(rr[0]) - reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git")) + username := rr[0] + reponame := strings.TrimSuffix(rr[1], ".git") + + // LowerCase and trim the repoPath as that's how they are stored. + // This should be done after splitting the repoPath into username and reponame + // so that username and reponame are not affected. + repoPath = strings.ToLower(strings.TrimSpace(repoPath)) if alphaDashDotPattern.MatchString(reponame) { return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame) diff --git a/contrib/environment-to-ini/environment-to-ini.go b/contrib/environment-to-ini/environment-to-ini.go index 7758045fd3..a7d7a6d293 100644 --- a/contrib/environment-to-ini/environment-to-ini.go +++ b/contrib/environment-to-ini/environment-to-ini.go @@ -47,24 +47,28 @@ func main() { on the configuration cheat sheet.` app.Flags = []cli.Flag{ &cli.StringFlag{ - Name: "custom-path, C", - Value: setting.CustomPath, - Usage: "Custom path file path", + Name: "custom-path", + Aliases: []string{"C"}, + Value: setting.CustomPath, + Usage: "Custom path file path", }, &cli.StringFlag{ - Name: "config, c", - Value: setting.CustomConf, - Usage: "Custom configuration file path", + Name: "config", + Aliases: []string{"c"}, + Value: setting.CustomConf, + Usage: "Custom configuration file path", }, &cli.StringFlag{ - Name: "work-path, w", - Value: setting.AppWorkPath, - Usage: "Set the gitea working path", + Name: "work-path", + Aliases: []string{"w"}, + Value: setting.AppWorkPath, + Usage: "Set the gitea working path", }, &cli.StringFlag{ - Name: "out, o", - Value: "", - Usage: "Destination file to write to", + Name: "out", + Aliases: []string{"o"}, + Value: "", + Usage: "Destination file to write to", }, } app.Action = runEnvironmentToIni diff --git a/contrib/systemd/gitea.service b/contrib/systemd/gitea.service index c097fb0d17..c091722a74 100644 --- a/contrib/systemd/gitea.service +++ b/contrib/systemd/gitea.service @@ -1,6 +1,5 @@ [Unit] Description=Gitea (Git with a cup of tea) -After=syslog.target After=network.target ### # Don't forget to add the database service dependencies @@ -52,7 +51,7 @@ After=network.target # Uncomment the next line if you have repos with lots of files and get a HTTP 500 error because of that # LimitNOFILE=524288:524288 RestartSec=2s -Type=notify +Type=simple User=git Group=git WorkingDirectory=/var/lib/gitea/ @@ -62,7 +61,6 @@ WorkingDirectory=/var/lib/gitea/ ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini Restart=always Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea -WatchdogSec=30s # If you install Git to directory prefix other than default PATH (which happens # for example if you install other versions of Git side-to-side with # distribution version), uncomment below line and add that prefix to PATH diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index cd6b2a914d..6122b36be3 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -491,6 +491,11 @@ INTERNAL_TOKEN= ;; Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations. ;; This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security. ;SUCCESSFUL_TOKENS_CACHE_SIZE = 20 +;; +;; Reject API tokens sent in URL query string (Accept Header-based API tokens only). This avoids security vulnerabilities +;; stemming from cached/logged plain-text API tokens. +;; In future releases, this will become the default behavior +;DISABLE_QUERY_AUTH_TOKEN = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1014,8 +1019,8 @@ LEVEL = Info ;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. ;ALLOWED_TYPES = ;; -;; Max size of each file in megabytes. Defaults to 3MB -;FILE_MAX_SIZE = 3 +;; Max size of each file in megabytes. Defaults to 50MB +;FILE_MAX_SIZE = 50 ;; ;; Max number of files per upload. Defaults to 5 ;MAX_FILES = 5 @@ -1151,15 +1156,9 @@ LEVEL = Info ;; enable cors headers (disabled by default) ;ENABLED = false ;; -;; scheme of allowed requests -;SCHEME = http -;; -;; list of requesting domains that are allowed +;; list of requesting origins that are allowed, eg: "https://*.example.com" ;ALLOW_DOMAIN = * ;; -;; allow subdomains of headers listed above to request -;ALLOW_SUBDOMAIN = false -;; ;; list of methods allowed to request ;METHODS = GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS ;; @@ -1205,6 +1204,9 @@ LEVEL = Info ;; Max size of files to be displayed (default is 8MiB) ;MAX_DISPLAY_FILE_SIZE = 8388608 ;; +;; Detect ambiguous unicode characters in file contents and show warnings on the UI +;AMBIGUOUS_UNICODE_DETECTION = true +;; ;; Whether the email of the user should be shown in the Explore Users page ;SHOW_USER_EMAIL = true ;; @@ -1219,6 +1221,9 @@ LEVEL = Info ;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png ;REACTIONS = +1, -1, laugh, hooray, confused, heart, rocket, eyes ;; +;; Change the number of users that are displayed in reactions tooltip (triggered by mouse hover). +;REACTION_MAX_USER_NUM = 10 +;; ;; Additional Emojis not defined in the utf8 standard ;; By default we support gitea (:gitea:), to add more copy them to public/assets/img/emoji/emoji_name.png and add it to this config. ;; Dont mistake it for Reactions. @@ -1420,7 +1425,7 @@ LEVEL = Info ;DATADIR = queues/ ; Relative paths will be made absolute against `%(APP_DATA_PATH)s`. ;; ;; Default queue length before a channel queue will block -;LENGTH = 100 +;LENGTH = 100000 ;; ;; Batch size to send for batched queues ;BATCH_LENGTH = 20 @@ -1812,8 +1817,8 @@ LEVEL = Info ;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. ;ALLOWED_TYPES = .csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip ;; -;; Max size of each file. Defaults to 4MB -;MAX_SIZE = 4 +;; Max size of each file. Defaults to 2048MB +;MAX_SIZE = 2048 ;; ;; Max number of files per upload. Defaults to 5 ;MAX_FILES = 5 @@ -2566,8 +2571,14 @@ LEVEL = Info ;; ;; Default platform to get action plugins, `github` for `https://github.com`, `self` for the current Gitea instance. ;DEFAULT_ACTIONS_URL = github -;; Default artifact retention time in days, default is 90 days +;; Default artifact retention time in days. Artifacts could have their own retention periods by setting the `retention-days` option in `actions/upload-artifact` step. ;ARTIFACT_RETENTION_DAYS = 90 +;; Timeout to stop the task which have running status, but haven't been updated for a long time +;ZOMBIE_TASK_TIMEOUT = 10m +;; Timeout to stop the tasks which have running status and continuous updates, but don't end for a long time +;ENDLESS_TASK_TIMEOUT = 3h +;; Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time +;ABANDONED_JOB_TIMEOUT = 24h ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docker/root/usr/bin/entrypoint b/docker/root/usr/bin/entrypoint index 0acfec4dbe..d9dbb3ebe0 100755 --- a/docker/root/usr/bin/entrypoint +++ b/docker/root/usr/bin/entrypoint @@ -7,7 +7,7 @@ if [ ! -x /bin/sh ]; then fi if [ "${USER}" != "git" ]; then - # rename user + # Rename user sed -i -e "s/^git\:/${USER}\:/g" /etc/passwd fi @@ -19,13 +19,13 @@ if [ -z "${USER_UID}" ]; then USER_UID="`id -u ${USER}`" fi -## Change GID for USER? +# Change GID for USER? if [ -n "${USER_GID}" ] && [ "${USER_GID}" != "`id -g ${USER}`" ]; then sed -i -e "s/^${USER}:\([^:]*\):[0-9]*/${USER}:\1:${USER_GID}/" /etc/group sed -i -e "s/^${USER}:\([^:]*\):\([0-9]*\):[0-9]*/${USER}:\1:\2:${USER_GID}/" /etc/passwd fi -## Change UID for USER? +# Change UID for USER? if [ -n "${USER_UID}" ] && [ "${USER_UID}" != "`id -u ${USER}`" ]; then sed -i -e "s/^${USER}:\([^:]*\):[0-9]*:\([0-9]*\)/${USER}:\1:${USER_UID}:\2/" /etc/passwd fi diff --git a/docs/content/administration.fr-fr.md b/docs/content/administration.fr-fr.md deleted file mode 100644 index ed11881b77..0000000000 --- a/docs/content/administration.fr-fr.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Avancé" -slug: "administration" -sidebar_position: 30 -toc: false -draft: false -menu: - sidebar: - name: "Avancé" - sidebar_position: 20 - identifier: "administration" ---- diff --git a/docs/content/administration.zh-tw.md b/docs/content/administration.zh-tw.md deleted file mode 100644 index 455d6a363f..0000000000 --- a/docs/content/administration.zh-tw.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "運維" -slug: "administration" -sidebar_position: 30 -toc: false -draft: false -menu: - sidebar: - name: "運維" - sidebar_position: 20 - identifier: "administration" ---- diff --git a/docs/content/administration/_index.zh-tw.md b/docs/content/administration/_index.zh-tw.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/administration/adding-legal-pages.en-us.md b/docs/content/administration/adding-legal-pages.en-us.md index c6f68edcd0..1ff0c0132d 100644 --- a/docs/content/administration/adding-legal-pages.en-us.md +++ b/docs/content/administration/adding-legal-pages.en-us.md @@ -19,10 +19,10 @@ Some jurisdictions (such as EU), requires certain legal pages (e.g. Privacy Poli ## Getting Pages -Gitea source code ships with sample pages, available in `contrib/legal` directory. Copy them to `custom/public/`. For example, to add Privacy Policy: +Gitea source code ships with sample pages, available in `contrib/legal` directory. Copy them to `custom/public/assets/`. For example, to add Privacy Policy: ``` -wget -O /path/to/custom/public/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample +wget -O /path/to/custom/public/assets/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample ``` Now you need to edit the page to meet your requirements. In particular you must change the email addresses, web addresses and references to "Your Gitea Instance" to match your situation. diff --git a/docs/content/administration/adding-legal-pages.zh-cn.md b/docs/content/administration/adding-legal-pages.zh-cn.md index 5d582e871d..3e18c6e6b0 100644 --- a/docs/content/administration/adding-legal-pages.zh-cn.md +++ b/docs/content/administration/adding-legal-pages.zh-cn.md @@ -19,10 +19,10 @@ menu: ## 获取页面 -Gitea 源代码附带了示例页面,位于 `contrib/legal` 目录中。将它们复制到 `custom/public/` 目录下。例如,如果要添加隐私政策: +Gitea 源代码附带了示例页面,位于 `contrib/legal` 目录中。将它们复制到 `custom/public/assets/` 目录下。例如,如果要添加隐私政策: ``` -wget -O /path/to/custom/public/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample +wget -O /path/to/custom/public/assets/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample ``` 现在,你需要编辑该页面以满足你的需求。特别是,你必须更改电子邮件地址、网址以及与 "Your Gitea Instance" 相关的引用,以匹配你的情况。 diff --git a/docs/content/administration/backup-and-restore.zh-tw.md b/docs/content/administration/backup-and-restore.zh-tw.md deleted file mode 100644 index 4966ccdc50..0000000000 --- a/docs/content/administration/backup-and-restore.zh-tw.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -date: "2017-01-01T16:00:00+02:00" -title: "用法: 備份與還原" -slug: "backup-and-restore" -sidebar_position: 11 -toc: false -draft: false -aliases: - - /zh-tw/backup-and-restore -menu: - sidebar: - parent: "administration" - name: "備份與還原" - sidebar_position: 11 - identifier: "backup-and-restore" ---- - -# 備份與還原 - -Gitea 目前支援 `dump` 指令,用來將資料備份成 zip 檔案,後續會提供還原指令,讓你可以輕易的將備份資料及還原到另外一台機器。 - -## 備份指令 (`dump`) - -首先,切換到執行 Gitea 的使用者: `su git` (請修改成您指定的使用者),並在安裝目錄內執行 `./gitea dump` 指令,你可以看到 console 畫面如下: - -``` -2016/12/27 22:32:09 Creating tmp work dir: /tmp/gitea-dump-417443001 -2016/12/27 22:32:09 Dumping local repositories.../home/git/gitea-repositories -2016/12/27 22:32:22 Dumping database... -2016/12/27 22:32:22 Packing dump files... -2016/12/27 22:32:34 Removing tmp work dir: /tmp/gitea-dump-417443001 -2016/12/27 22:32:34 Finish dumping in file gitea-dump-1482906742.zip -``` - -備份出來的 `gitea-dump-1482906742.zip` 檔案,檔案內會包含底下內容: - -* `custom/conf/app.ini` - 伺服器設定檔。 -* `gitea-db.sql` - SQL 備份檔案。 -* `gitea-repo.zip` - 此 zip 檔案為全部的 repo 目錄。 - 請參考 Config -> repository -> `ROOT` 所設定的路徑。 -* `log/` - 全部 logs 檔案,如果你要 migrate 到其他伺服器,此目錄不用保留。 - -你可以透過設定 `--tempdir` 指令參數來指定備份檔案目錄,或者是設定 `TMPDIR` 環境變數來達到此功能。 - -## 還原指令 (`restore`) - -持續更新中: 此文件尚未完成. - -例: - -```sh -unzip gitea-dump-1610949662.zip -cd gitea-dump-1610949662 -mv data/conf/app.ini /etc/gitea/conf/app.ini -mv data/* /var/lib/gitea/data/ -mv log/* /var/lib/gitea/log/ -mv repos/* /var/lib/gitea/repositories/ -chown -R gitea:gitea /etc/gitea/conf/app.ini /var/lib/gitea - -# mysql -mysql --default-character-set=utf8mb4 -u$USER -p$PASS $DATABASE ### Labels -Starting with Gitea 1.19, you can add a file that follows the [YAML label format](https://github.com/go-gitea/gitea/blob/main/options/label/Advanced.yaml) to `$GITEA_CUSTOM/options/label`: +Starting with Gitea 1.19, you can add a file that follows the [YAML label format](https://github.com/go-gitea/gitea/blob/main/options/label/Advanced.yaml) in the directory `$GITEA_CUSTOM/options/label/`: ```yaml labels: @@ -330,7 +330,7 @@ For more information, see the [labels documentation](usage/labels.md). ### Licenses -To add a custom license, add a file with the license text to `$GITEA_CUSTOM/options/license` +To add a custom license, add a file with the license text in the directory `$GITEA_CUSTOM/options/license/` ### Locales diff --git a/docs/content/administration/customizing-gitea.zh-cn.md b/docs/content/administration/customizing-gitea.zh-cn.md index 2babf03da7..4c4efb9590 100644 --- a/docs/content/administration/customizing-gitea.zh-cn.md +++ b/docs/content/administration/customizing-gitea.zh-cn.md @@ -42,11 +42,11 @@ Gitea 引用 `custom` 目录中的自定义配置文件来覆盖配置、模板 将自定义的公共文件(比如页面和图片)作为 webroot 放在 `custom/public/` 中来让 Gitea 提供这些自定义内容(符号链接将被追踪)。 -举例说明:`image.png` 存放在 `custom/public/`中,那么它可以通过链接 http://gitea.domain.tld/assets/image.png 访问。 +举例说明:`image.png` 存放在 `custom/public/assets/`中,那么它可以通过链接 http://gitea.domain.tld/assets/image.png 访问。 ## 修改默认头像 -替换以下目录中的 png 图片: `custom/public/img/avatar\_default.png` +替换以下目录中的 png 图片: `custom/public/assets/img/avatar\_default.png` ## 自定义 Gitea 页面 diff --git a/docs/content/administration/email-setup.en-us.md b/docs/content/administration/email-setup.en-us.md index 645a7a3f43..f9621e6075 100644 --- a/docs/content/administration/email-setup.en-us.md +++ b/docs/content/administration/email-setup.en-us.md @@ -61,7 +61,7 @@ Please note: authentication is only supported when the SMTP server communication - STARTTLS (also known as Opportunistic TLS) via port 587. Initial connection is done over cleartext, but then be upgraded over TLS if the server supports it. - SMTPS connection (SMTP over TLS) via the default port 465. Connection to the server use TLS from the beginning. -- Forced SMTPS connection with `IS_TLS_ENABLED=true`. (These are both known as Implicit TLS.) +- Forced SMTPS connection with `PROTOCOL=smtps`. (These are both known as Implicit TLS.) This is due to protections imposed by the Go internal libraries against STRIPTLS attacks. Note that Implicit TLS is recommended by [RFC8314](https://tools.ietf.org/html/rfc8314#section-3) since 2018. diff --git a/docs/content/administration/email-setup.zh-cn.md b/docs/content/administration/email-setup.zh-cn.md index 0a7ac3378f..2e670be85b 100644 --- a/docs/content/administration/email-setup.zh-cn.md +++ b/docs/content/administration/email-setup.zh-cn.md @@ -55,13 +55,13 @@ PASSWD = `password` 要发送测试邮件以验证设置,请转到 Gitea > 站点管理 > 配置 > SMTP 邮件配置。 -有关所有选项的完整列表,请查看[配置速查表](doc/administration/config-cheat-sheet.zh-cn.md)。 +有关所有选项的完整列表,请查看[配置速查表](administration/config-cheat-sheet.md)。 请注意:只有在使用 TLS 或 `HOST=localhost` 加密 SMTP 服务器通信时才支持身份验证。TLS 加密可以通过以下方式进行: - 通过端口 587 的 STARTTLS(也称为 Opportunistic TLS)。初始连接是明文的,但如果服务器支持,则可以升级为 TLS。 - 通过默认端口 465 的 SMTPS 连接。连接到服务器从一开始就使用 TLS。 -- 使用 `IS_TLS_ENABLED=true` 进行强制的 SMTPS 连接。(这两种方式都被称为 Implicit TLS) +- 使用 `PROTOCOL=smtps` 进行强制的 SMTPS 连接。(这两种方式都被称为 Implicit TLS) 这是由于 Go 内部库对 STRIPTLS 攻击的保护机制。 请注意,自2018年起,[RFC8314](https://tools.ietf.org/html/rfc8314#section-3) 推荐使用 Implicit TLS。 diff --git a/docs/content/administration/external-renderers.zh-cn.md b/docs/content/administration/external-renderers.zh-cn.md index 0b53b45277..fdf7315d7b 100644 --- a/docs/content/administration/external-renderers.zh-cn.md +++ b/docs/content/administration/external-renderers.zh-cn.md @@ -194,7 +194,7 @@ ALLOW_DATA_URI_IMAGES = true } ``` -将您的样式表添加到自定义目录中,例如 `custom/public/css/my-style-XXXXX.css`,并使用自定义的头文件 `custom/templates/custom/header.tmpl` 进行导入: +将您的样式表添加到自定义目录中,例如 `custom/public/assets/css/my-style-XXXXX.css`,并使用自定义的头文件 `custom/templates/custom/header.tmpl` 进行导入: ```html diff --git a/docs/content/administration/https-support.zh-cn.md b/docs/content/administration/https-support.zh-cn.md index add7906cc6..8beb06e80f 100644 --- a/docs/content/administration/https-support.zh-cn.md +++ b/docs/content/administration/https-support.zh-cn.md @@ -33,7 +33,7 @@ CERT_FILE = cert.pem KEY_FILE = key.pem ``` -请注意,如果您的证书由第三方证书颁发机构签名(即不是自签名的),则 cert.pem 应包含证书链。服务器证书必须是 cert.pem 中的第一个条目,后跟中介(如果有)。不必包含根证书,因为连接客户端必须已经拥有根证书才能建立信任关系。要了解有关配置值的更多信息,请查看 [配置备忘单](../config-cheat-sheet#server-server)。 +请注意,如果您的证书由第三方证书颁发机构签名(即不是自签名的),则 cert.pem 应包含证书链。服务器证书必须是 cert.pem 中的第一个条目,后跟中介(如果有)。不必包含根证书,因为连接客户端必须已经拥有根证书才能建立信任关系。要了解有关配置值的更多信息,请查看 [配置备忘单](administration/config-cheat-sheet#server-server)。 对于“CERT_FILE”或“KEY_FILE”字段,当文件路径是相对路径时,文件路径相对于“GITEA_CUSTOM”环境变量。它也可以是绝对路径。 diff --git a/docs/content/contributing.fr-fr.md b/docs/content/contributing.fr-fr.md deleted file mode 100644 index 9dc4aa6ca9..0000000000 --- a/docs/content/contributing.fr-fr.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2021-01-22T00:00:00+02:00" -title: "Übersetzung" -slug: "contributing" -sidebar_position: 35 -toc: false -draft: false -menu: - sidebar: - name: "Übersetzung" - sidebar_position: 50 - identifier: "contributing" ---- diff --git a/docs/content/contributing.zh-tw.md b/docs/content/contributing.zh-tw.md deleted file mode 100644 index 0c715eefe4..0000000000 --- a/docs/content/contributing.zh-tw.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2021-01-22T00:00:00+02:00" -title: "貢獻" -slug: "contributing" -sidebar_position: 35 -toc: false -draft: false -menu: - sidebar: - name: "貢獻" - sidebar_position: 50 - identifier: "contributing" ---- diff --git a/docs/content/contributing/_index.de-de.md b/docs/content/contributing/_index.de-de.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/contributing/_index.zh-tw.md b/docs/content/contributing/_index.zh-tw.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/contributing/guidelines-frontend.en-us.md b/docs/content/contributing/guidelines-frontend.en-us.md index 0d9e510e70..aa1759d9c9 100644 --- a/docs/content/contributing/guidelines-frontend.en-us.md +++ b/docs/content/contributing/guidelines-frontend.en-us.md @@ -48,11 +48,12 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h 10. Avoid mixing different events in one event listener, prefer to use individual event listeners for every event. 11. Custom event names are recommended to use `ce-` prefix. 12. Gitea's tailwind-style CSS classes use `gt-` prefix (`gt-relative`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`). +13. Avoid inline scripts & styles as much as possible, it's recommended to put JS code into JS files and use CSS classes. If inline scripts & styles are unavoidable, explain the reason why it can't be avoided. ### Accessibility / ARIA In history, Gitea heavily uses Fomantic UI which is not an accessibility-friendly framework. -Gitea uses some patches to make Fomantic UI more accessible (see the `aria.js` and `aria.md`), +Gitea uses some patches to make Fomantic UI more accessible (see `aria.md` and related JS files), but there are still many problems which need a lot of work and time to fix. ### Framework Usage diff --git a/docs/content/contributing/localization.zh-tw.md b/docs/content/contributing/localization.zh-tw.md deleted file mode 100644 index b045d26be2..0000000000 --- a/docs/content/contributing/localization.zh-tw.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "在地化" -slug: "localization" -sidebar_position: 70 -toc: false -draft: false -aliases: - - /zh-tw/localization -menu: - sidebar: - parent: "contributing" - name: "在地化" - sidebar_position: 70 - identifier: "localization" ---- - -# 在地化 - -我們在 [Crowdin 專案](https://crowdin.com/project/gitea)上進行在地化工作。 - -**英語系**的翻譯,可在修改[英文語言檔](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini)後提出合併請求。 - -**非英語系**的翻譯,請前往上述的 Crowdin 專案。 - -## 已支援的語言 - -上述 Crowdin 專案中列出的語言在翻譯超過 25% 後將被支援。 - -翻譯被認可後將在下次 Crowdin 同步後進入到主儲存庫,通常是在任何合併請求被合併之後。 - -這表示更改的翻譯要到下次 Gitea 發佈後才會出現。 - -如果您使用的是最新建置,它將會在同步完成、您更新後出現。 diff --git a/docs/content/development.zh-tw.md b/docs/content/development.zh-tw.md deleted file mode 100644 index 2c9d335fda..0000000000 --- a/docs/content/development.zh-tw.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "開發" -slug: "development" -sidebar_position: 40 -toc: false -draft: false -menu: - sidebar: - name: "開發" - sidebar_position: 40 - identifier: "development" ---- diff --git a/docs/content/development/_index.zh-tw.md b/docs/content/development/_index.zh-tw.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/development/api-usage.en-us.md b/docs/content/development/api-usage.en-us.md index 465f4d380c..94dac70b88 100644 --- a/docs/content/development/api-usage.en-us.md +++ b/docs/content/development/api-usage.en-us.md @@ -19,10 +19,7 @@ menu: ## Enabling/configuring API access -By default, `ENABLE_SWAGGER` is true, and -`MAX_RESPONSE_ITEMS` is set to 50. See [Config Cheat -Sheet](administration/config-cheat-sheet.md) for more -information. +By default, `ENABLE_SWAGGER` is true, and `MAX_RESPONSE_ITEMS` is set to 50. See [Config Cheat Sheet](administration/config-cheat-sheet.md) for more information. ## Authentication diff --git a/docs/content/development/api-usage.zh-cn.md b/docs/content/development/api-usage.zh-cn.md index c7eeceeb7d..96c1997294 100644 --- a/docs/content/development/api-usage.zh-cn.md +++ b/docs/content/development/api-usage.zh-cn.md @@ -19,8 +19,7 @@ menu: ## 开启/配置 API 访问 -通常情况下, `ENABLE_SWAGGER` 默认开启并且参数 `MAX_RESPONSE_ITEMS` 默认为 50。您可以从 [Config Cheat -Sheet](administration/config-cheat-sheet.md) 中获取更多配置相关信息。 +通常情况下, `ENABLE_SWAGGER` 默认开启并且参数 `MAX_RESPONSE_ITEMS` 默认为 50。您可以从 [Config Cheat Sheet](administration/config-cheat-sheet.md) 中获取更多配置相关信息。 ## 通过 API 认证 diff --git a/docs/content/development/hacking-on-gitea.en-us.md b/docs/content/development/hacking-on-gitea.en-us.md index 1eaa3d62b8..1289d846b2 100644 --- a/docs/content/development/hacking-on-gitea.en-us.md +++ b/docs/content/development/hacking-on-gitea.en-us.md @@ -243,10 +243,10 @@ documentation using: make generate-swagger ``` -You should validate your generated Swagger file and spell-check it with: +You should validate your generated Swagger file: ```bash -make swagger-validate misspell-check +make swagger-validate ``` You should commit the changed swagger JSON file. The continuous integration diff --git a/docs/content/development/hacking-on-gitea.zh-cn.md b/docs/content/development/hacking-on-gitea.zh-cn.md index 8e32c4c244..2888bd8318 100644 --- a/docs/content/development/hacking-on-gitea.zh-cn.md +++ b/docs/content/development/hacking-on-gitea.zh-cn.md @@ -228,10 +228,10 @@ Gitea Logo的 PNG 和 SVG 版本是使用 `TAGS="gitea" make generate-images` make generate-swagger ``` -您应该验证生成的 Swagger 文件并使用以下命令对其进行拼写检查: +您应该验证生成的 Swagger 文件: ```bash -make swagger-validate misspell-check +make swagger-validate ``` 您应该提交更改后的 swagger JSON 文件。持续集成服务器将使用以下方法检查是否已完成: diff --git a/docs/content/development/integrations.zh-tw.md b/docs/content/development/integrations.zh-tw.md deleted file mode 100644 index 9a6afad4ff..0000000000 --- a/docs/content/development/integrations.zh-tw.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -date: "2019-04-15T17:29:00+08:00" -title: "整合" -slug: "integrations" -sidebar_position: 65 -toc: false -draft: false -aliases: - - /zh-tw/integrations -menu: - sidebar: - parent: "development" - name: "整合" - sidebar_position: 65 - identifier: "integrations" ---- - -# 整合 - -Gitea 有著很棒的第三方整合社群, 以及其它有著一流支援的專案。 - -我們持續的整理一份清單以追蹤他們!請到 [awesome-gitea](https://gitea.com/gitea/awesome-gitea) 查看。 - -如果您正在找尋有關 [CI/CD](https://gitea.com/gitea/awesome-gitea#user-content-devops)、[SDK](https://gitea.com/gitea/awesome-gitea#user-content-sdk) 或是其它佈景主題,您可以在存儲庫 [awesome-gitea](https://gitea.com/gitea/awesome-gitea) 找到他們。 diff --git a/docs/content/development/migrations.zh-tw.md b/docs/content/development/migrations.zh-tw.md deleted file mode 100644 index 1dc222f379..0000000000 --- a/docs/content/development/migrations.zh-tw.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -date: "2019-04-15T17:29:00+08:00" -title: "遷移介面" -slug: "migrations-interfaces" -sidebar_position: 55 -toc: false -draft: false -aliases: - - /zh-tw/migrations-interfaces -menu: - sidebar: - parent: "development" - name: "遷移介面" - sidebar_position: 55 - identifier: "migrations-interfaces" ---- - -# 遷移功能 - -完整的遷移從 Gitea 1.9.0 開始提供。它定義了兩個介面用來從其它 Git 託管平臺遷移儲存庫資料到 Gitea,未來或許會提供遷移到其它 git 託管平臺。 -目前已實作了從 Github, Gitlab 和其它 Gitea 遷移資料。 - -Gitea 定義了一些基本物件於套件 [modules/migration](https://github.com/go-gitea/gitea/tree/master/modules/migration)。 -分別是 `Repository`, `Milestone`, `Release`, `ReleaseAsset`, `Label`, `Issue`, `Comment`, `PullRequest`, `Reaction`, `Review`, `ReviewComment`。 - -## Downloader 介面 - -從新的 Git 託管平臺遷移,有兩個新的步驟。 - -- 您必須實作一個 `Downloader`,它用來取得儲存庫資訊。 -- 您必須實作一個 `DownloaderFactory`,它用來偵測 URL 是否符合並建立上述的 `Downloader`。 - - 您需要在 `init()` 透過 `RegisterDownloaderFactory` 來註冊 `DownloaderFactory`。 - -您可以在 [downloader.go](https://github.com/go-gitea/gitea/blob/main/modules/migration/downloader.go) 中找到這些介面。 - -## Uploader 介面 - -目前只有 `GiteaLocalUploader` 被實作出來,所以我們只能通過 `Uploader` 儲存已下載的資料到本地的 Gitea 實例。 -目前尚未支援其它 Uploader。 - -您可以在 [uploader.go](https://github.com/go-gitea/gitea/blob/main/modules/migration/uploader.go) 中找到這些介面。 diff --git a/docs/content/development/oauth2-provider.zh-tw.md b/docs/content/development/oauth2-provider.zh-tw.md deleted file mode 100644 index dd9f04f03c..0000000000 --- a/docs/content/development/oauth2-provider.zh-tw.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -date: "2019-04-19:44:00+01:00" -title: "OAuth2 提供者" -slug: "oauth2-provider" -sidebar_position: 41 -toc: false -draft: false -aliases: - - /zh-tw/oauth2-provider -menu: - sidebar: - parent: "development" - name: "OAuth2 提供者" - sidebar_position: 41 - identifier: "oauth2-provider" ---- - -# OAuth2 提供者 - -**目錄** - -Gitea 支援作為 OAuth2 提供者,能讓第三方程式能在使用者同意下存取 Gitea 的資源。此功能自 1.8.0 版開始提供。 - -## Endpoint - -| Endpoint | URL | -| ---------------------- | --------------------------- | -| Authorization Endpoint | `/login/oauth/authorize` | -| Access Token Endpoint | `/login/oauth/access_token` | - -## 支援的 OAuth2 Grant - -目前 Gitea 只支援 [**Authorization Code Grant**](https://tools.ietf.org/html/rfc6749#section-1.3.1) 標準並額外支援下列擴充標準: - -- [Proof Key for Code Exchange (PKCE)](https://tools.ietf.org/html/rfc7636) -- [OpenID Connect (OIDC)](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth) - -若想要讓第三方程式使用 Authorization Code Grant,需先在「設定」(`/user/settings/applications`)中註冊一個新的應用程式。 - -## Scope - -目前 Gitea 尚未支援 scope (參見 [#4300](https://github.com/go-gitea/gitea/issues/4300)),所有的第三方程式都可獲得該使用者及他所屬的組織中所有資源的存取權。 - -## 範例 - -**備註:** 此範例未使用 PKCE。 - -1. 重新導向使用者到 authorization endpoint 以獲得他同意授權存取資源: - - - ```curl - https://[YOUR-GITEA-URL]/login/oauth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI& response_type=code&state=STATE - ``` - - 在設定中註冊應用程式以獲得 `CLIENT_ID`。`STATE` 是一個隨機的字串,它將在使用者授權後發送回您的應用程式。`state` 參數是選用的,但應該要用它來防止 CSRF 攻擊。 - - ![Authorization Page](/authorize.png) - - 使用者將會被詢問是否授權給您的應用程式。如果它們同意了,使用者將被重新導向到 `REDIRECT_URL`,例如: - - ```curl - https://[REDIRECT_URI]?code=RETURNED_CODE&state=STATE - ``` - -1. 使用重新導向提供的 `code`,您可以要求一個新的應用程式和 Refresh Token。Access Token Endpoint 接受 POST 請求使用 `application/json` 或 `application/x-www-form-urlencoded` 類型的請求內容,例如: - - ```curl - POST https://[YOUR-GITEA-URL]/login/oauth/access_token - ``` - - ```json - { - "client_id": "YOUR_CLIENT_ID", - "client_secret": "YOUR_CLIENT_SECRET", - "code": "RETURNED_CODE", - "grant_type": "authorization_code", - "redirect_uri": "REDIRECT_URI" - } - ``` - - 回應: - - ```json - { - "access_token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJnbnQiOjIsInR0IjowLCJleHAiOjE1NTUxNzk5MTIsImlhdCI6MTU1NTE3NjMxMn0.0-iFsAwBtxuckA0sNZ6QpBQmywVPz129u75vOM7wPJecw5wqGyBkmstfJHAjEOqrAf_V5Z-1QYeCh_Cz4RiKug", - "token_type": "bearer", - "expires_in": 3600, - "refresh_token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJnbnQiOjIsInR0IjoxLCJjbnQiOjEsImV4cCI6MTU1NzgwNDMxMiwiaWF0IjoxNTU1MTc2MzEyfQ.S_HZQBy4q9r5SEzNGNIoFClT43HPNDbUdHH-GYNYYdkRfft6XptJBkUQscZsGxOW975Yk6RbgtGvq1nkEcklOw" - } - ``` - - `CLIENT_SECRET` 是產生給此應用程式的唯一密鑰。請記住該密鑰只會在您於 Gitea 建立/註冊應用程式時出現一次。若您遺失密鑰,您必須在該應用程式的設定中重新產生密鑰。 - - `access_token` 請求中的 `REDIRECT_URI` 必須符合 `authorize` 請求中的 `REDIRECT_URI`。 - -1. 發送 [API requests](development/api-usage.md#oauth2-provider) 時使用 `access_token` 以存取使用者的資源。 diff --git a/docs/content/help.fr-fr.md b/docs/content/help.fr-fr.md deleted file mode 100644 index cb41ad6487..0000000000 --- a/docs/content/help.fr-fr.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2017-01-20T15:00:00+08:00" -title: "Aide" -slug: "help" -sidebar_position: 5 -toc: false -draft: false -menu: - sidebar: - name: "Aide" - sidebar_position: 100 - identifier: "help" ---- diff --git a/docs/content/help.zh-tw.md b/docs/content/help.zh-tw.md deleted file mode 100644 index 840ea100e8..0000000000 --- a/docs/content/help.zh-tw.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2017-01-20T15:00:00+08:00" -title: "幫助" -slug: "help" -sidebar_position: 5 -toc: false -draft: false -menu: - sidebar: - name: "幫助" - sidebar_position: 100 - identifier: "help" ---- diff --git a/docs/content/help/_index.zh-tw.md b/docs/content/help/_index.zh-tw.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/help/faq.en-us.md b/docs/content/help/faq.en-us.md index c83f8549e2..a7031d082c 100644 --- a/docs/content/help/faq.en-us.md +++ b/docs/content/help/faq.en-us.md @@ -138,9 +138,9 @@ All Gitea instances have the built-in API and there is no way to disable it comp You can, however, disable showing its documentation by setting `ENABLE_SWAGGER` to `false` in the `api` section of your `app.ini`. For more information, refer to Gitea's [API docs](development/api-usage.md). -You can see the latest API (for example) on . +You can see the latest API (for example) on https://try.gitea.io/api/swagger -You can also see an example of the `swagger.json` file at . +You can also see an example of the `swagger.json` file at https://try.gitea.io/swagger.v1.json ## Adjusting your server for public/private use @@ -222,9 +222,11 @@ Our translations are currently crowd-sourced on our [Crowdin project](https://cr Whether you want to change a translation or add a new one, it will need to be there as all translations are overwritten in our CI via the Crowdin integration. -## Push Hook / Webhook aren't running +## Push Hook / Webhook / Actions aren't running -If you can push but can't see push activities on the home dashboard, or the push doesn't trigger webhook, there are a few possibilities: +If you can push but can't see push activities on the home dashboard, or the push doesn't trigger webhook and Actions workflows, it's likely that the git hooks are not working. + +There are a few possibilities: 1. The git hooks are out of sync: run "Resynchronize pre-receive, update and post-receive hooks of all repositories" on the site admin panel 2. The git repositories (and hooks) are stored on some filesystems (ex: mounted by NAS) which don't support script execution, make sure the filesystem supports `chmod a+x any-script` diff --git a/docs/content/help/faq.zh-cn.md b/docs/content/help/faq.zh-cn.md index 36610db8e0..cb8afcbc60 100644 --- a/docs/content/help/faq.zh-cn.md +++ b/docs/content/help/faq.zh-cn.md @@ -142,9 +142,9 @@ Gitea不提供内置的Pages服务器。您需要一个专用的域名来提供 但是,您可以在app.ini的api部分将ENABLE_SWAGGER设置为false,以禁用其文档显示。 有关更多信息,请参阅Gitea的[API文档](development/api-usage.md)。 -您可以在上查看最新的API(例如)。 +您可以在上查看最新的API(例如)https://try.gitea.io/api/swagger -您还可以在上查看`swagger.json`文件的示例 。 +您还可以在上查看`swagger.json`文件的示例 https://try.gitea.io/swagger.v1.json ## 调整服务器用于公共/私有使用 @@ -190,7 +190,7 @@ Gitea 目前支持三个官方主题,分别是 `gitea`(亮色)、`arc-gree 假设我们的主题是 `arc-blue`(这是一个真实的主题,可以在[此问题](https://github.com/go-gitea/gitea/issues/6011)中找到) -将`.css`文件命名为`theme-arc-blue.css`并将其添加到`custom/public/css`文件夹中 +将`.css`文件命名为`theme-arc-blue.css`并将其添加到`custom/public/assets/css`文件夹中 通过将`arc-blue`添加到`app.ini`中的`THEMES`列表中,允许用户使用该主题 @@ -226,9 +226,11 @@ Gitea还提供了自己的SSH服务器,用于在SSHD不可用时使用。 无论您想要更改翻译还是添加新的翻译,都需要在Crowdin集成中进行,因为所有翻译都会被CI覆盖。 -## 推送钩子/ Webhook未运行 +## 推送钩子/ Webhook / Actions 未运行 -如果您可以推送但无法在主页仪表板上看到推送活动,或者推送不触发Webhook,有几种可能性: +如果您可以推送但无法在主页仪表板上看到推送活动,或者推送不触发 Webhook 和 Actions,可能是 git 钩子不工作而导致的。 + +这可能是由于以下原因: 1. Git钩子不同步:在站点管理面板上运行“重新同步所有仓库的pre-receive、update和post-receive钩子” 2. Git仓库(和钩子)存储在一些不支持脚本执行的文件系统上(例如由NAS挂载),请确保文件系统支持`chmod a+x any-script` diff --git a/docs/content/help/support.zh-tw.md b/docs/content/help/support.zh-tw.md deleted file mode 100644 index 511b362417..0000000000 --- a/docs/content/help/support.zh-tw.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -date: "2018-05-21T15:00:00+00:00" -title: "取得協助" -slug: "support" -sidebar_position: 20 -toc: false -draft: false -aliases: - - /zh-tw/seek-help -menu: - sidebar: - parent: "help" - name: "取得協助" - sidebar_position: 20 - identifier: "support" ---- - -# 取得協助 - -- [Discord 聊天室](https://discord.gg/Gitea) -- [Discourse 討論區](https://discourse.gitea.io/) - -**備註:** 尋求支援時,若能先備妥下列資訊將可讓提供協助的人能快速地分析您的問題: - -1. 您的 `app.ini` (必要時清除掉任何機密資訊) -2. `gitea.log` (以及任何有關的日誌檔案) - - 例:如果錯誤和資料庫相關,提供 `xorm.log` 可能會有幫助 -3. 您看到的任何錯誤訊息 -4. 儘可能地在 [try.gitea.io](https://try.gitea.io) 觸發您的問題並記下步驟,以便其他人能重現那個問題。 - - 這將讓我們更有機會快速地找出問題的根源並解決它。 -5. 堆棧跟踪,[請參考英文文檔](https://docs.gitea.com/help/support) - -## 錯誤回報 - -如果您發現錯誤,請到 [GitHub 的 Issue](https://github.com/go-gitea/gitea/issues) 回報。 diff --git a/docs/content/index.de-de.md b/docs/content/index.de-de.md deleted file mode 100644 index 7f0f611c25..0000000000 --- a/docs/content/index.de-de.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -date: "2023-01-07T22:03:00+01:00" -title: "Dokumentation" -slug: / -sidebar_position: 10 -toc: false -draft: false ---- - -# Was ist Gitea? - -Gitea ist ein einfacher, selbst gehosteter Git-Service. Änlich wie GitHub, Bitbucket oder GitLab. - -Gitea ist ein [Gogs](http://gogs.io)-Fork. - -## Ziele - -* Einfach zu installieren -* Plattformübergreifend -* Leichtgewichtig -* Quelloffen - -## System Voraussetzungen - -* Ein Raspberry Pi 3 ist leistungsstark genug, um Gitea für kleine Belastungen laufen zu lassen. -* 2 CPU Kerne und 1GB RAM sind für kleine Teams/Projekte ausreichend. -* Gitea sollte unter einem seperaten nicht-root Account auf UNIX-Systemen ausgeführt werden. - * Achtung: Gitea verwaltet die `~/.ssh/authorized_keys` Datei. Gitea unter einem normalen Benutzer auszuführen könnte dazu führen, dass dieser sich nicht mehr anmelden kann. -* [Git](https://git-scm.com/) Version 2.0 oder aktueller wird benötigt. - * Wenn Git >= 2.1.2 und [Git LFS](https://git-lfs.github.com/) vorhanden ist, dann wird Git LFS Support automatisch für Gitea aktiviert. - * Wenn Git >= 2.18, dann wird das Rendern von Commit-Graphen automatisch aktiviert. - -## Browser Unterstützung - -* Die neuesten zwei Versionen von Chrome, Firefox, Safari und Edge -* Firefox ESR diff --git a/docs/content/index.fr-fr.md b/docs/content/index.fr-fr.md deleted file mode 100755 index 60a9dfb6a3..0000000000 --- a/docs/content/index.fr-fr.md +++ /dev/null @@ -1,273 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Documentation" -slug: / -sidebar_position: 10 -toc: false -draft: false ---- - -# A propos de Gitea - -Gitea est un service Git auto-hébergé très simple à installer et à utiliser. Il est similaire à GitHub, Bitbucket ou Gitlab. Le développement initial provient sur [Gogs] (http://gogs.io), mais nous l'avons forké puis renommé Gitea. Si vous souhaitez en savoir plus sur les raisons pour lesquelles nous avons fait cela, lisez [cette publication] (https://blog.gitea.com/welcome-to-gitea/) sur le blog. - -## Objectif - -Le but de ce projet est de fournir de la manière la plus simple, la plus rapide et sans complication un service Git auto-hébergé. Grâce à Go, cela peut se faire via un binaire indépendant fonctionnant sur toutes les plateformes que Go prend en charge, y compris Linux, macOS et Windows, même sur des architectures comme ARM ou PowerPC. - -## Fonctionalités - -- Tableau de bord de l'utilisateur - - Choix du contexte (organisation ou utilisateur actuel) - - Chronologie de l'activité - - Révisions (_Commits_) - - Tickets - - Demande d'ajout (_Pull request_) - - Création de dépôts - - Liste des dépôts - - Liste de vos organisations - - Liste des dépôts miroires -- Tableau de bord des tickets - - Choix du contexte (organisation ou utilisateur actuel) - - Filtres - - Ouvert - - Fermé - - Vos dépôts - - Tickets assignés - - Vos tickets - - Dépôts - - Options de tri - - Plus vieux - - Dernière mise à jour - - Nombre de commentaires -- Tableau de bord des demandes d'ajout - - Identique au tableau de bord des tickets -- Types de dépôt - - Miroire - - Normal - - Migré -- Notifications (courriel et web) - - Lu - - Non lu - - Épinglé -- Page d'exploration - - Utilisateurs - - Dépôts - - Organisations - - Moteur de recherche -- Interface personnalisables -- Fichiers publiques remplaçables (logo, css, etc) -- Protection CSRF et XSS -- Support d'HTTPS -- Configuration des types et de la taille maximale des fichiers téléversés -- Journalisation (_Log_) -- Configuration - - Base de données - - MySQL - - PostgreSQL - - SQLite3 - - MSSQL - - [TiDB](https://github.com/pingcap/tidb) (MySQL protocol) - - Fichier de configuration - - Voir [ici](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini) - - Panel d'administration - - Statistiques - - Actions - - Suppression des comptes inactifs - - Suppression des dépôts archivés - - Suppression des dépôts pour lesquels il manque leurs fichiers - - Exécution du _garbage collector_ sur les dépôts - - Ré-écriture des clefs SSH - - Resynchronisation des hooks - - Recreation des dépôts manquants - - Status du server - - Temps de disponibilité - - Mémoire - - Nombre de goroutines - - et bien plus... - - Gestion des utilisateurs - - Recherche - - Tri - - Dernière connexion - - Méthode d'authentification - - Nombre maximum de dépôts - - Désactivation du compte - - Permissions d'administration - - Permission pour crééer des hooks - - Permission pour crééer des organisations - - Permission pour importer des dépôts - - Gestion des organisations - - Membres - - Équipes - - Avatar - - Hooks - - Gestion des depôts - - Voir toutes les informations pour un dépôt donné et gérer tous les dépôts - - Méthodes d'authentification - - OAuth - - PAM - - LDAP - - SMTP - - Visualisation de la configuration - - Tout ce que contient le fichier de configuration - - Alertes du système - - Quand quelque chose d'inattendu survient - - Surveillance - - Processus courrants - - Tâches CRON - - Mise à jour des dépôts miroires - - Vérification de l'état des dépôts - - Vérification des statistiques des dépôts - - Nettoyage des anciennes archives - - Variables d'environement - - Options de ligne de commande -- Internationalisation ([21 langues](https://github.com/go-gitea/gitea/tree/master/options/locale)) -- Courriel - - Notifications - - Confirmation d'inscription - - Ré-initialisation du mot de passe -- Support de _reverse proxy_ - - _subpaths_ inclus -- Utilisateurs - - Profil - - Nom - - Prénom - - Courriel - - Site internet - - Date de création - - Abonnés et abonnements - - Organisations - - Dépôts - - Activité - - Dépôts suivis - - Paramètres - - Identiques au profil avec en plus les éléments ci-dessous - - Rendre l'adresse de courriel privée - - Avatar - - Gravatar - - Libravatar - - Personnalisé - - Mot de passe - - Courriels multiples - - Clefs SSH - - Applications connectées - - Authentification à double facteurs - - Identités OAuth2 attachées - - Suppression du compte -- Dépôts - - Clone à partir de SSH / HTTP / HTTPS - - Git LFS - - Suivre, Voter, Fork - - Voir les personnes qui suivent, les votes et les forks - - Code - - Navigation entre les branches - - Création ou téléversement de fichier depuis le navigateur - - URLs pour clôner le dépôt - - Téléchargement - - ZIP - - TAR.GZ - - Édition en ligne - - Éditeur Markdown - - Éditeur de texte - - Coloration syntaxique - - Visualisation des Diffs - - Visualisation - - Possibilité de choisir où sauvegarder la révision - - Historiques des fichiers - - Suppression de fichiers - - Voir le fichier brut - - Tickets - - Modèle de ticket - - Jalons - - Étiquettes - - Affecter des tickets - - Filtres - - Ouvert - - Ferme - - Personne assignée - - Créer par vous - - Qui vous mentionne - - Tri - - Plus vieux - - Dernière mise à jour - - Nombre de commentaires - - Moteur de recherche - - Commentaires - - Joindre des fichiers - - Demande d’ajout (_Pull request_) - - Les mêmes fonctionnalités que pour les tickets - - Révisions (_Commits_) - - Representation graphique des révisions - - Révisions par branches - - Moteur de recherche - - Voir les différences - - Voir les numéro de révision SHA - - Voir l'auteur - - Naviguer dans les fichiers d'une révision donnée - - Publication - - Pièces jointes - - Titre - - Contenu - - Suppression - - Définir comme une pré-publication - - Choix de la branche - - Wiki - - Import - - Éditeur Markdown - - Paramètres - - Options - - Nom - - Description - - Privé / Publique - - Site internet - - Wiki - - Activé / Désactivé - - Interne / externe - - Tickets - - Activé / Désactivé - - Interne / externe - - URL personnalisable pour une meilleur intégration avec un gestionnaire de tickets externe - - Activer / désactiver les demandes d'ajout (_Pull request_) - - Transfert du dépôt - - Suppression du wiki - - Suppression du dépôt - - Collaboration - - Lecture / Écriture / Administration - - Branches - - Branche par défaut - - Protection - - Webhooks - - Git hooks - - Clefs de déploiement - -## Configuration requise - -- Un simple Raspberry Pi est assez puissant pour les fonctionnalités de base. -- Un processeur double coeurs et 1Gb de RAM est une bonne base pour une utilisation en équipe. -- Gitea est censé être exécuté avec un compte utilisateur dédié et non root, aucun autre mode de fonctionnement n'est pris en charge. (**NOTE**: Dans le cas où vous l'exécutez avec votre propre compte d'utilisateur et que le serveur SSH intégré est désactivé, Gitea modifie le fichier `~ /.ssh /authorized_keys` afin que vous ne soyez **plus capable** de vous connecter interactivement). - -## Navigateurs supportés - -- Chrome, Firefox, Safari, Edge - -## Composants - -- Framework web : [Chi](http://github.com/go-chi/chi) -- ORM: [XORM](https://xorm.io) -- Interface graphique : - - [jQuery](https://jquery.com) - - [Fomantic UI](https://fomantic-ui.com) - - [Vue3](https://vuejs.org) - - [CodeMirror](https://codemirror.net) - - [EasyMDE](https://github.com/Ionaru/easy-markdown-editor) - - [Monaco Editor](https://microsoft.github.io/monaco-editor) - - ... (package.json) -- Connecteurs de base de données : - - [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) - - [github.com/lib/pq](https://github.com/lib/pq) - - [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) - - [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) - -## Logiciels et services - -- [Drone](https://github.com/drone/drone) (Intégration continue) diff --git a/docs/content/index.zh-tw.md b/docs/content/index.zh-tw.md deleted file mode 100644 index 41a5e7e067..0000000000 --- a/docs/content/index.zh-tw.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -date: "2016-11-08T16:00:00+02:00" -title: "文件" -slug: / -sidebar_position: 10 -toc: false -draft: false ---- - -# 關於 Gitea - -Gitea 是一個可自行託管的 Git 服務。你可以拿 GitHub、Bitbucket 或 Gitlab 來比較看看。 -Gitea 是從 [Gogs](http://gogs.io) Fork 出來的,請閱讀部落格文章 [Gitea 公告](https://blog.gitea.com/welcome-to-gitea/)以了解我們 Fork 的理由。 - -## 目標 - -本專案的首要目標是建立一個容易安裝,執行快速,安装和使用體驗良好的自建 Git 服務。我們採用 GO 為後端語言,Go 可以產生各平台使用的執行檔。它支援 Linux、macOS 和 Windows 外,處理器架構包含 amd64、i386、ARM 和 PowerPC 等。 - -## 功能 - -- 代碼託管:Gitea 支援建立和管理存儲庫、瀏覽提交歷史和程式碼檔案、審查和合併程式碼提交、管理協作者、處理分支等。它還支援許多常見的 Git 功能,如標籤、Cherry-pick、鉤子、集成協作工具等。 - -- 輕量級和快速:Gitea 的設計目標之一就是輕量級和快速響應。與某些大型代碼託管平台不同,它保持了精簡,在速度方面表現出色,適用於資源有限的伺服器環境。由於其輕量級設計,Gitea 的資源消耗相對較低,在資源受限的環境中表現出色。 - -- 易於部署和維護:它可以輕鬆地部署在各種伺服器上,無需複雜的配置或依賴。這使得個人開發者或小團隊可以方便地設置和管理自己的 Git 服務。 - -- 安全性:Gitea 強調安全性,提供用戶權限管理、訪問控制列表等功能,確保程式碼和數據的安全性。 - -- 代碼審查:代碼審查同時支援拉取請求工作流和 AGit 工作流。審查者可以在線瀏覽程式碼並提供審查意見或反饋。提交者可以接收審查意見並在線回覆或修改程式碼。代碼審查可以幫助個人和組織提升程式碼質量。 - -- CI/CD:Gitea Actions 支援 CI/CD 功能,與 GitHub Actions 相容。用戶可以使用熟悉的 YAML 格式編寫工作流程,並重複使用各種現有的 Actions 插件。Actions 插件支援從任何 Git 網站下載。 - -- 專案管理:Gitea 通過看板和工單來追蹤一個專案的需求、功能和錯誤。工單支援分支、標籤、里程碑、指派、時間追蹤、到期日期、依賴關係等功能。 - -- 制品庫:Gitea 支援超過 20 種不同類型的公有或私有軟體包管理,包括:Cargo、Chef、Composer、Conan、Conda、Container、Helm、Maven、npm、NuGet、Pub、PyPI、RubyGems、Vagrant 等。 - -- 開源社區支援:Gitea 是一個基於 MIT 許可證的開源專案,擁有活躍的開源社區,能夠持續進行開發和改進,同時也積極接受社區貢獻,保持了平台的更新和創新。 - -- 多語言支援:Gitea 提供多種語言界面,適應全球範圍內的用戶,促進了國際化和本地化。 - -更多功能特性:詳見:https://docs.gitea.com/installation/comparison#general-features - -## 系統需求 - -- Raspberry Pi 3 的效能足夠讓 Gitea 承擔小型工作負載。 -- 雙核心 CPU 和 1GB 記憶體通常足以應付小型團隊/專案。 -- 在類 UNIX 系統上, 應該以專用的非 root 系統帳號來執行 Gitea。 - - 備註:Gitea 管理著 `~/.ssh/authorized_keys` 檔案。以一般身份使用者執行 Gitea 可能會破壞該使用者的登入能力。 - -- [Git](https://git-scm.com/) 的最低需求為 2.0 或更新版本。 - - 當 git 版本 >= 2.1.2 時,可啟用 Git [large file storage](https://git-lfs.github.com/)。 - - 當 git 版本 >= 2.18 時,將自動啟用 Git 提交線圖渲染。 - -## 瀏覽器支援 - -- 最近 2 個版本的 Chrome, Firefox, Safari, Edge -- Firefox ESR - -## 元件 - -- Web 框架: [Chi](http://github.com/go-chi/chi) -- ORM: [XORM](https://xorm.io) -- UI 元件: - - [jQuery](https://jquery.com) - - [Fomantic UI](https://fomantic-ui.com) - - [Vue3](https://vuejs.org) - - [CodeMirror](https://codemirror.net) - - [EasyMDE](https://github.com/Ionaru/easy-markdown-editor) - - [Monaco Editor](https://microsoft.github.io/monaco-editor) - - ... (package.json) -- 資料庫驅動程式: - - [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) - - [github.com/lib/pq](https://github.com/lib/pq) - - [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) - - [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) - -## 集成支持 - -請訪問 [Awesome Gitea](https://gitea.com/gitea/awesome-gitea/) 獲得更多的第三方集成支持 diff --git a/docs/content/installation.fr-fr.md b/docs/content/installation.fr-fr.md deleted file mode 100644 index 1466e03e18..0000000000 --- a/docs/content/installation.fr-fr.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Installation" -slug: "installation" -sidebar_position: 10 -toc: false -draft: false -menu: - sidebar: - name: "Installation" - sidebar_position: 10 - identifier: "installation" ---- diff --git a/docs/content/installation.zh-tw.md b/docs/content/installation.zh-tw.md deleted file mode 100644 index 48b0983f2a..0000000000 --- a/docs/content/installation.zh-tw.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "安裝" -slug: "installation" -sidebar_position: 10 -toc: false -draft: false -menu: - sidebar: - name: "安裝" - sidebar_position: 10 - identifier: "installation" ---- diff --git a/docs/content/installation/_index.fr-fr.md b/docs/content/installation/_index.fr-fr.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/installation/_index.zh-tw.md b/docs/content/installation/_index.zh-tw.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/installation/comparison.zh-tw.md b/docs/content/installation/comparison.zh-tw.md deleted file mode 100644 index 01a1035daf..0000000000 --- a/docs/content/installation/comparison.zh-tw.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -date: "2018-05-07T13:00:00+02:00" -title: "比較 Gitea 和其它自託管 Git 服務" -slug: "comparison" -sidebar_position: 5 -toc: false -draft: false -aliases: - - /zh-tw/comparison -menu: - sidebar: - parent: "installation" - name: "比較" - sidebar_position: 5 - identifier: "comparison" ---- - -# 比較 Gitea 和其它自託管 Git 服務 - -**目錄** - -為了幫助您判斷 Gitea 是否適合您的需求,這裡列出了它和其它自託管 Git 服務的比較。 - -請注意我們不會經常檢查其它產品的功能異動,所以這份清單可能過期,如果您在下方表格中找到需要更新的資料,請在 [GitHub 的 Issue](https://github.com/go-gitea/gitea/issues) 回報。 - -表格中使用的符號: - -- ✓ - 支援 - -- ⁄ - 有限度的支援 - -- ✘ - 不支援 - -- _⚙️ - 由第三方服務或外掛程式支援_ - -## 一般功能 - -| 功能 | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| ------------------------ | -------------------------------------------------- | ---- | --------- | --------- | --------- | --------- | ------------ | -| 免費及開放原始碼 | ✓ | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | -| 低資源使用 (RAM/CPU) | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | -| 支援多種資料庫 | ✓ | ✓ | ✘ | ⁄ | ⁄ | ✓ | ✓ | -| 支援多種作業系統 | ✓ | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | -| 簡單的升級程序 | ✓ | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ | -| 支援 Markdown | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 支援 Orgmode | ✓ | ✘ | ✓ | ✘ | ✘ | ✘ | ? | -| 支援 CSV | ✓ | ✘ | ✓ | ✘ | ✘ | ✓ | ? | -| 支援第三方渲染工具 | ✓ | ✘ | ✘ | ✘ | ✘ | ✓ | ? | -| Git 驅動的靜態頁面 | [⚙️][gitea-pages-server], [⚙️][gitea-caddy-plugin] | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Git 驅動的整合 wiki | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 部署 Token | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 有寫入權限的儲存庫 Token | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ | -| 內建 Container Registry | [✘](https://github.com/go-gitea/gitea/issues/2316) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 對外部 Git 鏡像 | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | -| FIDO (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 內建 CI/CD | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 子群組: 群組中的群組 | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ | - -## 程式碼管理 - -| 功能 | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| ----------------------------------------- | ------------------------------------------------ | ---- | --------- | --------- | --------- | --------- | ------------ | -| 儲存庫主題描述 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 儲存庫程式碼搜尋 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 全域程式碼搜尋 | ✓ | ✘ | ✓ | ✘ | ✓ | ✓ | ✓ | -| Git LFS 2.0 | ✓ | ✘ | ✓ | ✓ | ✓ | ⁄ | ✓ | -| 群組里程碑 | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 精細的使用者權限(程式碼, 問題, Wiki 等) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 驗證提交者 | ⁄ | ✘ | ? | ✓ | ✓ | ✓ | ✘ | -| GPG 簽署提交 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 拒絕未經簽署的提交 | [✓](https://github.com/go-gitea/gitea/pull/9708) | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ | -| 儲存庫動態頁 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 分支管理 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 建立新分支 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 網頁程式碼編輯器 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 提交線圖 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 儲存庫範本 | [✓](https://github.com/go-gitea/gitea/pull/8768) | ✘ | ✓ | ✘ | ✓ | ✓ | ✘ | - -## 問題追蹤器 - -| 功能 | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| -------------------- | -------------------------------------------------- | --------------------------------------------- | --------- | ----------------------------------------------------------------------- | --------- | --------- | ------------ | -| 問題追蹤器 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 問題範本 | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 標籤 | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 時間追蹤 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 指派問題給多個成員 | ✓ | ✘ | ✓ | ✘ | ✓ | ✘ | ✘ | -| 相關問題 | ✘ | ✘ | ⁄ | [✓](https://docs.gitlab.com/ce/user/project/issues/related_issues.html) | ✓ | ✘ | ✘ | -| 機密問題 | [✘](https://github.com/go-gitea/gitea/issues/3217) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 對留言的反應 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 鎖定對話 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 批次處理問題 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 問題看板(看板方法) | [✓](https://github.com/go-gitea/gitea/pull/8346) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 從問題建立新分支 | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 問題搜尋 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 全域問題搜尋 | [✘](https://github.com/go-gitea/gitea/issues/2434) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 問題相依 | ✓ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | -| 從電子郵件建立問題 | [✘](https://github.com/go-gitea/gitea/issues/6226) | [✘](https://github.com/gogs/gogs/issues/2602) | ✘ | ✓ | ✓ | ✓ | ✘ | -| 服務台 | [✘](https://github.com/go-gitea/gitea/issues/6219) | ✘ | ✘ | [✓](https://gitlab.com/groups/gitlab-org/-/epics/3103) | ✓ | ✘ | ✘ | - -## 拉取/合併請求 - -| 功能 | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| -------------------------- | -------------------------------------------------- | ---- | --------- | --------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------ | ------------ | -| 拉取/合併請求 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| Squash 合併 | ✓ | ✘ | ✓ | [✓](https://docs.gitlab.com/ce/user/project/merge_requests/squash_and_merge.html) | ✓ | ✓ | ✓ | -| Rebase 合併 | ✓ | ✓ | ✓ | ✘ | ⁄ | ✘ | ✓ | -| 拉取/合併請求的行內留言 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 拉取/合併請求的核可 | ✓ | ✘ | ⁄ | ✓ | ✓ | ✓ | ✓ | -| 解決合併衝突 | [✘](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 限制某些使用者的推送及合併 | ✓ | ✘ | ✓ | ⁄ | ✓ | ✓ | ✓ | -| 還原指定的提交或合併請求 | [✘](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 拉取/合併請求範本 | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | -| Cherry-picking 變更 | [✘](https://github.com/go-gitea/gitea/issues/5158) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | -| 下載 Patch | ✓ | ✘ | ✓ | ✓ | ✓ | [/](https://jira.atlassian.com/plugins/servlet/mobile#issue/BCLOUD-8323) | ✘ | - -## 第三方整合 - -| 功能 | Gitea | Gogs | GitHub EE | GitLab CE | GitLab EE | BitBucket | RhodeCode CE | -| ------------------------- | ------------------------------------------------ | ---- | --------- | --------- | --------- | --------- | ------------ | -| 支援 Webhook | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 自訂 Git Hook | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 整合 AD / LDAP | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| 支援多重 LDAP / AD 伺服器 | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | -| 同步 LDAP 使用者 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | -| SAML 2.0 service provider | [✘](https://github.com/go-gitea/gitea/issues/5512) | [✘](https://github.com/gogs/gogs/issues/1221) | ✓ | ✓ | ✓ | ✓ | ✘ | -| 支援 OpenId Connect | ✓ | ✘ | ✓ | ✓ | ✓ | ? | ✘ | -| 整合 OAuth 2.0 (外部驗證) | ✓ | ✘ | ⁄ | ✓ | ✓ | ? | ✓ | -| 成為 OAuth 2.0 提供者 | [✓](https://github.com/go-gitea/gitea/pull/5378) | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 兩步驟驗證 (2FA) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 整合 Mattermost/Slack | ✓ | ✓ | ⁄ | ✓ | ✓ | ⁄ | ✓ | -| 整合 Discord | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | -| 整合 Microsoft Teams | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | -| 顯示外部 CI/CD 狀態 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ | - -[gitea-caddy-plugin]: https://github.com/42wim/caddy-gitea -[gitea-pages-server]: https://codeberg.org/Codeberg/pages-server diff --git a/docs/content/installation/database-preparation.en-us.md b/docs/content/installation/database-preparation.en-us.md index 21360fa4d2..5e0b94665f 100644 --- a/docs/content/installation/database-preparation.en-us.md +++ b/docs/content/installation/database-preparation.en-us.md @@ -17,13 +17,15 @@ menu: # Database Preparation -You need a database to use Gitea. Gitea supports PostgreSQL (>=10), MySQL (>=5.7), SQLite, and MSSQL (>=2008R2 SP3). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. If you plan to use SQLite, you can ignore this chapter. +You need a database to use Gitea. Gitea supports PostgreSQL (>= 12), MySQL (>= 8.0), MariaDB (>= 10.4), SQLite (builtin), and MSSQL (>= 2012 SP4). This page will guide into preparing database. Only PostgreSQL and MySQL will be covered here since those database engines are widely-used in production. If you plan to use SQLite, you can ignore this chapter. + +If you use an unsupported database version, please [get in touch](/help/support) with us for information on our Extended Support Contracts. We can provide testing and support for older databases and integrate those fixes into the Gitea codebase. Database instance can be on same machine as Gitea (local database setup), or on different machine (remote database). Note: All steps below requires that the database engine of your choice is installed on your system. For remote database setup, install the server application on database instance and client program on your Gitea server. The client program is used to test connection to the database from Gitea server, while Gitea itself use database driver provided by Go to accomplish the same thing. In addition, make sure you use same engine version for both server and client for some engine features to work. For security reason, protect `root` (MySQL) or `postgres` (PostgreSQL) database superuser with secure password. The steps assumes that you run Linux for both database and Gitea servers. -## MySQL +## MySQL/MariaDB 1. For remote database setup, you will need to make MySQL listen to your IP address. Edit `bind-address` option on `/etc/mysql/my.cnf` on database instance to: @@ -45,7 +47,7 @@ Note: All steps below requires that the database engine of your choice is instal ```sql SET old_passwords=0; - CREATE USER 'gitea' IDENTIFIED BY 'gitea'; + CREATE USER 'gitea'@'%' IDENTIFIED BY 'gitea'; ``` For remote database: @@ -182,7 +184,7 @@ If the communication between Gitea and your database instance is performed throu - On the database server certificate, one of `Subject Alternative Name` or `Common Name` entries must be the fully-qualified domain name (FQDN) of the database instance (e.g. `db.example.com`). On the database client certificate, one of the entries mentioned above must contain the database username that Gitea will be using to connect. - You need domain name mappings of both Gitea and database servers to their respective IP addresses. Either set up DNS records for them or add local mappings to `/etc/hosts` (`%WINDIR%\System32\drivers\etc\hosts` in Windows) on each system. This allows the database connections to be performed by domain name instead of IP address. See documentation of your system for details. -### PostgreSQL +### PostgreSQL TLS The PostgreSQL driver used by Gitea supports two-way TLS. In two-way TLS, both database client and server authenticate each other by sending their respective certificates to their respective opposite for validation. In other words, the server verifies client certificate, and the client verifies server certificate. @@ -250,7 +252,7 @@ The PostgreSQL driver used by Gitea supports two-way TLS. In two-way TLS, both d You should be prompted to enter password for the database user, and then be connected to the database. -### MySQL +### MySQL/MariaDB TLS While the MySQL driver used by Gitea also supports two-way TLS, Gitea currently supports only one-way TLS. See issue #10828 for details. diff --git a/docs/content/installation/database-preparation.zh-cn.md b/docs/content/installation/database-preparation.zh-cn.md index b5e8b73f1f..a97d70a135 100644 --- a/docs/content/installation/database-preparation.zh-cn.md +++ b/docs/content/installation/database-preparation.zh-cn.md @@ -23,7 +23,7 @@ menu: 注意:以下所有步骤要求您的选择的数据库引擎已安装在您的系统上。对于远程数据库设置,请在数据库实例上安装服务器应用程序,在 Gitea 服务器上安装客户端程序。客户端程序用于测试 Gitea 服务器与数据库之间的连接,而 Gitea 本身使用 Go 提供的数据库驱动程序完成相同的任务。此外,请确保服务器和客户端使用相同的引擎版本,以使某些引擎功能正常工作。出于安全原因,请使用安全密码保护 `root`(MySQL)或 `postgres`(PostgreSQL)数据库超级用户。以下步骤假设您在数据库和 Gitea 服务器上都使用 Linux。 -## MySQL +## MySQL/MariaDB 1. 对于远程数据库设置,您需要让 MySQL 监听您的 IP 地址。编辑数据库实例上的 `/etc/mysql/my.cnf` 文件中的 `bind-address` 选项为: @@ -182,7 +182,7 @@ menu: - 在数据库服务器证书中,`Subject Alternative Name` 或 `Common Name` 条目之一必须是数据库实例的完全限定域名(FQDN)(例如 `db.example.com`)。在数据库客户端证书中,上述提到的条目之一必须包含 Gitea 将用于连接的数据库用户名。 - 您需要将 Gitea 和数据库服务器的域名映射到它们各自的 IP 地址。可以为它们设置 DNS 记录,也可以在每个系统上的 `/etc/hosts`(Windows 中的 `%WINDIR%\System32\drivers\etc\hosts`)中添加本地映射。这样可以通过域名而不是 IP 地址进行数据库连接。有关详细信息,请参阅您系统的文档。 -### PostgreSQL +### PostgreSQL TLS Gitea 使用的 PostgreSQL 驱动程序支持双向 TLS。在双向 TLS 中,数据库客户端和服务器通过将各自的证书发送给对方进行验证来相互认证。换句话说,服务器验证客户端证书,客户端验证服务器证书。 @@ -250,7 +250,7 @@ Gitea 使用的 PostgreSQL 驱动程序支持双向 TLS。在双向 TLS 中, 您将被提示输入数据库用户的密码,然后连接到数据库。 -### MySQL +### MySQL/MariaDB TLS 虽然 Gitea 使用的MySQL驱动程序也支持双向 TLS,但目前 Gitea 仅支持单向 TLS。有关详细信息,请参见工单#10828。 diff --git a/docs/content/installation/from-binary.fr-fr.md b/docs/content/installation/from-binary.fr-fr.md deleted file mode 100644 index ac534263e1..0000000000 --- a/docs/content/installation/from-binary.fr-fr.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Installation avec le binaire pré-compilé" -slug: "install-from-binary" -sidebar_position: 15 -toc: false -draft: false -aliases: - - /fr-fr/install-from-binary -menu: - sidebar: - parent: "installation" - name: "Binaire pré-compilé" - sidebar_position: 15 - identifier: "install-from-binary" ---- - -# Installation avec le binaire pré-compilé - -Tous les binaires sont livrés avec le support de SQLite, MySQL et PostgreSQL, et sont construits avec les ressources incorporées. Gardez à l'esprit que cela peut être différent pour les versions antérieures. L'installation basée sur nos binaires est assez simple, il suffit de choisir le fichier correspondant à votre plateforme à partir de la [page de téléchargement](https://dl.gitea.com/gitea). Copiez l'URL et remplacer l'URL dans les commandes suivantes par la nouvelle: - -``` -wget -O gitea https://dl.gitea.com/gitea/@version@/gitea-@version@-linux-amd64 -chmod +x gitea -``` - -## Test - -Après avoir suivi les étapes ci-dessus, vous aurez un binaire `gitea` dans votre répertoire de travail. En premier lieu, vous pouvez tester si le binaire fonctionne comme prévu et ensuite vous pouvez le copier vers la destination où vous souhaitez le stocker. Lorsque vous lancez Gitea manuellement à partir de votre CLI, vous pouvez toujours le tuer en appuyant sur `Ctrl + C`. - -``` -./gitea web -``` - -## Dépannage - -### Anciennes version de glibc - -Les anciennes distributions Linux (comme Debian 7 ou CentOS 6) peuvent ne pas être capable d'exécuter le binaire Gitea, résultant généralement une erreur du type ```./gitea: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.14' not found (required by ./gitea)```. Cette erreur est due au driver SQLite que nous intégrons dans le binaire Gitea. Dans le futur, nous fournirons des binaires sans la dépendance pour la bibliothèque glibc. En attendant, vous pouvez mettre à jour votre distribution ou installer Gitea depuis le [code source](installation/from-source.md). - -### Exécuter Gitea avec un autre port - -Si vous obtenez l'erreur `702 runWeb()] [E] Failed to start server: listen tcp 0.0.0.0:3000: bind: address already in use`, Gitea à besoin d'utiliser un autre port. Vous pouvez changer le port par défaut en utilisant `./gitea web -p $PORT`. - -## Il manque quelque chose ? - -Est-ce que nous avons oublié quelque chose sur cette page ? N'hésitez pas à nous contacter sur notre [serveur Discord](https://discord.gg/Gitea), vous obtiendrez des réponses à toute vos questions assez rapidement. diff --git a/docs/content/installation/from-binary.zh-cn.md b/docs/content/installation/from-binary.zh-cn.md index 56c7cc0ae0..216a6be51e 100644 --- a/docs/content/installation/from-binary.zh-cn.md +++ b/docs/content/installation/from-binary.zh-cn.md @@ -117,7 +117,7 @@ chmod 770 /etc/gitea - 使用 `gitea generate secret` 创建 `SECRET_KEY` 和 `INTERNAL_TOKEN` - 提供所有必要的密钥 -详情参考 [命令行文档](/zh-cn/command-line/) 中有关 `gitea generate secret` 的内容。 +详情参考 [命令行文档](administration/command-line.md) 中有关 `gitea generate secret` 的内容。 ### 配置 Gitea 工作路径 @@ -209,6 +209,6 @@ remote: ./hooks/pre-receive.d/gitea: line 2: [...]: No such file or directory 如果您没有使用 Gitea 内置的 SSH 服务器,您还需要通过在管理选项中运行任务 `Update the '.ssh/authorized_keys' file with Gitea SSH keys.` 来重新编写授权密钥文件。 -> 更多经验总结,请参考英文版 [Troubleshooting](/en-us/install-from-binary/#troubleshooting) +> 更多经验总结,请参考英文版 [Troubleshooting](https://docs.gitea.com/installation/install-from-binary#troubleshooting) 如果从本页中没有找到你需要的内容,请访问 [帮助页面](help/support.md) diff --git a/docs/content/installation/from-binary.zh-tw.md b/docs/content/installation/from-binary.zh-tw.md deleted file mode 100644 index 1fff90aa37..0000000000 --- a/docs/content/installation/from-binary.zh-tw.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "執行檔安裝" -slug: "install-from-binary" -sidebar_position: 15 -toc: false -draft: false -aliases: - - /zh-tw/install-from-binary -menu: - sidebar: - parent: "installation" - name: "執行檔" - sidebar_position: 15 - identifier: "install-from-binary" ---- - -# 從執行檔安裝 - -所有的執行檔皆支援 SQLite, MySQL and PostgreSQL,且所有檔案都已經包在執行檔內,這一點跟之前的版本有所不同。關於執行檔的安裝方式非常簡單,只要從[下載頁面](https://dl.gitea.com/gitea)選擇相對應平台,複製下載連結,使用底下指令就可以完成了: - -``` -wget -O gitea https://dl.gitea.com/gitea/@version@/gitea-@version@-linux-amd64 -chmod +x gitea -``` - -## 測試 - -執行完上述步驟,您將會得到 `gita` 執行檔,在複製到遠端伺服器前,您可以先測試看看,在命令列執行完成後,可以透過 `Ctrl + C` 關閉程式。 - -``` -./gitea web -``` - -## 需要協助? - -如果本頁中無法解決您的問題,請直接到 [Discord server](https://discord.gg/Gitea),在那邊可以快速得到協助。 diff --git a/docs/content/installation/from-package.fr-fr.md b/docs/content/installation/from-package.fr-fr.md deleted file mode 100644 index 32e020dc7b..0000000000 --- a/docs/content/installation/from-package.fr-fr.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "Installation depuis le gestionnaire de paquets" -slug: "install-from-package" -sidebar_position: 20 -toc: false -draft: false -aliases: - - /fr-fr/install-from-package -menu: - sidebar: - parent: "installation" - name: "Gestionnaire de paquets" - sidebar_position: 20 - identifier: "install-from-package" ---- - -# Installation depuis le gestionnaire de paquets - -## Linux - -Nous n'avons pas encore publié de paquet pour Linux, nous allons mettre à jour cette page directement lorsque nous commencerons à publier des paquets pour toutes distributions Linux. En attendant, vous devriez suivre les [instructions d'installation](installation/from-binary.md) avec le binaire pré-compilé. - -## Windows - -Nous n'avons pas encore publié de paquet pour Windows, nous allons mettre à jour cette page directement lorsque nous commencerons à publier des paquets sous la forme de fichiers `MSI` ou via [Chocolatey](https://chocolatey.org/). En attendant, vous devriez suivre les [instructions d'installation](installation/from-binary.md) avec le binaire pré-compilé. - -## macOS - -Actuellement, nous ne supportons que l'installation via `brew` pour macOS. Si vous n'utilisez pas [Homebrew](http://brew.sh/), vous pouvez suivre les [instructions d'installation](installation/from-binary.md) avec le binaire pré-compilé. Pour installer Gitea depuis `brew`, utilisez les commandes suivantes : - -``` -brew install gitea -``` - -## FreeBSD - -Le portage FreeBSD `www/gitea` est disponible. Vous pouvez également installer le paquet pré-compilé avec la commande suivante: - -``` -pkg install gitea -``` - -Pour une version plus récente, ou pour les instructions de compilations, veuillez consulter la documentation officielle de FreeBSD : [install it from the port](https://www.freebsd.org/doc/handbook/ports-using.html) - -``` -su - -cd /usr/ports/www/gitea -make install clean -``` - -Le port utilise la schéma standard du système de fichiers FreeBSD : Les fichiers de configuration sont localisés dans le répertoire `/usr/local/etc/gitea`, les modèles, options, plugins et thèmes sont localisés dans le répertoire `/usr/local/share/gitea`, et le script de démarrage se situe dans `/usr/local/etc/rc.d/gitea`. - -Pour exécuter Gitea en tant que service, utilisez la commande `sysrc gitea_enable=YES` et la commande `service gitea start` pour démarrer le service. - -## Il manque quelque chose ? - -Est-ce que nous avons oublié quelque chose sur cette page ? N'hésitez pas à nous contacter sur notre [serveur Discord](https://discord.gg/Gitea), vous obtiendrez des réponses à toute vos questions assez rapidement. diff --git a/docs/content/installation/from-package.zh-tw.md b/docs/content/installation/from-package.zh-tw.md deleted file mode 100644 index 8a2de2997d..0000000000 --- a/docs/content/installation/from-package.zh-tw.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "套件安裝" -slug: "install-from-package" -sidebar_position: 20 -toc: false -draft: false -aliases: - - /zh-tw/install-from-package -menu: - sidebar: - parent: "installation" - name: "套件安裝" - sidebar_position: 20 - identifier: "install-from-package" ---- - -# 從套件安裝 - -## Linux - -目前尚未發佈任何 Linux 套件,如果我們發佈了,會直接更新此網頁。在這之前請先參考[執行檔安裝](installation/from-binary.md)方式。 - -## Windows - -在 Windows 作業系統你可以透過 [Chocolatey](https://chocolatey.org/) 套件管理器安裝 [Gitea](https://chocolatey.org/packages/gitea) 套件: - -```sh -choco install gitea -``` - -也可以參考[執行檔安裝](installation/from-binary.md)方式。 - -## macOS - -目前我們只支援透過 `brew` 來安裝套件。假如您尚未使用 [Homebrew](http://brew.sh/),您就必須參考[執行檔安裝](installation/from-binary.md)方式。透過 `brew` 安裝 Gitea,您只需要執行底下指令: - -``` -brew install gitea -``` - -## FreeBSD - -下載 FreeBSD port `www/gitea` 套件。你可以安裝 pre-built 執行檔: - -``` -pkg install gitea -``` - -對於最新版本或想要自行編譯特定選項,請使用 [port 安裝](https://www.freebsd.org/doc/handbook/ports-using.html): - -``` -su - -cd /usr/ports/www/gitea -make install clean -``` - -## 需要協助? - -如果本頁中無法解決您的問題,請直接到 [Discord server](https://discord.gg/Gitea),在那邊可以快速得到協助。 diff --git a/docs/content/installation/from-source.en-us.md b/docs/content/installation/from-source.en-us.md index ef720f0824..e6998ce86d 100644 --- a/docs/content/installation/from-source.en-us.md +++ b/docs/content/installation/from-source.en-us.md @@ -27,13 +27,7 @@ Next, [install Node.js with npm](https://nodejs.org/en/download/) which is required to build the JavaScript and CSS files. The minimum supported Node.js version is @minNodeVersion@ and the latest LTS version is recommended. -**Note**: When executing make tasks that require external tools, like -`make misspell-check`, Gitea will automatically download and build these as -necessary. To be able to use these, you must have the `"$GOPATH/bin"` directory -on the executable path. If you don't add the go bin directory to the -executable path, you will have to manage this yourself. - -**Note 2**: Go version @minGoVersion@ or higher is required. However, it is recommended to +**Note**: Go version @minGoVersion@ or higher is required. However, it is recommended to obtain the same version as our continuous integration, see the advice given in [Hacking on Gitea](development/hacking-on-gitea.md) @@ -128,8 +122,6 @@ If pre-built frontend files are present it is possible to only build the backend TAGS="bindata" make backend ``` -Webpack source maps are by default enabled in development builds and disabled in production builds. They can be enabled by setting the `ENABLE_SOURCEMAP=true` environment variable. - ## Test After following the steps above, a `gitea` binary will be available in the working directory. @@ -260,3 +252,11 @@ GOARCH=amd64 \ TAGS="bindata sqlite sqlite_unlock_notify" \ make build ``` + +## Source Maps + +By default, gitea generates reduced source maps for frontend files to conserve space. This can be controlled with the `ENABLE_SOURCEMAP` environment variable: + +- `ENABLE_SOURCEMAP=true` generates all source maps, the default for development builds +- `ENABLE_SOURCEMAP=reduced` generates limited source maps, the default for production builds +- `ENABLE_SOURCEMAP=false` generates no source maps diff --git a/docs/content/installation/from-source.fr-fr.md b/docs/content/installation/from-source.fr-fr.md deleted file mode 100644 index fe0f173bda..0000000000 --- a/docs/content/installation/from-source.fr-fr.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Installation depuis le code source" -slug: "install-from-source" -sidebar_position: 30 -toc: false -draft: false -aliases: - - /fr-fr/install-from-source -menu: - sidebar: - parent: "installation" - name: "Code source" - sidebar_position: 30 - identifier: "install-from-source" ---- - -# Installation depuis le code source - -Nous ne couvrirons pas les bases de la configuration de Golang dans ce guide. Si vous ne savez pas comment démarrer un environnement fonctionnel, vous devrez suivre les [instructions d'installation](https://golang.org/doc/install) officielles. - -**Attention**: La version 1.7 ou suppérieur de Go est nécessaire - -## Téléchargement - -Tout d'abord, vous devez récupérer le code source, la manière la plus simple est d'utiliser directement Go. Il suffit d'appeler les commandes suivantes pour récupérer le code source et passer au répertoire de travail. - -``` -go get -d -u code.gitea.io/gitea -cd $GOPATH/src/code.gitea.io/gitea -``` - -Maintenant, il est temps de décider quelle version de Gitea vous souhaitez compiler et installer. Actuellement, ils existent plusieurs options possibles. Si vous voulez compiler notre branche `master`, vous pouvez directement passer à la [section compilation](#compilation), cette branche représente la dernière version en cours de développement et n'a pas vocation à être utiliser en production. - -Si vous souhaitez compiler la dernière version stable, utilisez les étiquettes ou les différentes branches disponibles. Vous pouvez voir les branches disponibles et comment utiliser cette branche avec ces commandes: - -``` -git branch -a -git checkout v@version@ -``` - -Si vous souhaitez valider une demande d'ajout (_Pull request_), vous devez activer cette branche en premier : - -``` -git fetch origin pull/xyz/head:pr-xyz # xyz is PR value -``` - -Enfin, vous pouvez directement utiliser les versions étiquettées (ex : `v@version@`). Pour utiliser les étiquettes, vous devez lister les étiquettes disponibles et choisir une étiquette spécifique avec les commandes suivantes : - -``` -git tag -l -git checkout v@version@ -git checkout pr-xyz -``` - -## Compilation - -Comme nous regroupons déjà toutes les bibliothèques requises pour compiler Gitea, vous pouvez continuer avec le processus de compilation lui-même. Nous fournissons diverses [tâches Make](https://github.com/go-gitea/gitea/blob/main/Makefile) pour rendre le processus de construction aussi simple que possible. [Voyez ici comment obtenir Make](/fr-fr/hacking-on-gitea/). Selon vos besoins, vous pourrez éventuellement ajouter diverses options de compilation, vous pouvez choisir entre ces options : - -* `bindata`: Intègre toutes les ressources nécessaires à l'exécution d'une instance de Gitea, ce qui rend un déploiement facile car il n'est pas nécessaire de se préoccuper des fichiers supplémentaires. -* `sqlite sqlite_unlock_notify`: Active la prise en charge d'une base de données [SQLite3](https://sqlite.org/), ceci n'est recommandé que pour les petites installations de Gitea. -* `pam`: Active la prise en charge de PAM (mLinux Pluggable Authentication Modules), très utile si vos utilisateurs doivent être authentifiés avec les comptes du système. - -Il est temps de compiler le binaire, nous suggérons d'intégrer les ressources avec l'option de compilation `bindata`: - -``` -TAGS="bindata" make build -``` - -## Test - -Après avoir suivi toutes les étapes, vous devriez avoir le binaire `gitea` dans votre répertoire courant. Dans un premier temps, vous pouvez tester qu'il fonctionne puis, dans un second temps, vous pouvez le copier dans la destination de votre choix. Lorsque vous lancez Gitea manuellement à partir de votre CLI, vous pouvez toujours le tuer en appuyant sur `Ctrl + C`. - -``` -./gitea web -``` - -## Il manque quelque chose ? - -Est-ce que nous avons oublié quelque chose sur cette page ? N'hésitez pas à nous contacter sur notre [serveur Discord](https://discord.gg/Gitea), vous obtiendrez des réponses à toute vos questions assez rapidement. diff --git a/docs/content/installation/from-source.zh-cn.md b/docs/content/installation/from-source.zh-cn.md index 260ccbee3c..019184415d 100644 --- a/docs/content/installation/from-source.zh-cn.md +++ b/docs/content/installation/from-source.zh-cn.md @@ -21,9 +21,7 @@ menu: 接下来,[安装 Node.js 和 npm](https://nodejs.org/zh-cn/download/), 这是构建 JavaScript 和 CSS 文件所需的。最低支持的 Node.js 版本是 @minNodeVersion@,建议使用最新的 LTS 版本。 -**注意**:当执行需要外部工具的 make 任务(如`make misspell-check`)时,Gitea 将根据需要自动下载和构建这些工具。为了能够实现这个目的,你必须将`"$GOPATH/bin"`目录添加到可执行路径中。如果没有将 Go 的二进制目录添加到可执行路径中,你需要自行解决产生的问题。 - -**注意2**:需要 Go 版本 @minGoVersion@ 或更高版本。不过,建议获取与我们的持续集成(continuous integration, CI)相同的版本,请参阅在 [Hacking on Gitea](development/hacking-on-gitea.md) 中给出的建议。 +**注意**:需要 Go 版本 @minGoVersion@ 或更高版本。不过,建议获取与我们的持续集成(continuous integration, CI)相同的版本,请参阅在 [Hacking on Gitea](development/hacking-on-gitea.md) 中给出的建议。 ## 下载 @@ -64,7 +62,7 @@ git checkout v@version@ # or git checkout pr-xyz - `go` @minGoVersion@ 或更高版本,请参阅 [这里](https://golang.org/dl/) - `node` @minNodeVersion@ 或更高版本,并且安装 `npm`, 请参阅 [这里](https://nodejs.org/zh-cn/download/) -- `make`, 请参阅 [这里](/zh-cn/hacking-on-gitea/) +- `make`, 请参阅 [这里](development/hacking-on-gitea.md) 为了尽可能简化编译过程,提供了各种 [make任务](https://github.com/go-gitea/gitea/blob/main/Makefile)。 @@ -100,8 +98,6 @@ TAGS="bindata sqlite sqlite_unlock_notify" make build TAGS="bindata" make backend ``` -在开发构建中,默认启用 Webpack 源映射,在生产构建中禁用。可以通过设置`ENABLE_SOURCEMAP=true`环境变量来启用它们。 - ## 测试 按照上述步骤完成后,工作目录中将会有一个`gitea`二进制文件。可以从该目录进行测试,或将其移动到带有测试数据的目录中。当手动从命令行启动 Gitea 时,可以通过按下`Ctrl + C`来停止程序。 @@ -221,3 +217,11 @@ GOARCH=amd64 \ TAGS="bindata sqlite sqlite_unlock_notify" \ make build ``` + +## 源映射 + +默认情况下,gitea 会为前端文件生成精简的源映射以节省空间。 这可以通过“ENABLE_SOURCEMAP”环境变量进行控制: + +- `ENABLE_SOURCEMAP=true` 生成所有源映射,这是开发版本的默认设置 +- `ENABLE_SOURCEMAP=reduced` 生成有限的源映射,这是生产版本的默认设置 +- `ENABLE_SOURCEMAP=false` 不生成源映射 diff --git a/docs/content/installation/from-source.zh-tw.md b/docs/content/installation/from-source.zh-tw.md deleted file mode 100644 index eedfc36f0c..0000000000 --- a/docs/content/installation/from-source.zh-tw.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "原始碼安裝" -slug: "install-from-source" -sidebar_position: 30 -toc: false -draft: false -aliases: - - /zh-tw/install-from-source -menu: - sidebar: - parent: "installation" - name: "原始碼安裝" - sidebar_position: 30 - identifier: "install-from-source" ---- - -# 從原始碼安裝 - -我們不會在本文教大家如何安裝 Golang 環境。假如您不知道如何設定環境,請直接參考[官方安裝文件](https://golang.org/doc/install)。 - -## 下載 - -首先您必須先下載原始碼,最簡單的方式就是透過 Go 指令下載,請透過底下指令下載原始碼並且切換到工作目錄。 - -``` -go get -d -u code.gitea.io/gitea -cd $GOPATH/src/code.gitea.io/gitea -``` - -現在該決定您要編譯或安裝的 Gitea 版本,您有很多可以選擇。如果您想編譯 `master` 版本,你可以直接跳到[編譯章節](#編譯),這是我們開發分支,雖然很穩定,但是不建議用在正式環境。 - -假如您想要編譯最新穩定版本,可以執行底下命令切換到正確版本: - -``` -git branch -a -git checkout v@version@ -``` - -最後您也可以直接編譯最新的標籤版本像是 `v@version@`,假如您想要從原始碼編譯,這方法是最合適的,在編譯標籤版本前,您需要列出當下所有標籤,並且直接切換到標籤版本,請使用底下指令:: - -``` -git tag -l -git checkout v@version@ -``` - -## 編譯 - -完成設定相依性套件環境等工作後,您就可以開始編譯工作了。我們提供了不同的[編譯選項](https://github.com/go-gitea/gitea/blob/main/Makefile) ,讓編譯過程更加簡單。您可以根據需求來調整編譯選項,底下是可用的編譯選項說明: - -* `bindata`: 使用此標籤來嵌入所有 Gitea 相關資源,您不用擔心其他額外檔案,對於部署來說非常方便。 -* `sqlite sqlite_unlock_notify`: 使用此標籤來啟用 [SQLite3](https://sqlite.org/) 資料庫,建議只有少數人時才使用此模式。 -* `pam`: 使用此標籤來啟用 PAM (Linux Pluggable Authentication Modules) 認證,對於系統使用者來說,此方式最方便了。 - -現在您可以開始編譯執行檔了,我們建議使用 `bindata` 編譯選項: - -``` -TAGS="bindata" make build -``` - -**注意**: 因為使用了套件管理工具,我們建議 Go 環境版本為 1.6 或者是更高,這樣不用在 Go 1.5 版本設定全域變數 `GO15VENDOREXPERIMENT`。 - -## 測試 - -完成上述步驟後,您可以在當下目錄發現 `gitea` 執行檔,在複製執行檔到遠端環境之前,您必須透過底下指令執行測試,使用 `Ctrl + C` 則可以關閉當下 gitea 程序。 - -``` -./gitea web -``` - -## 需要協助? - -如果本頁中無法解決您的問題,請直接到 [Discord server](https://discord.gg/Gitea),在那邊可以快速得到協助。 diff --git a/docs/content/installation/on-kubernetes.zh-tw.md b/docs/content/installation/on-kubernetes.zh-tw.md deleted file mode 100644 index 294e11ee1e..0000000000 --- a/docs/content/installation/on-kubernetes.zh-tw.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -date: "2020-03-19T19:27:00+02:00" -title: "在 Kubernetes 安裝" -slug: "install-on-kubernetes" -sidebar_position: 80 -toc: false -draft: false -aliases: - - /zh-tw/install-on-kubernetes -menu: - sidebar: - parent: "installation" - name: "Kubernetes" - sidebar_position: 80 - identifier: "install-on-kubernetes" ---- - -# 使用 Helm 安裝 (在 Kubernetes) - -Gitea 提供 Helm Chart 用來安裝於 kubernetes。 - -非自訂安裝可使用下列指令: - -``` -helm repo add gitea-charts https://dl.gitea.com/charts/ -helm install gitea gitea-charts/gitea -``` - -若您想自訂安裝(包括使用 kubernetes ingress),請前往完整的 [Gitea helm chart configuration details](https://gitea.com/gitea/helm-chart/) - -## 運行狀況檢查終端節點 - -Gitea 附帶了一個運行狀況檢查端點 `/api/healthz`,你可以像這樣在 kubernetes 中配置它: - -```yaml - livenessProbe: - httpGet: - path: /api/healthz - port: http - initialDelaySeconds: 200 - timeoutSeconds: 5 - periodSeconds: 10 - successThreshold: 1 - failureThreshold: 10 -``` - -成功的運行狀況檢查回應將使用 HTTP 代碼 `200` 進行回應,下面是示例: - -``` -HTTP/1.1 200 OK - -{ - "status": "pass", - "description": "Gitea: Git with a cup of tea", - "checks": { - "cache:ping": [ - { - "status": "pass", - "time": "2022-02-19T09:16:08Z" - } - ], - "database:ping": [ - { - "status": "pass", - "time": "2022-02-19T09:16:08Z" - } - ] - } -} -``` - -有關更多信息,請參考kubernetes文檔[定義一個存活態 HTTP請求接口](https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) diff --git a/docs/content/installation/run-as-service-in-ubuntu.zh-tw.md b/docs/content/installation/run-as-service-in-ubuntu.zh-tw.md deleted file mode 100644 index 51b23deaa4..0000000000 --- a/docs/content/installation/run-as-service-in-ubuntu.zh-tw.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -date: "2017-07-21T12:00:00+02:00" -title: "在 Linux 中以服務執行" -slug: "linux-service" -sidebar_position: 40 -toc: false -draft: false -aliases: - - /zh-tw/linux-service -menu: - sidebar: - parent: "installation" - name: "Linux 服務" - sidebar_position: 40 - identifier: "linux-service" ---- - -### 以 Linux 服務執行 Gitea - -您可使用 systemd 或 supervisor 以服務的方式執行 Gitea。下列步驟已在 Ubuntu 16.04 中測試,但它們應該適用於所有的 Linux 發行版(只需要一些小小的調整)。 - -#### 使用 systemd - -複製範例 [gitea.service](https://github.com/go-gitea/gitea/blob/main/contrib/systemd/gitea.service) 到 `/etc/systemd/system/gitea.service` 後用您喜愛的文字編輯器開啟檔案。 - -取消註解任何需要在此系統上啟動的服務像是 MySQL。 - -修改 user, home directory 和其它必要的啟動參數。若預設埠已被占用請修改埠號或移除「-p」旗標。 - -在系統啟動時啟用並執行 Gitea: - -``` -sudo systemctl enable gitea -sudo systemctl start gitea -``` - -若您使用 systemd 220 或更新版本,您能以一行指令啟動並立即執行 Gitea: - -``` -sudo systemctl enable gitea --now -``` - -#### 使用 supervisor - -在終端機使用下列指令安裝 supervisor: - -``` -sudo apt install supervisor -``` - -為 supervisor 建立 log 資料夾: - -``` -# assuming Gitea is installed in /home/git/gitea/ -mkdir /home/git/gitea/log/supervisor -``` - -附加範例 [supervisord config](https://github.com/go-gitea/gitea/blob/main/contrib/supervisor/gitea) 的設定值到 `/etc/supervisor/supervisord.conf`。 - -用您喜愛的文字編輯器修改使用者(git)和家目錄(/home/git)設定以符合部署環境。若預設埠已被占用請修改埠號或移除「-p」旗標。 - -最後設定在系統啟動時啟用並執行 supervisor: - -``` -sudo systemctl enable supervisor -sudo systemctl start supervisor -``` - -若您使用 systemd 220 或更新版本,您能以一行指令啟動並立即執行 supervisor: - -``` -sudo systemctl enable supervisor --now -``` diff --git a/docs/content/installation/upgrade-from-gogs.fr-fr.md b/docs/content/installation/upgrade-from-gogs.fr-fr.md deleted file mode 100644 index a5cd875982..0000000000 --- a/docs/content/installation/upgrade-from-gogs.fr-fr.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Mise à jour depuis Gogs" -slug: "upgrade-from-gogs" -sidebar_position: 101 -toc: false -draft: false -aliases: - - /fr-fr/upgrade-from-gogs -menu: - sidebar: - parent: "installation" - name: "Depuis Gogs" - sidebar_position: 101 - identifier: "upgrade-from-gogs" ---- - -# Mise à jour depuis Gogs - -À partir de la version 0.9.146 (schéma de la base de données : version 15) de Gogs, Il est possible de migrer vers Gitea simplement et sans encombre. - -Veuillez suivre les étapes ci-dessous. Sur Unix, toute les commandes s'exécutent en tant que l'utilisateur utilisé pour votre installation de Gogs : - -* Crééer une sauvegarde de Gogs avec la commande `gogs dump`. Le fichier nouvellement créé `gogs-dump-[timestamp].zip` contient toutes les données de votre instance de Gogs. -* Téléchargez le fichier correspondant à votre plateforme à partir de la [page de téléchargements](https://dl.gitea.com/gitea). -* Mettez la binaire dans le répertoire d'installation souhaité. -* Copiez le fichier `gogs/custom/conf/app.ini` vers `gitea/custom/conf/app.ini`. -* Si vous avez personnalisé les répertoires `templates, public` dans `gogs/custom/`, copiez-les vers `gitea/custom/`. -* Si vous avez d'autres répertoires personnalisés comme `gitignore, label, license, locale, readme` dans `gogs/custom/conf` copiez-les vers `gitea/custom/options`. -* Copiez le répertoire `gogs/data/` vers `gitea/data/`. -* Vérifiez votre installation en exécutant Gitea avec la commande `gitea web`. -* Lancez le binaire de version majeure en version majeure ( `1.1.4` → `1.2.3` → `1.3.4` → `1.4.2` → etc ) afin de récupérer les migrations de base de données. -* Connectez vous au panel d'administration de Gitea et exécutez l'action `Rewrite '.ssh/authorized_keys' file`, puis l'action `Rewrite all update hook of repositories` (obligatoire si le chemin menant à votre configuration personnalisée à changé). - -## Modifier les informations spécifiques de gogs - -* Renommez `gogs-repositories/` vers `gitea-repositories/` -* Renommez `gogs-data/` to `gitea-data/` -* Dans votre fichier `gitea/custom/conf/app.ini`, modifiez les éléments suivants: - - DE : - - ```ini - [database] - PATH = /home/:USER/gogs/data/:DATABASE.db - [attachment] - PATH = /home/:USER/gogs-data/attachments - [picture] - AVATAR_UPLOAD_PATH = /home/:USER/gogs-data/avatars - [log] - ROOT_PATH = /home/:USER/gogs/log - ``` - - VERS : - - ```ini - [database] - PATH = /home/:USER/gitea/data/:DATABASE.db - [attachment] - PATH = /home/:USER/gitea-data/attachments - [picture] - AVATAR_UPLOAD_PATH = /home/:USER/gitea-data/avatars - [log] - ROOT_PATH = /home/:USER/gitea/log - ``` - -* Vérifiez votre installation en exécutant Gitea avec la commande `gitea web`. - -## Dépannage - -* Si vous rencontrez des erreurs relatives à des modèles personnalisés dans le dossier `gitea/custom/templates`, essayez de déplacer un par un les modèles provoquant les erreurs. Il est possible qu'ils ne soient pas compatibles avec Gitea. - -## Démarrer automatiquement Gitea (Unix) - -Distributions utilisant systemd: - -* Copiez le script mis à jour vers `/etc/systemd/system/gitea.service` -* Ajoutez le service avec la commande `sudo systemctl enable gitea` -* Désactivez Gogs avec la commande `sudo systemctl disable gogs` - -Distributions utilisant SysVinit: - -* Copiez le script mis à jour vers `/etc/init.d/gitea` -* Ajoutez le service avec la commande `sudo rc-update add gitea` -* Désactivez Gogs avec la commande `sudo rc-update del gogs` diff --git a/docs/content/installation/windows-service.fr-fr.md b/docs/content/installation/windows-service.fr-fr.md deleted file mode 100644 index d0d7606297..0000000000 --- a/docs/content/installation/windows-service.fr-fr.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Démarrer en tant que service Windows" -slug: "windows-service" -sidebar_position: 50 -toc: false -draft: false -aliases: - - /fr-fr/windows-service -menu: - sidebar: - parent: "installation" - name: "Service Windows" - sidebar_position: 50 - identifier: "windows-service" ---- - -# Activer un service Windows - -Pour activer le service Windows Gitea, ouvrez une `cmd` en tant qu'Administrateur puis utilisez la commande suivante : - -``` -sc create gitea start= auto binPath= "\"C:\gitea\gitea.exe\" web --config \"C:\gitea\custom\conf\app.ini\"" -``` - -N'oubliez pas de remplacer `C:\gitea` par le chemin que vous avez utilisé pour votre installation. - -Ensuite, ouvrez "Services Windows", puis recherchez le service `gitea`, faites un clic droit et selectionnez "Run". Si tout fonctionne, vous devriez être capable d'accèder à Gitea à l'URL `http://localhost:3000` (ou sur le port configuré si différent de 3000). - -## Désactiver un service Windows - -Pour désactiver le service Windows Gitea, ouvrez une `cmd` en tant qu'Administrateur puis utilisez la commande suivante : - -``` -sc delete gitea -``` diff --git a/docs/content/installation/windows-service.zh-tw.md b/docs/content/installation/windows-service.zh-tw.md deleted file mode 100644 index bd10b71db9..0000000000 --- a/docs/content/installation/windows-service.zh-tw.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -date: "2016-12-21T15:00:00-02:00" -title: "註冊為 Windows 服務" -slug: "windows-service" -sidebar_position: 50 -toc: false -draft: false -aliases: - - /zh-tw/windows-service -menu: - sidebar: - parent: "installation" - name: "Windows 服務" - sidebar_position: 50 - identifier: "windows-service" ---- - -# 事前準備 - -確認您的 C:\gitea\custom\conf\app.ini 中包含: - -``` -RUN_USER = COMPUTERNAME$ -``` - -設定 Gitea 以本地使用者身份執行。 - -請將在命令提示字元(cmd)執行 `echo %COMPUTERNAME%` 的結果輸入 `COMPUTERNAME`。若回應為 `USER-PC`,請輸入 `RUN_USER = USER-PC$` - -## 使用絕對路徑 - -如果您使用 sqlite3,修改 `PATH` 為完整路徑: - -``` -[database] -PATH = c:/gitea/data/gitea.db -``` - -# 註冊為 Windows 服務 - -要註冊為 Windows 服務,請先以系統管理員身份開啟命令提示字元,接著執行下列指令: - -``` -sc.exe create gitea start= auto binPath= "\"C:\gitea\gitea.exe\" web --config \"C:\gitea\custom\conf\app.ini\"" -``` - -別忘記將 `C:\gitea` 取代為您的 Gitea 安裝路徑。 - -開啟 Windows 的「服務」,並且搜尋服務名稱「gitea」,按右鍵選擇「啟動」。在瀏覽器打開 `http://localhost:3000` 就可以成功看到畫面 (如果修改過連接埠,請自行修正,3000 是預設值)。 - -## 刪除服務 - -要刪除 Gitea 服務,請用系統管理員身份開啟命令提示字元,接著執行下列指令: - -``` -sc.exe delete gitea -``` diff --git a/docs/content/installation/with-docker.fr-fr.md b/docs/content/installation/with-docker.fr-fr.md deleted file mode 100644 index 46b562e14e..0000000000 --- a/docs/content/installation/with-docker.fr-fr.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -date: "2017-08-23T09:00:00+02:00" -title: "Installation avec Docker" -slug: "install-with-docker" -sidebar_position: 70 -toc: false -draft: false -aliases: - - /fr-fr/install-with-docker -menu: - sidebar: - parent: "installation" - name: "Docker" - sidebar_position: 70 - identifier: "install-with-docker" ---- - -# Installation avec Docker - -Nous fournissons des images Docker mises à jour automatiquement via le Docker Hub de notre organisation. C'est à vous, lors devotre déploiement, de vous assurez d'utiliser toujours la dernière version stable ou d'utiliser un autre service qui met à jour l'image Docker pour vous. - -## Données stockées sur l'hôte - -Tout d'abord, vous devez simplement récupérer l'image Docker avec la commande suivante : - -``` -docker pull gitea/gitea:latest -``` - -Pour garder vos dépôts et certaines autres données persistantes, vous devez créer un répertoire qui contiendra ces données à l'avenir. - -``` -sudo mkdir -p /var/lib/gitea -``` - -Il est temps de démarrer votre instance Docker, c'est un processus assez simple. Vous avez à définir le mappage des ports et le volume à utiliser pour la persistance de vos données : - -``` -docker run -d --name=gitea -p 10022:22 -p 10080:3000 -v /var/lib/gitea:/data gitea/gitea:latest -``` - -Vous devriez avoir une instance fonctionnelle de Gitea. Pour accèder à l'interface web, visitez l'adresse http://hostname:10080 avec votre navigateur web préféré. Si vous voulez clôner un dépôt, vous pouvez le faire avec la commande `git clone ssh://git@hostname:10022/username/repo.git`. - -## Named Volumes - -Ce guide aboutira à une installation avec les données Gitea et PostgreSQL stockées dans des volumes nommés. Cela permet une sauvegarde, une restauration et des mises à niveau en toute simplicité. - -### The Database - -Création du volume nommé pour la base de données : - -``` -$ docker volume create --name gitea-db-data -``` - -Une fois votre volume pret, vous pouvez récupérer l'image Docker de PostgreSQL et créer une instance. Tout comme Gitea, c'est également une image Docker basée sur Alpine Linux, Le montage des données se fera sans aucun problème. - -``` -$ docker pull postgres:alpine -$ docker run -d --name gitea-db \ - -e POSTGRES_PASSWORD= \ - -v gitea-db-data:/var/lib/postgresql/data \ - -p 5432:5432 \ - postgres:alpine -``` - -Maintenant que la base de données est démarrée, il faut la configurer. N'oubliez pas le mot de passe que vous avez choisi, vous en aurez besoin lors de l'installation de Gitea. - -``` -$ docker exec -it gitea-db psql -U postgres -psql (9.6.1) -Type "help" for help. - -postgres=# CREATE USER gitea WITH PASSWORD ''; -CREATE ROLE -postgres=# CREATE DATABASE gitea OWNER gitea; -CREATE DATABASE -postgres=# \q -$ -``` - -### Gitea - -Premièrement, le volume nommé : - -``` -$ docker volume create --name gitea-data -``` - -Puis l'instance de Gitea : - -``` -$ docker run -d --name gitea \ - --link gitea-db:gitea-db \ - --dns 10.12.10.160 \ - -p 11180:3000 \ - -p 8322:22 \ - -v gitea-data:/data \ - gitea/gitea:latest -``` - -Vous devriez maintenant avoir deux conteneurs Docker pour Gitea et PostgreSQL plus deux volumes nommés Docker. - -# Personnalisation - -Les fichier personnalisés ([voir les instructions](administration/customizing-gitea.md)) peuvent être placés dans le répertoire `/data/gitea`. - -Le fichier de configuration sera sauvegardé à l'emplacement suivant : `/data/gitea/conf/app.ini` - -## Il manque quelque chose ? - -Est-ce que nous avons oublié quelque chose sur cette page ? N'hésitez pas à nous contacter sur notre [serveur Discord](https://discord.gg/Gitea), vous obtiendrez des réponses à toute vos questions assez rapidement. diff --git a/docs/content/installation/with-docker.zh-tw.md b/docs/content/installation/with-docker.zh-tw.md deleted file mode 100644 index 95f5cfffe8..0000000000 --- a/docs/content/installation/with-docker.zh-tw.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "Docker 安裝" -slug: "install-with-docker" -sidebar_position: 70 -toc: false -draft: false -aliases: - - /zh-tw/install-with-docker -menu: - sidebar: - parent: "installation" - name: "Docker 安裝" - sidebar_position: 70 - identifier: "install-with-docker" ---- - -# 用 Docker 安裝 - -我們在 Docker Hub 提供了自動更新的映像檔,它會保持最新穩定版。根據您的部屬環境來使用最新版本或用其他服務來更新 Docker 映像檔。首先您需要下載映像檔: - -``` -docker pull gitea/gitea:latest -``` - -為了儲存您的所有 Git 儲存庫資料,您應該建立一個目錄,用來存放資料的地方。 - -``` -sudo mkdir -p /var/lib/gitea -``` - -現在就可以直接啟動 Docker 容器,這是一個非常簡單的過程,您必須定義啟動連接埠,並且提供上面所建立的資料儲存路徑: - -``` -docker run -d --name=gitea -p 10022:22 -p 10080:3000 -v /var/lib/gitea:/data gitea/gitea:latest -``` - -然後 Gitea 容器已經開始運行,您可以透過個人喜愛的瀏覽器來訪問 http://hostname:10080,假如您想要開始 Clone 儲存庫,可以直接執行 `git clone ssh://git@hostname:10022/username/repo.git` 指令。 - -## 需要協助? - -如果本頁中無法解決您的問題,請直接到 [Discord server](https://discord.gg/Gitea),在那邊可以快速得到協助。 diff --git a/docs/content/search.de-de.md b/docs/content/search.de-de.md deleted file mode 100644 index 09a1ac22f2..0000000000 --- a/docs/content/search.de-de.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -date: "2019-11-12T16:00:00+02:00" -title: "Search" -slug: "search" -sidebar_position: 1 -toc: false -draft: false -aliases: - - /de-de/help/search -sitemap: - priority : 1 -layout: "search" ---- - -This file exists solely to respond to /search URL with the related `search` layout template. - -No content shown here is rendered, all content is based in the template layouts/doc/search.html - -Setting a very low sitemap priority will tell search engines this is not important content. diff --git a/docs/content/search.fr-fr.md b/docs/content/search.fr-fr.md deleted file mode 100644 index 25510b7cfd..0000000000 --- a/docs/content/search.fr-fr.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -date: "2019-11-12T16:00:00+02:00" -title: "Chercher" -slug: "search" -sidebar_position: 1 -toc: false -draft: false -aliases: - - /fr-fr/help/search -sitemap: - priority : 1 -layout: "search" ---- - -This file exists solely to respond to /search URL with the related `search` layout template. - -No content shown here is rendered, all content is based in the template layouts/doc/search.html - -Setting a very low sitemap priority will tell search engines this is not important content. diff --git a/docs/content/search.zh-tw.md b/docs/content/search.zh-tw.md deleted file mode 100644 index aa8a1ced75..0000000000 --- a/docs/content/search.zh-tw.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -date: "2019-11-12T16:00:00+02:00" -title: "搜尋" -slug: "search" -sidebar_position: 1 -toc: false -draft: false -aliases: - - /zh-tw/help/search -sitemap: - priority : 1 -layout: "search" ---- - -This file exists solely to respond to /search URL with the related `search` layout template. - -No content shown here is rendered, all content is based in the template layouts/doc/search.html - -Setting a very low sitemap priority will tell search engines this is not important content. diff --git a/docs/content/usage.zh-tw.md b/docs/content/usage.zh-tw.md deleted file mode 100644 index 795f95c726..0000000000 --- a/docs/content/usage.zh-tw.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -date: "2016-12-27T16:00:00+02:00" -title: "使用" -slug: "usage" -sidebar_position: 35 -toc: false -draft: false -menu: - sidebar: - name: "使用" - sidebar_position: 30 - identifier: "usage" ---- diff --git a/docs/content/usage/_index.zh-tw.md b/docs/content/usage/_index.zh-tw.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/content/usage/actions/act-runner.en-us.md b/docs/content/usage/actions/act-runner.en-us.md index e2915be365..3fad9cbfe8 100644 --- a/docs/content/usage/actions/act-runner.en-us.md +++ b/docs/content/usage/actions/act-runner.en-us.md @@ -114,6 +114,12 @@ If you cannot see the settings page, please make sure that you have the right pe The format of the registration token is a random string `D0gvfu2iHfUjNqCYVljVyRV14fISpJxxxxxxxxxx`. +A registration token can also be obtained from the gitea [command-line interface](administration/command-line.md#actions-generate-runner-token): + +``` +gitea --config /etc/gitea/app.ini actions generate-runner-token +``` + ### Register the runner The act runner can be registered by running the following command: @@ -262,6 +268,40 @@ The runner will fetch jobs from the Gitea instance and run them automatically. Since act runner is still in development, it is recommended to check the latest version and upgrade it regularly. +## Systemd service + +It is also possible to run act-runner as a [systemd](https://en.wikipedia.org/wiki/Systemd) service. Create an unprivileged `act_runner` user on your system, and the following file in `/etc/systemd/system/act_runner.service`. The paths in `ExecStart` and `WorkingDirectory` may need to be adjusted depending on where you installed the `act_runner` binary, its configuration file, and the home directory of the `act_runner` user. + +```ini +[Unit] +Description=Gitea Actions runner +Documentation=https://gitea.com/gitea/act_runner +After=docker.service + +[Service] +ExecStart=/usr/local/bin/act_runner daemon --config /etc/act_runner/config.yaml +ExecReload=/bin/kill -s HUP $MAINPID +WorkingDirectory=/var/lib/act_runner +TimeoutSec=0 +RestartSec=10 +Restart=always +User=act_runner + +[Install] +WantedBy=multi-user.target +``` + +Then: + +```bash +# load the new systemd unit file +sudo systemctl daemon-reload +# start the service and enable it at boot +sudo systemctl enable act_runner --now +``` + +If using Docker, the `act_runner` user should also be added to the `docker` group before starting the service. Keep in mind that this effectively gives `act_runner` root access to the system [[1]](https://docs.docker.com/engine/security/#docker-daemon-attack-surface). + ## Configuration variable You can create configuration variables on the user, organization and repository level. diff --git a/docs/content/usage/actions/act-runner.zh-cn.md b/docs/content/usage/actions/act-runner.zh-cn.md index f1404bf0b4..98e3008dd7 100644 --- a/docs/content/usage/actions/act-runner.zh-cn.md +++ b/docs/content/usage/actions/act-runner.zh-cn.md @@ -113,6 +113,8 @@ Runner级别决定了从哪里获取注册令牌。 注册令牌的格式是一个随机字符串 `D0gvfu2iHfUjNqCYVljVyRV14fISpJxxxxxxxxxx`。 +注册令牌也可以通过 Gitea 的 [命令行](administration/command-line.md#actions-generate-runner-token) 获得: + ### 注册Runner 可以通过运行以下命令来注册Act Runner: diff --git a/docs/content/usage/actions/comparison.en-us.md b/docs/content/usage/actions/comparison.en-us.md index caec9d3dea..4d725f7a7e 100644 --- a/docs/content/usage/actions/comparison.en-us.md +++ b/docs/content/usage/actions/comparison.en-us.md @@ -29,6 +29,10 @@ Like `uses: https://github.com/actions/checkout@v3` or `uses: http://your_gitea. Gitea Actions supports writing actions in Go. See [Creating Go Actions](https://blog.gitea.com/creating-go-actions/). +### Support the non-standard syntax @yearly, @monthly, @weekly, @daily, @hourly on schedule + +Github Actions doesn't support that. https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule + ## Unsupported workflows syntax ### `concurrency` @@ -110,14 +114,18 @@ It's ignored by Gitea Actions now. Pre and Post steps don't have their own section in the job log user interface. +### Services steps + +Services steps don't have their own section in the job log user interface. + ## Different behavior ### Downloading actions Previously (Pre 1.21.0), `[actions].DEFAULT_ACTIONS_URL` defaulted to `https://gitea.com`. We have since restricted this option to only allow two values (`github` and `self`). -When set to `github`, the new default, Gitea will download non-fully-qualified actions from . -For example, if you use `uses: actions/checkout@v3`, it will download the checkout repository from . +When set to `github`, the new default, Gitea will download non-fully-qualified actions from `https://github.com`. +For example, if you use `uses: actions/checkout@v3`, it will download the checkout repository from `https://github.com/actions/checkout.git`. If you want to download an action from another git hoster, you can use an absolute URL, e.g. `uses: https://gitea.com/actions/checkout@v3`. @@ -130,34 +138,3 @@ More details about the `[actions].DEFAULT_ACTIONS_URL` configuration can be foun Context availability is not checked, so you can use the env context on more places. See [Context availability](https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability). - -## Known issues - -### `docker/build-push-action@v4` - -See [act_runner#119](https://gitea.com/gitea/act_runner/issues/119#issuecomment-738294). - -`ACTIONS_RUNTIME_TOKEN` is a random string in Gitea Actions, not a JWT. -But the `docker/build-push-action@v4` tries to parse the token as JWT and doesn't handle the error, so the job fails. - -There are two workarounds: - -Set the `ACTIONS_RUNTIME_TOKEN` to empty manually, like: - -``` yml -- name: Build and push - uses: docker/build-push-action@v4 - env: - ACTIONS_RUNTIME_TOKEN: '' - with: -... -``` - -The bug has been fixed in a newer [commit](https://gitea.com/docker/build-push-action/commit/d8823bfaed2a82c6f5d4799a2f8e86173c461aba?style=split&whitespace=show-all#diff-1af9a5bdf96ddff3a2f3427ed520b7005e9564ad), but it has not been released. So you could use the latest version by specifying the branch name, like: - -``` yml -- name: Build and push - uses: docker/build-push-action@master - with: -... -``` diff --git a/docs/content/usage/actions/comparison.zh-cn.md b/docs/content/usage/actions/comparison.zh-cn.md index 5dae75a44b..9a23ef15f6 100644 --- a/docs/content/usage/actions/comparison.zh-cn.md +++ b/docs/content/usage/actions/comparison.zh-cn.md @@ -29,6 +29,10 @@ Gitea Actions支持通过URL绝对路径定义actions,这意味着您可以使 Gitea Actions支持使用Go编写Actions。 请参阅[创建Go Actions](https://blog.gitea.com/creating-go-actions/)。 +### 支持非标准的调度语法 @yearly, @monthly, @weekly, @daily, @hourly + +Github Actions 不支持这些语法,详见: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule + ## 不支持的工作流语法 ### `concurrency` @@ -116,6 +120,10 @@ Gitea Actions目前不支持此功能。 预处理和后处理步骤在Job日志用户界面中没有自己的用户界面。 +### 服务步骤 + +服务步骤在Job日志用户界面中没有自己的用户界面。 + ## 不一样的行为 ### 下载Actions @@ -124,7 +132,7 @@ Gitea Actions目前不支持此功能。 如果你使用 `uses: actions/checkout@v3`,Gitea将会从 https://github.com/actions/checkout.git 下载这个 actions 项目。 如果你想要从另外一个 Git服务下载actions,你只需要使用绝对URL `uses: https://gitea.com/actions/checkout@v3` 来下载。 -如果你的 Gitea 实例是部署在一个互联网限制的网络中,有可以使用绝对地址来下载 actions。你也可以讲配置项修改为 `[actions].DEFAULT_ACTIONS_URL = self`。这样所有的相对路径的actions引用,将不再会从 github.com 去下载,而会从这个 Gitea 实例自己的仓库中去下载。例如: `uses: actions/checkout@v3` 将会从 `[server].ROOT_URL`/actions/checkout.git 这个地址去下载 actions。 +如果你的 Gitea 实例是部署在一个互联网限制的网络中,也可以使用绝对地址来下载 actions。你也可以将配置项修改为 `[actions].DEFAULT_ACTIONS_URL = self`。这样所有的相对路径的actions引用,将不再会从 github.com 去下载,而会从这个 Gitea 实例自己的仓库中去下载。例如: `uses: actions/checkout@v4` 将会从 `[server].ROOT_URL`/actions/checkout.git 这个地址去下载 actions。 设置`[actions].DEFAULT_ACTIONS_URL`进行配置。请参阅[配置备忘单](administration/config-cheat-sheet.md#actions-actions)。 @@ -132,34 +140,3 @@ Gitea Actions目前不支持此功能。 不检查上下文可用性,因此您可以在更多地方使用env上下文。 请参阅[上下文可用性](https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability)。 - -## 已知问题 - -### `docker/build-push-action@v4` - -请参阅[act_runner#119](https://gitea.com/gitea/act_runner/issues/119#issuecomment-738294)。 - -`ACTIONS_RUNTIME_TOKEN`在Gitea Actions中是一个随机字符串,而不是JWT。 -但是`DOCKER/BUILD-PUSH-ACTION@V4尝试将令牌解析为JWT,并且不处理错误,因此Job失败。 - -有两种解决方法: - -手动将`ACTIONS_RUNTIME_TOKEN`设置为空字符串,例如: - -``` yml -- name: Build and push - uses: docker/build-push-action@v4 - env: - ACTIONS_RUNTIME_TOKEN: '' - with: -... -``` - -该问题已在较新的[提交](https://gitea.com/docker/build-push-action/commit/d8823bfaed2a82c6f5d4799a2f8e86173c461aba?style=split&whitespace=show-all#diff-1af9a5bdf96ddff3a2f3427ed520b7005e9564ad)中修复,但尚未发布。因此,您可以通过指定分支名称来使用最新版本,例如: - -``` yml -- name: Build and push - uses: docker/build-push-action@master - with: -... -``` diff --git a/docs/content/usage/actions/faq.en-us.md b/docs/content/usage/actions/faq.en-us.md index 1d59872936..427d57c43e 100644 --- a/docs/content/usage/actions/faq.en-us.md +++ b/docs/content/usage/actions/faq.en-us.md @@ -43,27 +43,26 @@ Still, this is completely optional since both options have the same effect at th Not yet. It is technically possible to implement, but we need to discuss whether it is necessary. -## Where will the runner download scripts when using actions such as `actions/checkout@v3`? +## Where will the runner download scripts when using actions such as `actions/checkout@v4`? -You may be aware that there are tens of thousands of [marketplace actions](https://github.com/marketplace?type=actions) in GitHub. -However, when you write `uses: actions/checkout@v3`, it actually downloads the scripts from [gitea.com/actions/checkout](http://gitea.com/actions/checkout) by default (not GitHub). -This is a mirror of [github.com/actions/checkout](http://github.com/actions/checkout), but it's impossible to mirror all of them. -That's why you may encounter failures when trying to use some actions that haven't been mirrored. +There are tens of thousands of [actions scripts](https://github.com/marketplace?type=actions) in GitHub, and when you write `uses: actions/checkout@v4`, it downloads the scripts from [github.com/actions/checkout](http://github.com/actions/checkout) by default. +But what if you want to use actions from other places such as gitea.com instead of GitHub? The good news is that you can specify the URL prefix to use actions from anywhere. This is an extra syntax in Gitea Actions. For example: -- `uses: https://github.com/xxx/xxx@xxx` - `uses: https://gitea.com/xxx/xxx@xxx` +- `uses: https://github.com/xxx/xxx@xxx` - `uses: http://your_gitea_instance.com/xxx@xxx` Be careful, the `https://` or `http://` prefix is necessary! -Alternatively, if you want your runners to download actions from GitHub or your own Gitea instance by default, you can configure it by setting `[actions].DEFAULT_ACTIONS_URL`. -See [Configuration Cheat Sheet](administration/config-cheat-sheet.md#actions-actions). +This is one of the differences from GitHub Actions which supports actions scripts only from GitHub. +But it should allow users much more flexibility in how they run Actions. -This is one of the differences from GitHub Actions, but it should allow users much more flexibility in how they run Actions. +Alternatively, if you want your runners to download actions from your own Gitea instance by default, you can configure it by setting `[actions].DEFAULT_ACTIONS_URL`. +See [Configuration Cheat Sheet](administration/config-cheat-sheet.md#actions-actions). ## How to limit the permission of the runners? diff --git a/docs/content/usage/actions/faq.zh-cn.md b/docs/content/usage/actions/faq.zh-cn.md index 7bb79d02fc..d6e1466801 100644 --- a/docs/content/usage/actions/faq.zh-cn.md +++ b/docs/content/usage/actions/faq.zh-cn.md @@ -43,27 +43,27 @@ DEFAULT_REPO_UNITS = ...,repo.actions 目前还不可以。 从技术上讲是可以实现的,但我们需要讨论是否有必要。 -## 使用`actions/checkout@v3`等Actions时,Job容器会从何处下载脚本? +## 使用`actions/checkout@v4`等Actions时,Job容器会从何处下载脚本? -您可能知道GitHub上有成千上万个[Actions市场](https://github.com/marketplace?type=actions)。 -然而,当您编写`uses: actions/checkout@v3`时,它实际上默认从[gitea.com/actions/checkout](http://gitea.com/actions/checkout)下载脚本(而不是从GitHub下载)。 -这是[github.com/actions/checkout](http://github.com/actions/checkout)的镜像,但无法将它们全部镜像。 -这就是为什么在尝试使用尚未镜像的某些Actions时可能会遇到失败的原因。 +GitHub 上有成千上万个 [Actions 脚本](https://github.com/marketplace?type=actions)。 +当您编写 `uses: actions/checkout@v4` 时,它默认会从 [github.com/actions/checkout](https://github.com/actions/checkout) 下载脚本。 +那如果您想使用一些托管在其它平台上的脚本呢,比如在 gitea.com 上的? 好消息是,您可以指定要从任何位置使用Actions的URL前缀。 这是Gitea Actions中的额外语法。 例如: -- `uses: https://github.com/xxx/xxx@xxx` - `uses: https://gitea.com/xxx/xxx@xxx` +- `uses: https://github.com/xxx/xxx@xxx` - `uses: http://your_gitea_instance.com/xxx@xxx` 注意,`https://`或`http://`前缀是必需的! -另外,如果您希望您的Runner默认从GitHub或您自己的Gitea实例下载Actions,可以通过设置 `[actions].DEFAULT_ACTIONS_URL`进行配置。 -参见[配置速查表](administration/config-cheat-sheet.md#actions-actions)。 +这是与 GitHub Actions 的一个区别,GitHub Actions 只允许使用托管在 GitHub 上的 actions 脚本。 +但用户理应拥有权利去灵活决定如何运行 Actions。 -这是与GitHub Actions的一个区别,但它应该允许用户以更灵活的方式运行Actions。 +另外,如果您希望您的 Runner 默认从您自己的 Gitea 实例下载 Actions,可以通过设置 `[actions].DEFAULT_ACTIONS_URL`进行配置。 +参见[配置速查表](administration/config-cheat-sheet.md#actions-actions)。 ## 如何限制Runner的权限? diff --git a/docs/content/usage/actions/quickstart.en-us.md b/docs/content/usage/actions/quickstart.en-us.md index 73d90e2e68..f7f4ee2c36 100644 --- a/docs/content/usage/actions/quickstart.en-us.md +++ b/docs/content/usage/actions/quickstart.en-us.md @@ -23,7 +23,7 @@ First of all, you need a Gitea instance. You can follow the [documentation](installation/from-package.md) to set up a new instance or upgrade your existing one. It doesn't matter how you install or run Gitea, as long as its version is 1.19.0 or higher. -Actions are disabled by default, so you need to add the following to the configuration file to enable it: +Since 1.21.0, Actions are enabled by default. If you are using versions before 1.21.0, you need to add the following to the configuration file to enable it: ```ini [actions] diff --git a/docs/content/usage/actions/quickstart.zh-cn.md b/docs/content/usage/actions/quickstart.zh-cn.md index c5b67ce101..ffc1f07df2 100644 --- a/docs/content/usage/actions/quickstart.zh-cn.md +++ b/docs/content/usage/actions/quickstart.zh-cn.md @@ -23,7 +23,7 @@ menu: 您可以按照[文档](installation/from-package.md) 来设置一个新实例或升级现有实例。 无论您如何安装或运行Gitea,只要版本号是1.19.0或更高即可。 -默认情况下,Actions是禁用的,因此您需要将以下内容添加到配置文件中以启用它: +从1.21.0开始,默认情况下,Actions是启用的。如果您正在使用1.21.0之前的版本,您需要将以下内容添加到配置文件中以启用它: ```ini [actions] diff --git a/docs/content/usage/agit-support.en-us.md b/docs/content/usage/agit-support.en-us.md index b1643d27b3..78d2d9fdf7 100644 --- a/docs/content/usage/agit-support.en-us.md +++ b/docs/content/usage/agit-support.en-us.md @@ -26,7 +26,7 @@ This can be done by pushing to the branch followed by a specific refspec (a loca The following example illustrates this: ```shell -git push origin HEAD:refs/for/master +git push origin HEAD:refs/for/main ``` The command has the following structure: @@ -42,8 +42,8 @@ The command has the following structure: - `description`: The PR description - `force-push`: confirm force update the target branch -Here's another advanced example for creating a new PR targeting `master` with `topic`, `title`, and `description`: +Here's another advanced example for creating a new PR targeting `main` with `topic`, `title`, and `description`: ```shell -git push origin HEAD:refs/for/master -o topic="Topic of my PR" -o title="Title of the PR" -o description="# The PR Description\nThis can be **any** markdown content.\n- [x] Ok" +git push origin HEAD:refs/for/main -o topic="Topic of my PR" -o title="Title of the PR" -o description="# The PR Description\nThis can be **any** markdown content.\n- [x] Ok" ``` diff --git a/docs/content/usage/agit-support.zh-cn.md b/docs/content/usage/agit-support.zh-cn.md index 6a2ce1c373..54210d070b 100644 --- a/docs/content/usage/agit-support.zh-cn.md +++ b/docs/content/usage/agit-support.zh-cn.md @@ -26,7 +26,7 @@ Agit 允许在推送代码到远程仓库时创建 PR(合并请求)。 下面的示例说明了这一点: ```shell -git push origin HEAD:refs/for/master +git push origin HEAD:refs/for/main ``` 该命令的结构如下: @@ -42,8 +42,8 @@ git push origin HEAD:refs/for/master - `description`:PR 的描述 - `force-push`:确认强制更新目标分支 -下面是另一个高级示例,用于创建一个以 `topic`、`title` 和 `description` 为参数的新 PR,目标分支是 `master`: +下面是另一个高级示例,用于创建一个以 `topic`、`title` 和 `description` 为参数的新 PR,目标分支是 `main`: ```shell -git push origin HEAD:refs/for/master -o topic="Topic of my PR" -o title="Title of the PR" -o description="# The PR Description\nThis can be **any** markdown content.\n- [x] Ok" +git push origin HEAD:refs/for/main -o topic="Topic of my PR" -o title="Title of the PR" -o description="# The PR Description\nThis can be **any** markdown content.\n- [x] Ok" ``` diff --git a/docs/content/usage/authentication.en-us.md b/docs/content/usage/authentication.en-us.md index 6e4ede0be6..6a6de08d6c 100644 --- a/docs/content/usage/authentication.en-us.md +++ b/docs/content/usage/authentication.en-us.md @@ -198,7 +198,7 @@ administrative user. field is set to `mail.com`, then Gitea will expect the `user email` field for an authenticated GIT instance to be `gituser@mail.com`.[^2] -**Note**: PAM support is added via [build-time flags](installation/install-from-source.md#build), +**Note**: PAM support is added via [build-time flags](installation/from-source.md#build), and the official binaries provided do not have this enabled. PAM requires that the necessary libpam dynamic library be available and the necessary PAM development headers be accessible to the compiler. diff --git a/docs/content/usage/authentication.zh-cn.md b/docs/content/usage/authentication.zh-cn.md index e465bc3f23..d1cfeeb800 100644 --- a/docs/content/usage/authentication.zh-cn.md +++ b/docs/content/usage/authentication.zh-cn.md @@ -162,7 +162,7 @@ PAM提供了一种机制,通过对用户进行PAM认证来自动将其添加 - PAM电子邮件域:用户认证时要附加的电子邮件后缀。例如,如果登录系统期望一个名为gituse的用户, 并且将此字段设置为mail.com,那么Gitea在验证一个GIT实例的用户时将期望user emai字段为gituser@mail.com[^2]。 -**Note**: PAM 支持通过[build-time flags](installation/install-from-source.md#build)添加, +**Note**: PAM 支持通过[build-time flags](installation/from-source.md#build)添加, 而官方提供的二进制文件通常不会默认启用此功能。PAM需要确保系统上有必要的libpam动态库,并且编译器可以访问必要的PAM开发头文件。 [^1]: 例如,在Debian "Bullseye"上使用标准Linux登录,可以使用`common-session-noninteractive`。这个值对于其他版本的Debian, diff --git a/docs/content/usage/authentication.zh-tw.md b/docs/content/usage/authentication.zh-tw.md deleted file mode 100644 index 88dc314914..0000000000 --- a/docs/content/usage/authentication.zh-tw.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "認證" -slug: "authentication" -sidebar_position: 10 -toc: false -draft: false -aliases: - - /zh-tw/authentication -menu: - sidebar: - parent: "usage" - name: "認證" - sidebar_position: 10 - identifier: "authentication" ---- - -# 認證 - -## TBD diff --git a/docs/content/usage/issue-pull-request-templates.en-us.md b/docs/content/usage/issue-pull-request-templates.en-us.md index 34475e3465..7da72fd5dd 100644 --- a/docs/content/usage/issue-pull-request-templates.en-us.md +++ b/docs/content/usage/issue-pull-request-templates.en-us.md @@ -19,9 +19,10 @@ menu: Some projects have a standard list of questions that users need to answer when creating an issue or pull request. Gitea supports adding templates to the -main branch of the repository so that they can autopopulate the form when users are +**default branch of the repository** so that they can autopopulate the form when users are creating issues and pull requests. This will cut down on the initial back and forth of getting some clarifying details. +Is actually not possible to provide generic issue/pull-request templates for all instance. Additionally, the New Issue page URL can be suffixed with `?title=Issue+Title&body=Issue+Text` and the form will be populated with those strings. Those strings will be used instead of the template if there is one. diff --git a/docs/content/usage/packages/container.en-us.md b/docs/content/usage/packages/container.en-us.md index 6be21c2b27..5676aa36fb 100644 --- a/docs/content/usage/packages/container.en-us.md +++ b/docs/content/usage/packages/container.en-us.md @@ -39,6 +39,16 @@ Images must follow this naming convention: `{registry}/{owner}/{image}` +When building your docker image, using the naming convention above, this looks like: + +```shell +# build an image with tag +docker build -t {registry}/{owner}/{image}:{tag} . +# name an existing image with tag +docker tag {some-existing-image}:{tag} {registry}/{owner}/{image}:{tag} +``` + +where your registry is the domain of your gitea instance (e.g. gitea.example.com). For example, these are all valid image names for the owner `testuser`: `gitea.example.com/testuser/myimage` diff --git a/docs/content/usage/packages/debian.en-us.md b/docs/content/usage/packages/debian.en-us.md index 6bd7475dd6..127dcb27f8 100644 --- a/docs/content/usage/packages/debian.en-us.md +++ b/docs/content/usage/packages/debian.en-us.md @@ -27,7 +27,7 @@ The following examples use `apt`. To register the Debian registry add the url to the list of known apt sources: ```shell -echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list +echo "deb [signed-by=/etc/apt/keyrings/gitea-{owner}.asc] https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list ``` | Placeholder | Description | @@ -39,13 +39,13 @@ echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} { If the registry is private, provide credentials in the url. You can use a password or a [personal access token](development/api-usage.md#authentication): ```shell -echo "deb https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list +echo "deb [signed-by=/etc/apt/keyrings/gitea-{owner}.asc] https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list ``` The Debian registry files are signed with a PGP key which must be known to apt: ```shell -sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/trusted.gpg.d/gitea-{owner}.asc +sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/keyrings/gitea-{owner}.asc ``` Afterwards update the local package index: diff --git a/docs/content/usage/packages/debian.zh-cn.md b/docs/content/usage/packages/debian.zh-cn.md index 417b79f703..181130c502 100644 --- a/docs/content/usage/packages/debian.zh-cn.md +++ b/docs/content/usage/packages/debian.zh-cn.md @@ -27,7 +27,7 @@ menu: 要注册 Debian 注册表,请将 URL 添加到已知 `apt` 源列表中: ```shell -echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list +echo "deb [signed-by=/etc/apt/keyrings/gitea-{owner}.asc] https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list ``` | 占位符 | 描述 | @@ -39,13 +39,13 @@ echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} { 如果注册表是私有的,请在 URL 中提供凭据。您可以使用密码或[个人访问令牌](development/api-usage.md#通过-api-认证): ```shell -echo "deb https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list +echo "deb [signed-by=/etc/apt/keyrings/gitea-{owner}.asc] https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list ``` Debian 注册表文件使用 PGP 密钥进行签名,`apt` 必须知道该密钥: ```shell -sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/trusted.gpg.d/gitea-{owner}.asc +sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/keyrings/gitea-{owner}.asc ``` 然后更新本地软件包索引: diff --git a/docs/content/usage/packages/overview.en-us.md b/docs/content/usage/packages/overview.en-us.md index 44d18ff482..89fc6f286e 100644 --- a/docs/content/usage/packages/overview.en-us.md +++ b/docs/content/usage/packages/overview.en-us.md @@ -42,7 +42,7 @@ The following package managers are currently supported: | [PyPI](usage/packages/pypi.md) | Python | `pip`, `twine` | | [RPM](usage/packages/rpm.md) | - | `yum`, `dnf`, `zypper` | | [RubyGems](usage/packages/rubygems.md) | Ruby | `gem`, `Bundler` | -| [Swift](usage/packages/rubygems.md) | Swift | `swift` | +| [Swift](usage/packages/swift.md) | Swift | `swift` | | [Vagrant](usage/packages/vagrant.md) | - | `vagrant` | **The following paragraphs only apply if Packages are not globally disabled!** diff --git a/docs/content/usage/packages/swift.en-us.md b/docs/content/usage/packages/swift.en-us.md index 1bc271ddae..6090afa27a 100644 --- a/docs/content/usage/packages/swift.en-us.md +++ b/docs/content/usage/packages/swift.en-us.md @@ -26,7 +26,8 @@ To work with the Swift package registry, you need to use [swift](https://www.swi To register the package registry and provide credentials, execute: ```shell -swift package-registry set https://gitea.example.com/api/packages/{owner}/swift -login {username} -password {password} +swift package-registry set https://gitea.example.com/api/packages/{owner}/swift +swift package-registry login https://gitea.example.com/api/packages/{owner}/swift --username {username} --password {password} ``` | Placeholder | Description | diff --git a/docs/content/usage/pull-request.zh-tw.md b/docs/content/usage/pull-request.zh-tw.md deleted file mode 100644 index aeb5df5235..0000000000 --- a/docs/content/usage/pull-request.zh-tw.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -date: "2018-06-01T19:00:00+02:00" -title: "合併請求" -slug: "pull-request" -sidebar_position: 13 -toc: false -draft: false -aliases: - - /zh-tw/pull-request -menu: - sidebar: - parent: "usage" - name: "合併請求" - sidebar_position: 13 - identifier: "pull-request" ---- - -# 合併請求 - -## 「還在進行中(WIP)」的合併請求 - -將合併請求標記為還在進行中(Work In Progress, WIP)可避免意外地被合併。 -要將合併請求標記為還在進行中請在標題中使用 `WIP:` 或 `[WIP]` 前綴(不分大小寫)。這些值可在您的 `app.ini` 中設定: - -```ini -[repository.pull-request] -WORK_IN_PROGRESS_PREFIXES=WIP:,[WIP] -``` - -網頁提示會使用第一個值作為範例。 - -## 合併請求範本 - -您可以在[問題與合併請求範本](usage/issue-pull-request-templates.md)找到更多關於合併請求範本的資訊。 diff --git a/docs/content/usage/push.zh-tw.md b/docs/content/usage/push.zh-tw.md deleted file mode 100644 index ccecbf6284..0000000000 --- a/docs/content/usage/push.zh-tw.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -date: "2020-07-06T16:00:00+02:00" -title: "使用: Push" -slug: "push" -sidebar_position: 15 -toc: false -draft: false -aliases: - - /zh-tw/push-options -menu: - sidebar: - parent: "usage" - name: "Push" - sidebar_position: 15 - identifier: "push" ---- - -There are some additional features when pushing commits to Gitea server. - -# Push Merge Hint - -When you pushing commits to a non-default branch, you will get an information from -Gitea which is a link, you can click the link and go to a compare page. It's a quick -way to create a pull request or a code review yourself in the Gitea UI. - -![Gitea Push Hint](/gitea-push-hint.png) - -# Push Options - -Gitea 從 `1.13` 版開始支援某些 [push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt) -。 - -## 支援的 Options - -- `repo.private` (true|false) - 修改儲存庫的可見性。 - - 與 push-to-create 一起使用時特別有用。 - -- `repo.template` (true|false) - 修改儲存庫是否為範本儲存庫。 - -以下範例修改儲存庫的可見性為公開: - -```shell -git push -o repo.private=false -u origin main -``` - -# Push To Create - -Push to create is a feature that allows you to push to a repository that does not exist yet in Gitea. This is useful for automation and for allowing users to create repositories without having to go through the web interface. This feature is disabled by default. - -## Enabling Push To Create - -In the `app.ini` file, set `ENABLE_PUSH_CREATE_USER` to `true` and `ENABLE_PUSH_CREATE_ORG` to `true` if you want to allow users to create repositories in their own user account and in organizations they are a member of respectively. Restart Gitea for the changes to take effect. You can read more about these two options in the [Configuration Cheat Sheet](administration/config-cheat-sheet.md#repository-repository). - -## Using Push To Create - -Assuming you have a git repository in the current directory, you can push to a repository that does not exist yet in Gitea by running the following command: - -```shell -# Add the remote you want to push to -git remote add origin git@{domain}:{username}/{repo name that does not exist yet}.git - -# push to the remote -git push -u origin main -``` - -This assumes you are using an SSH remote, but you can also use HTTPS remotes as well. diff --git a/docs/content/usage/webhooks.zh-tw.md b/docs/content/usage/webhooks.zh-tw.md deleted file mode 100644 index 666dcee83c..0000000000 --- a/docs/content/usage/webhooks.zh-tw.md +++ /dev/null @@ -1,190 +0,0 @@ ---- -date: "2016-12-01T16:00:00+02:00" -title: "Webhook" -slug: "webhooks" -sidebar_position: 30 -toc: false -draft: false -aliases: - - /zh-tw/webhooks -menu: - sidebar: - parent: "usage" - name: "Webhook" - sidebar_position: 30 - identifier: "webhooks" ---- - -# Webhook - -Gitea 的儲存庫事件支援 web hook。這可以有儲存庫管理員在設定頁 `/:username/:reponame/settings/hooks` 中調整。Webhook 也可以按照組織調整或按照全系統調整。 -所有的事件推送都是 POST 請求。此方法目前被下列服務支援: - -- Gitea (也可以是 GET 請求) -- Gogs -- Slack -- Discord -- Dingtalk -- Telegram -- Microsoft Teams -- Feishu -- Wechatwork -- Packagist - -### 事件資訊 - -**警告**: Payload 中的 `secret` 欄位已經在 Gitea 1.13.0 棄用,並且將在 1.14.0 移除: https://github.com/go-gitea/gitea/issues/11755 - -下面是一個將由 Gitea 發送到 Payload URL 的事件資訊的範例: - -``` -X-GitHub-Delivery: f6266f16-1bf3-46a5-9ea4-602e06ead473 -X-GitHub-Event: push -X-Gogs-Delivery: f6266f16-1bf3-46a5-9ea4-602e06ead473 -X-Gogs-Event: push -X-Gitea-Delivery: f6266f16-1bf3-46a5-9ea4-602e06ead473 -X-Gitea-Event: push -``` - -```json -{ - "secret": "3gEsCfjlV2ugRwgpU#w1*WaW*wa4NXgGmpCfkbG3", - "ref": "refs/heads/develop", - "before": "28e1879d029cb852e4844d9c718537df08844e03", - "after": "bffeb74224043ba2feb48d137756c8a9331c449a", - "compare_url": "http://localhost:3000/gitea/webhooks/compare/28e1879d029cb852e4844d9c718537df08844e03...bffeb74224043ba2feb48d137756c8a9331c449a", - "commits": [ - { - "id": "bffeb74224043ba2feb48d137756c8a9331c449a", - "message": "Webhooks Yay!", - "url": "http://localhost:3000/gitea/webhooks/commit/bffeb74224043ba2feb48d137756c8a9331c449a", - "author": { - "name": "Gitea", - "email": "someone@gitea.io", - "username": "gitea" - }, - "committer": { - "name": "Gitea", - "email": "someone@gitea.io", - "username": "gitea" - }, - "timestamp": "2017-03-13T13:52:11-04:00" - } - ], - "repository": { - "id": 140, - "owner": { - "id": 1, - "login": "gitea", - "full_name": "Gitea", - "email": "someone@gitea.io", - "avatar_url": "https://localhost:3000/avatars/1", - "username": "gitea" - }, - "name": "webhooks", - "full_name": "gitea/webhooks", - "description": "", - "private": false, - "fork": false, - "html_url": "http://localhost:3000/gitea/webhooks", - "ssh_url": "ssh://gitea@localhost:2222/gitea/webhooks.git", - "clone_url": "http://localhost:3000/gitea/webhooks.git", - "website": "", - "stars_count": 0, - "forks_count": 1, - "watchers_count": 1, - "open_issues_count": 7, - "default_branch": "master", - "created_at": "2017-02-26T04:29:06-05:00", - "updated_at": "2017-03-13T13:51:58-04:00" - }, - "pusher": { - "id": 1, - "login": "gitea", - "full_name": "Gitea", - "email": "someone@gitea.io", - "avatar_url": "https://localhost:3000/avatars/1", - "username": "gitea" - }, - "sender": { - "id": 1, - "login": "gitea", - "full_name": "Gitea", - "email": "someone@gitea.io", - "avatar_url": "https://localhost:3000/avatars/1", - "username": "gitea" - } -} -``` - -### 範例 - -此範例示範在發生推送事件時,如何使用 webhook 觸發 php 程式。 -使用下列參數在您的儲存庫設定 Webhook 中建立一個 Gitea webhook: - -- 目標 URL: http://mydomain.com/webhook.php -- HTTP 請求方法:POST -- POST Content Type:application/json -- Secret:123 -- 觸發條件:推送事件 -- 啟用:勾選 - -現在請到您的伺服器上建立 webhook.php 檔案 - -``` - github.com/6543/go-version v1.3.1 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 -replace github.com/nektos/act => gitea.com/gitea/act v0.243.4 +replace github.com/nektos/act => gitea.com/gitea/act v0.259.1 exclude github.com/gofrs/uuid v3.2.0+incompatible diff --git a/go.sum b/go.sum index 5e32daf0c1..75187e15ae 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= +cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -42,11 +42,10 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= code.gitea.io/actions-proto-go v0.3.1 h1:PMyiQtBKb8dNnpEO2R5rcZdXSis+UQZVo/SciMtR1aU= code.gitea.io/actions-proto-go v0.3.1/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A= -code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= -code.gitea.io/gitea-vet v0.2.2 h1:TEOV/Glf38iGmKzKP0EB++Z5OSL4zGg3RrAvlwaMuvk= -code.gitea.io/gitea-vet v0.2.2/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= -code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M= -code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA= +code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI= +code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= +code.gitea.io/sdk/gitea v0.16.0 h1:gAfssETO1Hv9QbE+/nhWu7EjoFQYKt6kPoyDytQgw00= +code.gitea.io/sdk/gitea v0.16.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg= codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY= codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= @@ -54,8 +53,8 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= -gitea.com/gitea/act v0.243.4 h1:MuBHBLCJfpa6mzwwvs4xqQynrSP2RRzpHpWfTV16PmI= -gitea.com/gitea/act v0.243.4/go.mod h1:mabw6AZAiDgxGlK83orWLrNERSPvgBJzEUS3S7u2bHI= +gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw= +gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8= gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669 h1:RUBX+MK/TsDxpHmymaOaydfigEbbzqUnG1OTZU/HAeo= gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc= gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0= @@ -71,7 +70,6 @@ gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHq gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= -gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE= github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121 h1:r3qt8PCHnfjOv9PN3H+XXKmDA1dfFMIN1AislhlA/ps= github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121/go.mod h1:Ock8XgA7pvULhIaHGAk/cDnRfNrF9Jey81nPcc403iU= github.com/6543/go-version v1.3.1 h1:HvOp+Telns7HWJ2Xo/05YXQSB2bE0WmVgbHqwMPZT4U= @@ -85,15 +83,14 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0= github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw= -github.com/ClickHouse/clickhouse-go/v2 v2.13.4 h1:NcvYN9ONZn3vlPMfQVUBSG5LKz+1y2wk4vaaz5QZXIg= -github.com/ClickHouse/clickhouse-go/v2 v2.13.4/go.mod h1:u1AUh8E0XqN1sU1EDzbiGLTI4KWOd+lOHimNSsdyJec= +github.com/ClickHouse/clickhouse-go/v2 v2.14.3 h1:s9SuU3PfJrfJ4SDbVRo6XM2ZWlr7efvW9Z/ppUpE1vo= +github.com/ClickHouse/clickhouse-go/v2 v2.14.3/go.mod h1:qdw8IMGH4Y+PedKlf9QEhFO1ATTSFhh4exQRVIa3y2A= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= @@ -104,26 +101,24 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/RoaringBitmap/roaring v0.7.1/go.mod h1:jdT9ykXwHFNdJbEtxePexlFYH9LXucApeS0/+/g+p1I= -github.com/RoaringBitmap/roaring v1.5.0 h1:V0VCSiHjroItEYCM3guC8T83ehi5QMt3oM9EefTTOms= -github.com/RoaringBitmap/roaring v1.5.0/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE= -github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= -github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= -github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= -github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/RoaringBitmap/roaring v1.6.0 h1:dc7kRiroETgJcHhWX6BerXkZz2b3JgLGg9nTURJL/og= +github.com/RoaringBitmap/roaring v1.6.0/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE= +github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= +github.com/alecthomas/assert/v2 v2.6.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.9.1 h1:0O3lTQh9FxazJ4BYE/MOi/vDGuHn7B+6Bu902N2UZvU= -github.com/alecthomas/chroma/v2 v2.9.1/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw= +github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI= +github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= -github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= -github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +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/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= @@ -143,24 +138,22 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.1.10/go.mod h1:w0XsmFg8qg6cmpTtJ0z3pKgjTDBMMnI/+I2syrE6XBE= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= -github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.9.0 h1:g1YivPG8jOtrN013Fe8OBXubkiTwvm7/vG2vXz03ANU= +github.com/bits-and-blooms/bitset v1.9.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/blevesearch/bleve/v2 v2.0.5/go.mod h1:ZjWibgnbRX33c+vBRgla9QhPb4QOjD6fdVJ+R1Bk8LM= -github.com/blevesearch/bleve/v2 v2.3.9 h1:pUMvK0mxAexqasZcVj8lazmWnEW5XiV0tASIqANiNTQ= -github.com/blevesearch/bleve/v2 v2.3.9/go.mod h1:1PibElcjlQMQHF9uS9mRv58ODQgj4pCWHA1Wfd+qagU= +github.com/blevesearch/bleve/v2 v2.3.10 h1:z8V0wwGoL4rp7nG/O3qVVLYxUqCbEwskMt4iRJsPLgg= +github.com/blevesearch/bleve/v2 v2.3.10/go.mod h1:RJzeoeHC+vNHsoLR54+crS1HmOWpnH87fL70HAUCzIA= github.com/blevesearch/bleve_index_api v1.0.0/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4= -github.com/blevesearch/bleve_index_api v1.0.5 h1:Lc986kpC4Z0/n1g3gg8ul7H+lxgOQPcXb9SxvQGu+tw= -github.com/blevesearch/bleve_index_api v1.0.5/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms= -github.com/blevesearch/geo v0.1.17 h1:AguzI6/5mHXapzB0gE9IKWo+wWPHZmXZoscHcjFgAFA= -github.com/blevesearch/geo v0.1.17/go.mod h1:uRMGWG0HJYfWfFJpK3zTdnnr1K+ksZTuWKhXeSokfnM= +github.com/blevesearch/bleve_index_api v1.0.6 h1:gyUUxdsrvmW3jVhhYdCVL6h9dCjNT/geNU7PxGn37p8= +github.com/blevesearch/bleve_index_api v1.0.6/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms= +github.com/blevesearch/geo v0.1.18 h1:Np8jycHTZ5scFe7VEPLrDoHnnb9C4j636ue/CGrhtDw= +github.com/blevesearch/geo v0.1.18/go.mod h1:uRMGWG0HJYfWfFJpK3zTdnnr1K+ksZTuWKhXeSokfnM= github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y= @@ -169,8 +162,8 @@ github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+ github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc= github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= github.com/blevesearch/scorch_segment_api/v2 v2.0.1/go.mod h1:lq7yK2jQy1yQjtjTfU931aVqz7pYxEudHaDwOt1tXfU= -github.com/blevesearch/scorch_segment_api/v2 v2.1.5 h1:1g713kpCQZ8u4a3stRGBfrwVOuGRnmxOVU5MQkUPrHU= -github.com/blevesearch/scorch_segment_api/v2 v2.1.5/go.mod h1:f2nOkKS1HcjgIWZgDAErgBdxmr2eyt0Kn7IY+FU1Xe4= +github.com/blevesearch/scorch_segment_api/v2 v2.1.6 h1:CdekX/Ob6YCYmeHzD72cKpwzBjvkOGegHOqhAkXp6yA= +github.com/blevesearch/scorch_segment_api/v2 v2.1.6/go.mod h1:nQQYlp51XvoSVxcciBjtvuHPIVjlWrN1hX4qwK2cqdc= github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= @@ -184,30 +177,30 @@ github.com/blevesearch/vellum v1.0.4/go.mod h1:cMhywHI0de50f7Nj42YgvyD6bFJ2WkNRv github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI= github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k= github.com/blevesearch/zapx/v11 v11.2.0/go.mod h1:gN/a0alGw1FZt/YGTo1G6Z6XpDkeOfujX5exY9sCQQM= -github.com/blevesearch/zapx/v11 v11.3.9 h1:y3ijS4h4MJdmQ07MHASxat4owAixreK2xdo76w9ncrw= -github.com/blevesearch/zapx/v11 v11.3.9/go.mod h1:jcAYnQwlr+LqD2vLjDWjWiZDXDXGFqPbpPDRTd3XmS4= +github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk= +github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ= github.com/blevesearch/zapx/v12 v12.2.0/go.mod h1:fdjwvCwWWwJW/EYTYGtAp3gBA0geCYGLcVTtJEZnY6A= -github.com/blevesearch/zapx/v12 v12.3.9 h1:MXGLlZ03oxXH3DMJTZaBaRj2xb6t4wQVZeZK/wu1M6w= -github.com/blevesearch/zapx/v12 v12.3.9/go.mod h1:QXCMwmOkdLnMDgTN1P4CcuX5F851iUOtOwXbw0HMBYs= +github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s= +github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs= github.com/blevesearch/zapx/v13 v13.2.0/go.mod h1:o5rAy/lRS5JpAbITdrOHBS/TugWYbkcYZTz6VfEinAQ= -github.com/blevesearch/zapx/v13 v13.3.9 h1:+VAz9V0VmllHXlZV4DCvfYj0nqaZHgF3MeEHwOyRBwQ= -github.com/blevesearch/zapx/v13 v13.3.9/go.mod h1:s+WjNp4WSDtrBVBpa37DUOd7S/Gr/jTZ7ST/MbCVj/0= +github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8= +github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk= github.com/blevesearch/zapx/v14 v14.2.0/go.mod h1:GNgZusc1p4ot040cBQMRGEZobvwjCquiEKYh1xLFK9g= -github.com/blevesearch/zapx/v14 v14.3.9 h1:wuqxATgsTCNHM9xsOFOeFp8H2heZ/gMX/tsl9lRK8U4= -github.com/blevesearch/zapx/v14 v14.3.9/go.mod h1:MWZ4v8AzFBRurhDzkLvokFW8ljcq9Evm27mkWe8OGbM= +github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU= +github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns= github.com/blevesearch/zapx/v15 v15.2.0/go.mod h1:MmQceLpWfME4n1WrBFIwplhWmaQbQqLQARpaKUEOs/A= -github.com/blevesearch/zapx/v15 v15.3.12 h1:w/kU9aHyfMDEdwHGZzCiakC3HZ9z5gYlXaALDC4Dct8= -github.com/blevesearch/zapx/v15 v15.3.12/go.mod h1:tx53gDJS/7Oa3Je820cmVurqCuJ4dqdAy1kiDMV/IUo= +github.com/blevesearch/zapx/v15 v15.3.13 h1:6EkfaZiPlAxqXz0neniq35my6S48QI94W/wyhnpDHHQ= +github.com/blevesearch/zapx/v15 v15.3.13/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg= 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= github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= -github.com/bradfitz/gomemcache v0.0.0-20230611145640-acc696258285 h1:Dr+ezPI5ivhMn/3WOoB86XzMhie146DNaBbhaQWZHMY= -github.com/bradfitz/gomemcache v0.0.0-20230611145640-acc696258285/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= -github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= -github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= -github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= +github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bufbuild/connect-go v1.10.0 h1:QAJ3G9A1OYQW2Jbk3DeoJbkCxuKArrvZgDt47mjdTbg= github.com/bufbuild/connect-go v1.10.0/go.mod h1:CAIePUgkDR5pAFaylSMtNK45ANQjp9JvpluG20rhpV8= github.com/buildkite/terminal-to-html/v3 v3.9.1 h1:8SOCKFK9ntpYvPE3yUAXHiZYdQI4xf9o9S3wOX7x12A= @@ -224,23 +217,18 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ= github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k= github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A= github.com/couchbase/go-couchbase v0.1.1 h1:ClFXELcKj/ojyoTYbsY34QUrrYCBi/1G749sXSCkdhk= @@ -253,14 +241,18 @@ github.com/couchbase/goutils v0.1.2 h1:gWr8B6XNWPIhfalHNog3qQKfGiYyh4K4VhO3P2o9B github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE= github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= +github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= @@ -277,28 +269,27 @@ github.com/djherbis/nio/v3 v3.0.1/go.mod h1:Ng4h80pbZFMla1yKzm61cF0tqqilXZYrogmW github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= -github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dvyukov/go-fuzz v0.0.0-20210429054444-fca39067bc72/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= -github.com/editorconfig/editorconfig-core-go/v2 v2.5.2 h1:Z/G8cwnwOzGgTtvTutUhWPJ1ySUzuermioYMra7TuDQ= -github.com/editorconfig/editorconfig-core-go/v2 v2.5.2/go.mod h1:DoNm5QtDjTkizv0Oo1O+OJ92feoyUz3V4StZpOmP69E= +github.com/editorconfig/editorconfig-core-go/v2 v2.6.0 h1:5O8paxMLmi/5ONoKXzWNYxoSZU7+ITVbGcPga0IrzfE= +github.com/editorconfig/editorconfig-core-go/v2 v2.6.0/go.mod h1:hdTKe+hwa3mMnMn4JUQziT+yc3pF+6EVmK2LPbLZthE= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= -github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= -github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= 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= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= -github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y= -github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= +github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 h1:hH4PQfOndHDlpzYfLAAfl63E8Le6F2+EL/cdhlkyRJY= +github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= 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= @@ -310,13 +301,13 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethantkoenig/rupture v1.0.1 h1:6aAXghmvtnngMgQzy7SMGdicMvkV86V4n9fT0meE5E4= github.com/ethantkoenig/rupture v1.0.1/go.mod h1:Sjqo/nbffZp1pVVXNGhpugIjsWmuS9KiIB4GtpEBur4= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +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/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= @@ -327,18 +318,18 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46 h1:fYiA820jw7wmAvdXrHwMItxjJkra7dT9y8yiXhtzb94= +github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46/go.mod h1:i/TCLcdiX9Up/vs+Rp8c3yMbqp2Y4Y7Nh9uzGFCa5pM= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-ap/activitypub v0.0.0-20230807182453-602f717f6ca3 h1:Lxp2XxNZ+2bqlpJbKeQme5kMf9NqPJQB1l2eZLJ7W/A= -github.com/go-ap/activitypub v0.0.0-20230807182453-602f717f6ca3/go.mod h1:qw0WNf+PTG69Xu6mVqUluDuKl1VwVYdgntOZQFBZQ48= -github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea h1:ywGtLGVjJjMrq4mu35Qmu+NtlhlTk/gTayE6Bb4tQZk= -github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea/go.mod h1:SaTNjEEkp0q+w3pUS1ccyEL/lUrHteORlDq/e21mCc8= +github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b h1:VLD6IPBDkqEsOZ+EfLO6MayuHycZ0cv4BStTlRoZduo= +github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b/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-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= -github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +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.0.4/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= @@ -347,31 +338,30 @@ github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-co-op/gocron v1.31.1 h1:LZAuBlU0t3SPGUMJGhrJ6VuCc3CsrYzkzicygvVWlfA= github.com/go-co-op/gocron v1.31.1/go.mod h1:39f6KNSGVOU1LO/ZOoZfcSxwlsJDQOKSu8erN0SH48Y= -github.com/go-enry/go-enry/v2 v2.8.4 h1:QrY3hx/RiqCJJRbdU0MOcjfTM1a586J0WSooqdlJIhs= -github.com/go-enry/go-enry/v2 v2.8.4/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8= +github.com/go-enry/go-enry/v2 v2.8.6 h1:T6ljs5+qNiUTDqpfK5GUD5EvLNdDbf804u8iC30vw7U= +github.com/go-enry/go-enry/v2 v2.8.6/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8= github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo= github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4= github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY= +github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8= github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A= -github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +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.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= 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-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8= -github.com/go-ldap/ldap/v3 v3.4.5/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= +github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= @@ -416,7 +406,6 @@ github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUri github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v8 v8.4.0/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -425,8 +414,8 @@ github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewe github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0= github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= -github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= +github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-testfixtures/testfixtures/v3 v3.9.0 h1:938g5V+GWLVejm3Hc+nWCuEXRlcglZDDlN/t1gWzcSY= github.com/go-testfixtures/testfixtures/v3 v3.9.0/go.mod h1:cdsKD2ApFBjdog9jRsz6EJqF+LClq/hrwE9K/1Dzo4s= github.com/go-webauthn/webauthn v0.8.6 h1:bKMtL1qzd2WTFkf1mFTVbreYrwn7dsYmEPjTq6QN90E= @@ -459,7 +448,6 @@ github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/V github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= 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/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -509,6 +497,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -529,8 +518,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI= github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -553,13 +542,11 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/pprof v0.0.0-20230901174712-0191c66da455 h1:YhRUmI1ttDC4sxKY2V62BTI8hCXnyZBV9h38eAanInE= -github.com/google/pprof v0.0.0-20230901174712-0191c66da455/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ= +github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -588,22 +575,17 @@ github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -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 v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= -github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -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-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= 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.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM= -github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +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/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -615,61 +597,26 @@ github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= -github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.0/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= 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= @@ -677,8 +624,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jhillyerd/enmime v1.0.0 h1:8swYgO1fm68PllCKz5jiLzgD3axNUS388jr6BtRSsl8= -github.com/jhillyerd/enmime v1.0.0/go.mod h1:EktNOa/V6ka9yCrfoB2uxgefp1lno6OVdszW0iQ5LnM= +github.com/jhillyerd/enmime v1.0.1 h1:y6RyqIgBOI2hIinOXIzmeB+ITRVls0zTJIm5GwgXnjE= +github.com/jhillyerd/enmime v1.0.1/go.mod h1:LMMbm6oTlzWHghPavqHtOrP/NosVv3l42CUrZjn03/Q= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= @@ -707,8 +654,8 @@ github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 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.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -727,7 +674,6 @@ github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NB github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -737,11 +683,7 @@ github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++ github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= github.com/lestrrat-go/jwx v1.2.21/go.mod h1:9cfxnOH7G1gN75CaJP2hKGcxFEx5sPh1abRIA/ZJVh4= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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 v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis= @@ -761,42 +703,33 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= 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.77.0 h1:s3scqnWv/Zq/a5M766V0FKsLfOdFNdh/HEkuWCKbvT8= -github.com/markbates/goth v1.77.0/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc= +github.com/markbates/goth v1.78.0 h1:7VEIFDycJp9deyVv3YraGBPdD0ZYQW93Y3Aw1eVP3BY= +github.com/markbates/goth v1.78.0/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 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.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/meilisearch/meilisearch-go v0.25.0 h1:xIp+8YWterHuDvpdYlwQ4Qp7im3JlRHmSKiP0NvjyXs= -github.com/meilisearch/meilisearch-go v0.25.0/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0= +github.com/meilisearch/meilisearch-go v0.25.1 h1:D5wY22sn5kkpRH3uYMGlwltdUEq5regIFmO7awHz3Vo= +github.com/meilisearch/meilisearch-go v0.25.1/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= -github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= -github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= -github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= -github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= +github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= 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.63 h1:GbZ2oCvaUdgT5640WJOpyDhhDxvknAJU2/T3yurwcbQ= @@ -828,8 +761,8 @@ github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkF github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= -github.com/msteinert/pam v1.1.0 h1:VhLun/0n0kQYxiRBJJvVpC2jR6d21SWJFjpvUVj20Kc= -github.com/msteinert/pam v1.1.0/go.mod h1:M4FPeAW8g2ITO68W8gACDz13NDJyOQM9IQsQhrR6TOI= +github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE= +github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek= github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o= @@ -857,12 +790,12 @@ github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= -github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= -github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/paulmach/orb v0.10.0 h1:guVYVqzxHE/CQ1KpfGO077TR0ATHSNjp4s6XGLn3W9s= github.com/paulmach/orb v0.10.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= @@ -884,34 +817,33 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= -github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw= github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= -github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= 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.25 h1:0Is99a51w1iocdxKUzNYiBNwjoSlO2Klqzll98joVj4= -github.com/rhysd/actionlint v1.6.25/go.mod h1:Q+MtZKm1MdmJ9woOSKxLscMW7kU44/PShvjNy5ZKHA8= +github.com/rhysd/actionlint v1.6.26 h1:zi7jPZf3Ks14gCXYAAL47uBziyFlX7+Xwilqhexct9g= +github.com/rhysd/actionlint v1.6.26/go.mod h1:TIj1DlCgtYLOv5CH9wCK+WJTOr1qAdnFzkGi0IgSCO4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= -github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= -github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= 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.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -922,14 +854,15 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sassoftware/go-rpmutils v0.2.0 h1:pKW0HDYMFWQ5b4JQPiI3WI12hGsVoW0V8+GMoZiI/JE= @@ -939,7 +872,6 @@ github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -955,8 +887,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd 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.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= -github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= +github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= +github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck= @@ -964,9 +896,11 @@ github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYl github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= @@ -974,21 +908,18 @@ github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= 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/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc= github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -1026,17 +957,16 @@ github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6S github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= -github.com/valyala/fasthttp v1.49.0 h1:9FdvCpmxB74LH4dPb7IJ1cOSsluR07XG3I1txXWwJpE= -github.com/valyala/fasthttp v1.49.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= -github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= +github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= +github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= 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/go-gitlab v0.91.0 h1:o8S6TeaPq/b1cGcZ0UWnj4cBP5urtBdYqwmoLZyCdDY= -github.com/xanzy/go-gitlab v0.91.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= +github.com/xanzy/go-gitlab v0.93.1 h1:f7J33cw/P9b/8paIOoH0F3H+TFrswvWHs6yUgoTp9LY= +github.com/xanzy/go-gitlab v0.93.1/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= 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/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -1079,11 +1009,9 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= -go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= +go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= @@ -1097,59 +1025,45 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw= -go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM= -go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= -go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ= -go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= 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.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 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/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= -go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 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-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= 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= @@ -1160,10 +1074,12 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/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-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= 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.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= -golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= +golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= +golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= 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= @@ -1187,8 +1103,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/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 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= @@ -1202,7 +1118,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/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-20190813141303-74dc4d7220e7/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-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1231,14 +1146,14 @@ golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= 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= @@ -1248,8 +1163,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= 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= @@ -1264,15 +1179,14 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/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-20190222072716-a9d3bda3a223/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-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1295,7 +1209,6 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w 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-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1326,31 +1239,31 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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= -golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= 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= @@ -1365,9 +1278,9 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1384,7 +1297,6 @@ golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/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-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -1392,11 +1304,8 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw 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-20190823170909-c4a336ef6a2f/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-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/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= @@ -1405,7 +1314,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/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-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1430,7 +1338,6 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -1440,10 +1347,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 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.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= 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= @@ -1474,8 +1379,9 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 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/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= 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= @@ -1513,8 +1419,8 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a h1:a2MQQVoTo96JC9PMGtGBymLp7+/RzpFc2yX/9WfFg1c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= 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= @@ -1532,8 +1438,8 @@ google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= 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= @@ -1547,8 +1453,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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= @@ -1561,7 +1467,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -1590,43 +1495,26 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= 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.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= -modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= -modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= -modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0= -modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA= -modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0= -modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= -modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= -modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0= modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE= modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.15.0/go.mod h1:xRoGotBZ6dU+Zo2tca+2EqVEeMmOUBzHnhIwq4YrVnE= modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= 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= @@ -1634,8 +1522,7 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 h1:mUcz5b3FJbP5Cvdq7Khzn6J9OCUQJaBwgBkCR+MOwSs= strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY= -xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo= xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= -xorm.io/xorm v1.3.3-0.20230725140238-59b727260d35 h1:qjhRBMFRr3IdT2YR8VByOMfko8vxINiOPeOc267zuPI= -xorm.io/xorm v1.3.3-0.20230725140238-59b727260d35/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo= +xorm.io/xorm v1.3.7 h1:mLceAGu0b87r9pD4qXyxGHxifOXIIrAdVcA6k95/osw= +xorm.io/xorm v1.3.7/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw= diff --git a/models/actions/run.go b/models/actions/run.go index 8078613fb8..105bb304c9 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -168,13 +168,14 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err } // CancelRunningJobs cancels all running and waiting jobs associated with a specific workflow. -func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string) error { +func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error { // Find all runs in the specified repository, reference, and workflow with statuses 'Running' or 'Waiting'. runs, total, err := FindRuns(ctx, FindRunOptions{ - RepoID: repoID, - Ref: ref, - WorkflowID: workflowID, - Status: []Status{StatusRunning, StatusWaiting}, + RepoID: repoID, + Ref: ref, + WorkflowID: workflowID, + TriggerEvent: event, + Status: []Status{StatusRunning, StatusWaiting}, }) if err != nil { return err diff --git a/models/actions/run_list.go b/models/actions/run_list.go index db36f6df98..eda47b8e6a 100644 --- a/models/actions/run_list.go +++ b/models/actions/run_list.go @@ -10,6 +10,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" + webhook_module "code.gitea.io/gitea/modules/webhook" "xorm.io/builder" ) @@ -71,6 +72,7 @@ type FindRunOptions struct { WorkflowID string Ref string // the commit/tag/… that caused this workflow TriggerUserID int64 + TriggerEvent webhook_module.HookEventType Approved bool // not util.OptionalBool, it works only when it's true Status []Status } @@ -98,6 +100,9 @@ func (opts FindRunOptions) toConds() builder.Cond { if opts.Ref != "" { cond = cond.And(builder.Eq{"ref": opts.Ref}) } + if opts.TriggerEvent != "" { + cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent}) + } return cond } diff --git a/models/actions/runner.go b/models/actions/runner.go index ec6b49cf16..aaeb80ce63 100644 --- a/models/actions/runner.go +++ b/models/actions/runner.go @@ -51,6 +51,11 @@ type ActionRunner struct { Deleted timeutil.TimeStamp `xorm:"deleted"` } +const ( + RunnerOfflineTime = time.Minute + RunnerIdleTime = 10 * time.Second +) + // BelongsToOwnerName before calling, should guarantee that all attributes are loaded func (r *ActionRunner) BelongsToOwnerName() string { if r.RepoID != 0 { @@ -76,11 +81,12 @@ func (r *ActionRunner) BelongsToOwnerType() types.OwnerType { return types.OwnerTypeSystemGlobal } +// if the logic here changed, you should also modify FindRunnerOptions.ToCond func (r *ActionRunner) Status() runnerv1.RunnerStatus { - if time.Since(r.LastOnline.AsTime()) > time.Minute { + if time.Since(r.LastOnline.AsTime()) > RunnerOfflineTime { return runnerv1.RunnerStatus_RUNNER_STATUS_OFFLINE } - if time.Since(r.LastActive.AsTime()) > 10*time.Second { + if time.Since(r.LastActive.AsTime()) > RunnerIdleTime { return runnerv1.RunnerStatus_RUNNER_STATUS_IDLE } return runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE @@ -153,6 +159,7 @@ type FindRunnerOptions struct { OwnerID int64 Sort string Filter string + IsOnline util.OptionalBool WithAvailable bool // not only runners belong to, but also runners can be used } @@ -178,6 +185,12 @@ func (opts FindRunnerOptions) toCond() builder.Cond { if opts.Filter != "" { cond = cond.And(builder.Like{"name", opts.Filter}) } + + if opts.IsOnline.IsTrue() { + cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()}) + } else if opts.IsOnline.IsFalse() { + cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()}) + } return cond } @@ -266,3 +279,27 @@ func CreateRunner(ctx context.Context, t *ActionRunner) error { _, err := db.GetEngine(ctx).Insert(t) return err } + +func CountRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { + // Only affect action runners were a owner ID is set, as actions runners + // could also be created on a repository. + return db.GetEngine(ctx).Table("action_runner"). + Join("LEFT", "user", "`action_runner`.owner_id = `user`.id"). + Where("`action_runner`.owner_id != ?", 0). + And(builder.IsNull{"`user`.id"}). + Count(new(ActionRunner)) +} + +func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { + subQuery := builder.Select("`action_runner`.id"). + From("`action_runner`"). + Join("LEFT", "user", "`action_runner`.owner_id = `user`.id"). + Where(builder.Neq{"`action_runner`.owner_id": 0}). + And(builder.IsNull{"`user`.id"}) + b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`") + res, err := db.GetEngine(ctx).Exec(b) + if err != nil { + return 0, err + } + return res.RowsAffected() +} diff --git a/models/actions/runner_token.go b/models/actions/runner_token.go index fabd6c644c..cf4e90f3f1 100644 --- a/models/actions/runner_token.go +++ b/models/actions/runner_token.go @@ -22,7 +22,7 @@ type ActionRunnerToken struct { Owner *user_model.User `xorm:"-"` RepoID int64 `xorm:"index"` // repo level runner, if orgid also is zero, then it's a global Repo *repo_model.Repository `xorm:"-"` - IsActive bool + IsActive bool // true means it can be used Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` @@ -57,7 +57,7 @@ func UpdateRunnerToken(ctx context.Context, r *ActionRunnerToken, cols ...string return err } -// NewRunnerToken creates a new runner token +// NewRunnerToken creates a new active runner token and invalidate all old tokens func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) { token, err := util.CryptoRandomString(40) if err != nil { @@ -66,17 +66,27 @@ func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerTo runnerToken := &ActionRunnerToken{ OwnerID: ownerID, RepoID: repoID, - IsActive: false, + IsActive: true, Token: token, } - _, err = db.GetEngine(ctx).Insert(runnerToken) - return runnerToken, err + + return runnerToken, db.WithTx(ctx, func(ctx context.Context) error { + if _, err := db.GetEngine(ctx).Where("owner_id =? AND repo_id = ?", ownerID, repoID).Cols("is_active").Update(&ActionRunnerToken{ + IsActive: false, + }); err != nil { + return err + } + + _, err = db.GetEngine(ctx).Insert(runnerToken) + return err + }) } -// GetUnactivatedRunnerToken returns a unactivated runner token -func GetUnactivatedRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) { +// GetLastestRunnerToken returns the latest runner token +func GetLastestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) { var runnerToken ActionRunnerToken - has, err := db.GetEngine(ctx).Where("owner_id=? AND repo_id=? AND is_active=?", ownerID, repoID, false).OrderBy("id DESC").Get(&runnerToken) + has, err := db.GetEngine(ctx).Where("owner_id=? AND repo_id=?", ownerID, repoID). + OrderBy("id DESC").Get(&runnerToken) if err != nil { return nil, err } else if !has { diff --git a/models/actions/schedule.go b/models/actions/schedule.go index 34d23f1c01..d450e7aa07 100644 --- a/models/actions/schedule.go +++ b/models/actions/schedule.go @@ -5,6 +5,7 @@ package actions import ( "context" + "fmt" "time" "code.gitea.io/gitea/models/db" @@ -118,3 +119,22 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error { return committer.Commit() } + +func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error { + // If actions disabled when there is schedule task, this will remove the outdated schedule tasks + // There is no other place we can do this because the app.ini will be changed manually + if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil { + return fmt.Errorf("DeleteCronTaskByRepo: %v", err) + } + // cancel running cron jobs of this repository and delete old schedules + if err := CancelRunningJobs( + ctx, + repo.ID, + repo.DefaultBranch, + "", + webhook_module.HookEventSchedule, + ); err != nil { + return fmt.Errorf("CancelRunningJobs: %v", err) + } + return nil +} diff --git a/models/actions/task_output.go b/models/actions/task_output.go index 5291a783d6..eab5b93118 100644 --- a/models/actions/task_output.go +++ b/models/actions/task_output.go @@ -20,6 +20,10 @@ type ActionTaskOutput struct { OutputValue string `xorm:"MEDIUMTEXT"` } +func init() { + db.RegisterModel(new(ActionTaskOutput)) +} + // FindTaskOutputByTaskID returns the outputs of the task. func FindTaskOutputByTaskID(ctx context.Context, taskID int64) ([]*ActionTaskOutput, error) { var outputs []*ActionTaskOutput diff --git a/models/actions/variable.go b/models/actions/variable.go index e0bb59ccbe..66e2007946 100644 --- a/models/actions/variable.go +++ b/models/actions/variable.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -95,3 +96,35 @@ func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) }) return count != 0, err } + +func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) { + variables := map[string]string{} + + // Global + globalVariables, err := FindVariables(ctx, FindVariablesOpts{}) + if err != nil { + log.Error("find global variables: %v", err) + return nil, err + } + + // Org / User level + ownerVariables, err := FindVariables(ctx, FindVariablesOpts{OwnerID: run.Repo.OwnerID}) + if err != nil { + log.Error("find variables of org: %d, error: %v", run.Repo.OwnerID, err) + return nil, err + } + + // Repo level + repoVariables, err := FindVariables(ctx, FindVariablesOpts{RepoID: run.RepoID}) + if err != nil { + log.Error("find variables of repo: %d, error: %v", run.RepoID, err) + return nil, err + } + + // Level precedence: Repo > Org / User > Global + for _, v := range append(globalVariables, append(ownerVariables, repoVariables...)...) { + variables[v.Name] = v.Data + } + + return variables, nil +} diff --git a/models/activities/action.go b/models/activities/action.go index 07c8053425..238c2f2e92 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -140,7 +140,7 @@ func (at ActionType) InActions(actions ...string) bool { // used in template render. type Action struct { ID int64 `xorm:"pk autoincr"` - UserID int64 // Receiver user id. + UserID int64 `xorm:"INDEX"` // Receiver user id. OpType ActionType ActUserID int64 // Action user id. ActUser *user_model.User `xorm:"-"` @@ -208,91 +208,91 @@ func (a *Action) loadRepo(ctx context.Context) { } // GetActFullName gets the action's user full name. -func (a *Action) GetActFullName() string { - a.LoadActUser(db.DefaultContext) +func (a *Action) GetActFullName(ctx context.Context) string { + a.LoadActUser(ctx) return a.ActUser.FullName } // GetActUserName gets the action's user name. -func (a *Action) GetActUserName() string { - a.LoadActUser(db.DefaultContext) +func (a *Action) GetActUserName(ctx context.Context) string { + a.LoadActUser(ctx) return a.ActUser.Name } // ShortActUserName gets the action's user name trimmed to max 20 // chars. -func (a *Action) ShortActUserName() string { - return base.EllipsisString(a.GetActUserName(), 20) +func (a *Action) ShortActUserName(ctx context.Context) string { + return base.EllipsisString(a.GetActUserName(ctx), 20) } // GetDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank. -func (a *Action) GetDisplayName() string { +func (a *Action) GetDisplayName(ctx context.Context) string { if setting.UI.DefaultShowFullName { - trimmedFullName := strings.TrimSpace(a.GetActFullName()) + trimmedFullName := strings.TrimSpace(a.GetActFullName(ctx)) if len(trimmedFullName) > 0 { return trimmedFullName } } - return a.ShortActUserName() + return a.ShortActUserName(ctx) } // GetDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME -func (a *Action) GetDisplayNameTitle() string { +func (a *Action) GetDisplayNameTitle(ctx context.Context) string { if setting.UI.DefaultShowFullName { - return a.ShortActUserName() + return a.ShortActUserName(ctx) } - return a.GetActFullName() + return a.GetActFullName(ctx) } // GetRepoUserName returns the name of the action repository owner. -func (a *Action) GetRepoUserName() string { - a.loadRepo(db.DefaultContext) +func (a *Action) GetRepoUserName(ctx context.Context) string { + a.loadRepo(ctx) return a.Repo.OwnerName } // ShortRepoUserName returns the name of the action repository owner // trimmed to max 20 chars. -func (a *Action) ShortRepoUserName() string { - return base.EllipsisString(a.GetRepoUserName(), 20) +func (a *Action) ShortRepoUserName(ctx context.Context) string { + return base.EllipsisString(a.GetRepoUserName(ctx), 20) } // GetRepoName returns the name of the action repository. -func (a *Action) GetRepoName() string { - a.loadRepo(db.DefaultContext) +func (a *Action) GetRepoName(ctx context.Context) string { + a.loadRepo(ctx) return a.Repo.Name } // ShortRepoName returns the name of the action repository // trimmed to max 33 chars. -func (a *Action) ShortRepoName() string { - return base.EllipsisString(a.GetRepoName(), 33) +func (a *Action) ShortRepoName(ctx context.Context) string { + return base.EllipsisString(a.GetRepoName(ctx), 33) } // GetRepoPath returns the virtual path to the action repository. -func (a *Action) GetRepoPath() string { - return path.Join(a.GetRepoUserName(), a.GetRepoName()) +func (a *Action) GetRepoPath(ctx context.Context) string { + return path.Join(a.GetRepoUserName(ctx), a.GetRepoName(ctx)) } // ShortRepoPath returns the virtual path to the action repository // trimmed to max 20 + 1 + 33 chars. -func (a *Action) ShortRepoPath() string { - return path.Join(a.ShortRepoUserName(), a.ShortRepoName()) +func (a *Action) ShortRepoPath(ctx context.Context) string { + return path.Join(a.ShortRepoUserName(ctx), a.ShortRepoName(ctx)) } // GetRepoLink returns relative link to action repository. -func (a *Action) GetRepoLink() string { +func (a *Action) GetRepoLink(ctx context.Context) string { // path.Join will skip empty strings - return path.Join(setting.AppSubURL, "/", url.PathEscape(a.GetRepoUserName()), url.PathEscape(a.GetRepoName())) + return path.Join(setting.AppSubURL, "/", url.PathEscape(a.GetRepoUserName(ctx)), url.PathEscape(a.GetRepoName(ctx))) } // GetRepoAbsoluteLink returns the absolute link to action repository. -func (a *Action) GetRepoAbsoluteLink() string { - return setting.AppURL + url.PathEscape(a.GetRepoUserName()) + "/" + url.PathEscape(a.GetRepoName()) +func (a *Action) GetRepoAbsoluteLink(ctx context.Context) string { + return setting.AppURL + url.PathEscape(a.GetRepoUserName(ctx)) + "/" + url.PathEscape(a.GetRepoName(ctx)) } // GetCommentHTMLURL returns link to action comment. -func (a *Action) GetCommentHTMLURL() string { - return a.getCommentHTMLURL(db.DefaultContext) +func (a *Action) GetCommentHTMLURL(ctx context.Context) string { + return a.getCommentHTMLURL(ctx) } func (a *Action) loadComment(ctx context.Context) (err error) { @@ -309,7 +309,7 @@ func (a *Action) getCommentHTMLURL(ctx context.Context) string { } _ = a.loadComment(ctx) if a.Comment != nil { - return a.Comment.HTMLURL() + return a.Comment.HTMLURL(ctx) } if len(a.GetIssueInfos()) == 0 { return "#" @@ -334,8 +334,8 @@ func (a *Action) getCommentHTMLURL(ctx context.Context) string { } // GetCommentLink returns link to action comment. -func (a *Action) GetCommentLink() string { - return a.getCommentLink(db.DefaultContext) +func (a *Action) GetCommentLink(ctx context.Context) string { + return a.getCommentLink(ctx) } func (a *Action) getCommentLink(ctx context.Context) string { @@ -344,7 +344,7 @@ func (a *Action) getCommentLink(ctx context.Context) string { } _ = a.loadComment(ctx) if a.Comment != nil { - return a.Comment.Link() + return a.Comment.Link(ctx) } if len(a.GetIssueInfos()) == 0 { return "#" @@ -374,8 +374,8 @@ func (a *Action) GetBranch() string { } // GetRefLink returns the action's ref link. -func (a *Action) GetRefLink() string { - return git.RefURL(a.GetRepoLink(), a.RefName) +func (a *Action) GetRefLink(ctx context.Context) string { + return git.RefURL(a.GetRepoLink(ctx), a.RefName) } // GetTag returns the action's repository tag. @@ -393,17 +393,20 @@ func (a *Action) GetCreate() time.Time { return a.CreatedUnix.AsTime() } -// GetIssueInfos returns a list of issues associated with -// the action. +// GetIssueInfos returns a list of associated information with the action. func (a *Action) GetIssueInfos() []string { - return 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, "") + } + return ret } -// GetIssueTitle returns the title of first issue associated -// with the action. This function will be invoked in template so keep db.DefaultContext here -func (a *Action) GetIssueTitle() string { +// GetIssueTitle returns the title of first issue associated with the action. +func (a *Action) GetIssueTitle(ctx context.Context) string { index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64) - issue, err := issues_model.GetIssueByIndex(db.DefaultContext, a.RepoID, index) + issue, err := issues_model.GetIssueByIndex(ctx, a.RepoID, index) if err != nil { log.Error("GetIssueByIndex: %v", err) return "500 when get issue" @@ -442,7 +445,7 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo") } - cond, err := activityQueryCondition(opts) + cond, err := activityQueryCondition(ctx, opts) if err != nil { return nil, 0, err } @@ -473,11 +476,11 @@ func ActivityReadable(user, doer *user_model.User) bool { doer != nil && (doer.IsAdmin || user.ID == doer.ID) } -func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) { +func activityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.Cond, error) { cond := builder.NewCond() if opts.RequestedTeam != nil && opts.RequestedUser == nil { - org, err := user_model.GetUserByID(db.DefaultContext, opts.RequestedTeam.OrgID) + org, err := user_model.GetUserByID(ctx, opts.RequestedTeam.OrgID) if err != nil { return nil, err } @@ -564,12 +567,12 @@ func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) { } // DeleteOldActions deletes all old actions from database. -func DeleteOldActions(olderThan time.Duration) (err error) { +func DeleteOldActions(ctx context.Context, olderThan time.Duration) (err error) { if olderThan <= 0 { return nil } - _, err = db.GetEngine(db.DefaultContext).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Action{}) + _, err = db.GetEngine(ctx).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Action{}) return err } @@ -679,8 +682,8 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error { } // NotifyWatchersActions creates batch of actions for every watcher. -func NotifyWatchersActions(acts []*Action) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func NotifyWatchersActions(ctx context.Context, acts []*Action) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/activities/action_test.go b/models/activities/action_test.go index 9a42740880..5467bd35fb 100644 --- a/models/activities/action_test.go +++ b/models/activities/action_test.go @@ -24,7 +24,7 @@ func TestAction_GetRepoPath(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) action := &activities_model.Action{RepoID: repo.ID} - assert.Equal(t, path.Join(owner.Name, repo.Name), action.GetRepoPath()) + assert.Equal(t, path.Join(owner.Name, repo.Name), action.GetRepoPath(db.DefaultContext)) } func TestAction_GetRepoLink(t *testing.T) { @@ -35,9 +35,9 @@ func TestAction_GetRepoLink(t *testing.T) { action := &activities_model.Action{RepoID: repo.ID, CommentID: comment.ID} setting.AppSubURL = "/suburl" expected := path.Join(setting.AppSubURL, owner.Name, repo.Name) - assert.Equal(t, expected, action.GetRepoLink()) - assert.Equal(t, repo.HTMLURL(), action.GetRepoAbsoluteLink()) - assert.Equal(t, comment.HTMLURL(), action.GetCommentHTMLURL()) + assert.Equal(t, expected, action.GetRepoLink(db.DefaultContext)) + assert.Equal(t, repo.HTMLURL(), action.GetRepoAbsoluteLink(db.DefaultContext)) + assert.Equal(t, comment.HTMLURL(db.DefaultContext), action.GetCommentHTMLURL(db.DefaultContext)) } func TestGetFeeds(t *testing.T) { diff --git a/models/activities/notification.go b/models/activities/notification.go index ef263ef735..29b02b5fdf 100644 --- a/models/activities/notification.go +++ b/models/activities/notification.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" "xorm.io/builder" "xorm.io/xorm" @@ -175,8 +176,8 @@ func CreateRepoTransferNotification(ctx context.Context, doer, newOwner *user_mo // CreateOrUpdateIssueNotifications creates an issue notification // for each watcher, or updates it if already exists // receiverID > 0 just send to receiver, else send to all watcher -func CreateOrUpdateIssueNotifications(issueID, commentID, notificationAuthorID, receiverID int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func CreateOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -435,21 +436,21 @@ func (n *Notification) loadUser(ctx context.Context) (err error) { } // GetRepo returns the repo of the notification -func (n *Notification) GetRepo() (*repo_model.Repository, error) { - return n.Repository, n.loadRepo(db.DefaultContext) +func (n *Notification) GetRepo(ctx context.Context) (*repo_model.Repository, error) { + return n.Repository, n.loadRepo(ctx) } // GetIssue returns the issue of the notification -func (n *Notification) GetIssue() (*issues_model.Issue, error) { - return n.Issue, n.loadIssue(db.DefaultContext) +func (n *Notification) GetIssue(ctx context.Context) (*issues_model.Issue, error) { + return n.Issue, n.loadIssue(ctx) } // HTMLURL formats a URL-string to the notification -func (n *Notification) HTMLURL() string { +func (n *Notification) HTMLURL(ctx context.Context) string { switch n.Source { case NotificationSourceIssue, NotificationSourcePullRequest: if n.Comment != nil { - return n.Comment.HTMLURL() + return n.Comment.HTMLURL(ctx) } return n.Issue.HTMLURL() case NotificationSourceCommit: @@ -461,11 +462,11 @@ func (n *Notification) HTMLURL() string { } // Link formats a relative URL-string to the notification -func (n *Notification) Link() string { +func (n *Notification) Link(ctx context.Context) string { switch n.Source { case NotificationSourceIssue, NotificationSourcePullRequest: if n.Comment != nil { - return n.Comment.Link() + return n.Comment.Link(ctx) } return n.Issue.Link() case NotificationSourceCommit: @@ -733,12 +734,12 @@ type UserIDCount struct { } // GetUIDsAndNotificationCounts between the two provided times -func GetUIDsAndNotificationCounts(since, until timeutil.TimeStamp) ([]UserIDCount, error) { +func GetUIDsAndNotificationCounts(ctx context.Context, since, until timeutil.TimeStamp) ([]UserIDCount, error) { sql := `SELECT user_id, count(*) AS count FROM notification ` + `WHERE user_id IN (SELECT user_id FROM notification WHERE updated_unix >= ? AND ` + `updated_unix < ?) AND status = ? GROUP BY user_id` var res []UserIDCount - return res, db.GetEngine(db.DefaultContext).SQL(sql, since, until, NotificationStatusUnread).Find(&res) + return res, db.GetEngine(ctx).SQL(sql, since, until, NotificationStatusUnread).Find(&res) } // SetIssueReadBy sets issue to be read by given user. @@ -821,3 +822,31 @@ func UpdateNotificationStatuses(ctx context.Context, user *user_model.User, curr Update(n) return err } + +// LoadIssuePullRequests loads all issues' pull requests if possible +func (nl NotificationList) LoadIssuePullRequests(ctx context.Context) error { + issues := make(map[int64]*issues_model.Issue, len(nl)) + for _, notification := range nl { + if notification.Issue != nil && notification.Issue.IsPull && notification.Issue.PullRequest == nil { + issues[notification.Issue.ID] = notification.Issue + } + } + + if len(issues) == 0 { + return nil + } + + pulls, err := issues_model.GetPullRequestByIssueIDs(ctx, util.KeysOfMap(issues)) + if err != nil { + return err + } + + for _, pull := range pulls { + if issue := issues[pull.IssueID]; issue != nil { + issue.PullRequest = pull + issue.PullRequest.Issue = issue + } + } + + return nil +} diff --git a/models/activities/notification_test.go b/models/activities/notification_test.go index 2d4c369a99..b90ce70536 100644 --- a/models/activities/notification_test.go +++ b/models/activities/notification_test.go @@ -20,7 +20,7 @@ func TestCreateOrUpdateIssueNotifications(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - assert.NoError(t, activities_model.CreateOrUpdateIssueNotifications(issue.ID, 0, 2, 0)) + assert.NoError(t, activities_model.CreateOrUpdateIssueNotifications(db.DefaultContext, issue.ID, 0, 2, 0)) // User 9 is inactive, thus notifications for user 1 and 4 are created notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{UserID: 1, IssueID: issue.ID}) @@ -50,7 +50,7 @@ func TestNotificationsForUser(t *testing.T) { func TestNotification_GetRepo(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{RepoID: 1}) - repo, err := notf.GetRepo() + repo, err := notf.GetRepo(db.DefaultContext) assert.NoError(t, err) assert.Equal(t, repo, notf.Repository) assert.EqualValues(t, notf.RepoID, repo.ID) @@ -59,7 +59,7 @@ func TestNotification_GetRepo(t *testing.T) { func TestNotification_GetIssue(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) notf := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{RepoID: 1}) - issue, err := notf.GetIssue() + issue, err := notf.GetIssue(db.DefaultContext) assert.NoError(t, err) assert.Equal(t, issue, notf.Issue) assert.EqualValues(t, notf.IssueID, issue.ID) diff --git a/models/activities/repo_activity.go b/models/activities/repo_activity.go index 509f9caaf3..91f5ac12bd 100644 --- a/models/activities/repo_activity.go +++ b/models/activities/repo_activity.go @@ -342,7 +342,7 @@ func (stats *ActivityStats) FillReleases(ctx context.Context, repoID int64, from // Published releases list sess := releasesForActivityStatement(ctx, repoID, fromTime) - sess.OrderBy("release.created_unix DESC") + sess.OrderBy("`release`.created_unix DESC") stats.PublishedReleases = make([]*repo_model.Release, 0) if err = sess.Find(&stats.PublishedReleases); err != nil { return err @@ -350,7 +350,7 @@ func (stats *ActivityStats) FillReleases(ctx context.Context, repoID int64, from // Published releases authors sess = releasesForActivityStatement(ctx, repoID, fromTime) - if _, err = sess.Select("count(distinct release.publisher_id) as `count`").Table("release").Get(&count); err != nil { + if _, err = sess.Select("count(distinct `release`.publisher_id) as `count`").Table("release").Get(&count); err != nil { return err } stats.PublishedReleaseAuthorCount = count @@ -359,7 +359,7 @@ func (stats *ActivityStats) FillReleases(ctx context.Context, repoID int64, from } func releasesForActivityStatement(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session { - return db.GetEngine(ctx).Where("release.repo_id = ?", repoID). - And("release.is_draft = ?", false). - And("release.created_unix >= ?", fromTime.Unix()) + return db.GetEngine(ctx).Where("`release`.repo_id = ?", repoID). + And("`release`.is_draft = ?", false). + And("`release`.created_unix >= ?", fromTime.Unix()) } diff --git a/models/activities/user_heatmap.go b/models/activities/user_heatmap.go index 3320799526..78fcd76d43 100644 --- a/models/activities/user_heatmap.go +++ b/models/activities/user_heatmap.go @@ -4,6 +4,8 @@ package activities import ( + "context" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" @@ -18,16 +20,16 @@ type UserHeatmapData struct { } // GetUserHeatmapDataByUser returns an array of UserHeatmapData -func GetUserHeatmapDataByUser(user, doer *user_model.User) ([]*UserHeatmapData, error) { - return getUserHeatmapData(user, nil, doer) +func GetUserHeatmapDataByUser(ctx context.Context, user, doer *user_model.User) ([]*UserHeatmapData, error) { + return getUserHeatmapData(ctx, user, nil, doer) } // GetUserHeatmapDataByUserTeam returns an array of UserHeatmapData -func GetUserHeatmapDataByUserTeam(user *user_model.User, team *organization.Team, doer *user_model.User) ([]*UserHeatmapData, error) { - return getUserHeatmapData(user, team, doer) +func GetUserHeatmapDataByUserTeam(ctx context.Context, user *user_model.User, team *organization.Team, doer *user_model.User) ([]*UserHeatmapData, error) { + return getUserHeatmapData(ctx, user, team, doer) } -func getUserHeatmapData(user *user_model.User, team *organization.Team, doer *user_model.User) ([]*UserHeatmapData, error) { +func getUserHeatmapData(ctx context.Context, user *user_model.User, team *organization.Team, doer *user_model.User) ([]*UserHeatmapData, error) { hdata := make([]*UserHeatmapData, 0) if !ActivityReadable(user, doer) { @@ -45,7 +47,7 @@ func getUserHeatmapData(user *user_model.User, team *organization.Team, doer *us groupByName = groupBy } - cond, err := activityQueryCondition(GetFeedsOptions{ + cond, err := activityQueryCondition(ctx, GetFeedsOptions{ RequestedUser: user, RequestedTeam: team, Actor: doer, @@ -60,7 +62,7 @@ func getUserHeatmapData(user *user_model.User, team *organization.Team, doer *us return nil, err } - return hdata, db.GetEngine(db.DefaultContext). + return hdata, db.GetEngine(ctx). Select(groupBy+" AS timestamp, count(user_id) as contributions"). Table("action"). Where(cond). diff --git a/models/activities/user_heatmap_test.go b/models/activities/user_heatmap_test.go index 98df7b38aa..657f0f043c 100644 --- a/models/activities/user_heatmap_test.go +++ b/models/activities/user_heatmap_test.go @@ -83,7 +83,7 @@ func TestGetUserHeatmapDataByUser(t *testing.T) { assert.NoError(t, err) // Get the heatmap and compare - heatmap, err := activities_model.GetUserHeatmapDataByUser(user, doer) + heatmap, err := activities_model.GetUserHeatmapDataByUser(db.DefaultContext, user, doer) var contributions int for _, hm := range heatmap { contributions += int(hm.Contributions) diff --git a/models/asymkey/gpg_key.go b/models/asymkey/gpg_key.go index e5e5fdb2f3..15e0de7ff5 100644 --- a/models/asymkey/gpg_key.go +++ b/models/asymkey/gpg_key.go @@ -88,14 +88,13 @@ func ListGPGKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([] } // CountUserGPGKeys return number of gpg keys a user own -func CountUserGPGKeys(userID int64) (int64, error) { - return db.GetEngine(db.DefaultContext).Where("owner_id=? AND primary_key_id=''", userID).Count(&GPGKey{}) +func CountUserGPGKeys(ctx context.Context, userID int64) (int64, error) { + return db.GetEngine(ctx).Where("owner_id=? AND primary_key_id=''", userID).Count(&GPGKey{}) } -// GetGPGKeyByID returns public key by given ID. -func GetGPGKeyByID(keyID int64) (*GPGKey, error) { +func GetGPGKeyForUserByID(ctx context.Context, ownerID, keyID int64) (*GPGKey, error) { key := new(GPGKey) - has, err := db.GetEngine(db.DefaultContext).ID(keyID).Get(key) + has, err := db.GetEngine(ctx).Where("id=? AND owner_id=?", keyID, ownerID).Get(key) if err != nil { return nil, err } else if !has { @@ -105,9 +104,9 @@ func GetGPGKeyByID(keyID int64) (*GPGKey, error) { } // GetGPGKeysByKeyID returns public key by given ID. -func GetGPGKeysByKeyID(keyID string) ([]*GPGKey, error) { +func GetGPGKeysByKeyID(ctx context.Context, keyID string) ([]*GPGKey, error) { keys := make([]*GPGKey, 0, 1) - return keys, db.GetEngine(db.DefaultContext).Where("key_id=?", keyID).Find(&keys) + return keys, db.GetEngine(ctx).Where("key_id=?", keyID).Find(&keys) } // GPGKeyToEntity retrieve the imported key and the traducted entity @@ -224,8 +223,8 @@ func deleteGPGKey(ctx context.Context, keyID string) (int64, error) { } // DeleteGPGKey deletes GPG key information in database. -func DeleteGPGKey(doer *user_model.User, id int64) (err error) { - key, err := GetGPGKeyByID(id) +func DeleteGPGKey(ctx context.Context, doer *user_model.User, id int64) (err error) { + key, err := GetGPGKeyForUserByID(ctx, doer.ID, id) if err != nil { if IsErrGPGKeyNotExist(err) { return nil @@ -233,12 +232,7 @@ func DeleteGPGKey(doer *user_model.User, id int64) (err error) { return fmt.Errorf("GetPublicKeyByID: %w", err) } - // Check if user has access to delete this key. - if !doer.IsAdmin && doer.ID != key.OwnerID { - return ErrGPGKeyAccessDenied{doer.ID, key.ID} - } - - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/asymkey/gpg_key_add.go b/models/asymkey/gpg_key_add.go index 6926fd2143..11124b1366 100644 --- a/models/asymkey/gpg_key_add.go +++ b/models/asymkey/gpg_key_add.go @@ -66,13 +66,13 @@ func addGPGSubKey(ctx context.Context, key *GPGKey) (err error) { } // AddGPGKey adds new public key to database. -func AddGPGKey(ownerID int64, content, token, signature string) ([]*GPGKey, error) { +func AddGPGKey(ctx context.Context, ownerID int64, content, token, signature string) ([]*GPGKey, error) { ekeys, err := checkArmoredGPGKeyString(content) if err != nil { return nil, err } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } diff --git a/models/asymkey/gpg_key_commit_verification.go b/models/asymkey/gpg_key_commit_verification.go index bf0fdd9a9a..8ac4364404 100644 --- a/models/asymkey/gpg_key_commit_verification.go +++ b/models/asymkey/gpg_key_commit_verification.go @@ -392,7 +392,7 @@ func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s if keyID == "" { return nil } - keys, err := GetGPGKeysByKeyID(keyID) + keys, err := GetGPGKeysByKeyID(ctx, keyID) if err != nil { log.Error("GetGPGKeysByKeyID: %v", err) return &CommitVerification{ @@ -407,7 +407,7 @@ func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s for _, key := range keys { var primaryKeys []*GPGKey if key.PrimaryKeyID != "" { - primaryKeys, err = GetGPGKeysByKeyID(key.PrimaryKeyID) + primaryKeys, err = GetGPGKeysByKeyID(ctx, key.PrimaryKeyID) if err != nil { log.Error("GetGPGKeysByKeyID: %v", err) return &CommitVerification{ diff --git a/models/asymkey/gpg_key_test.go b/models/asymkey/gpg_key_test.go index 6a0f9c6144..dee74bc281 100644 --- a/models/asymkey/gpg_key_test.go +++ b/models/asymkey/gpg_key_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/timeutil" @@ -228,7 +229,7 @@ Q0KHb+QcycSgbDx0ZAvdIacuKvBBcbxrsmFUI4LR+oIup0G9gUc0roPvr014jYQL =zHo9 -----END PGP PUBLIC KEY BLOCK-----` - keys, err := AddGPGKey(1, testEmailWithUpperCaseLetters, "", "") + keys, err := AddGPGKey(db.DefaultContext, 1, testEmailWithUpperCaseLetters, "", "") assert.NoError(t, err) if assert.NotEmpty(t, keys) { key := keys[0] diff --git a/models/asymkey/ssh_key_authorized_keys.go b/models/asymkey/ssh_key_authorized_keys.go index 77803d6709..f0a3a77eaf 100644 --- a/models/asymkey/ssh_key_authorized_keys.go +++ b/models/asymkey/ssh_key_authorized_keys.go @@ -117,7 +117,7 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error { // RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again. // Note: db.GetEngine(db.DefaultContext).Iterate does not get latest data after insert/delete, so we have to call this function // outside any session scope independently. -func RewriteAllPublicKeys() error { +func RewriteAllPublicKeys(ctx context.Context) error { // Don't rewrite key if internal server if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile { return nil @@ -165,7 +165,7 @@ func RewriteAllPublicKeys() error { } } - if err := RegeneratePublicKeys(db.DefaultContext, t); err != nil { + if err := RegeneratePublicKeys(ctx, t); err != nil { return err } diff --git a/models/asymkey/ssh_key_fingerprint.go b/models/asymkey/ssh_key_fingerprint.go index 8a8d4fce15..2d6af0e3d6 100644 --- a/models/asymkey/ssh_key_fingerprint.go +++ b/models/asymkey/ssh_key_fingerprint.go @@ -81,7 +81,7 @@ func CalcFingerprint(publicKeyContent string) (string, error) { fnName, fp string err error ) - if setting.SSH.StartBuiltinServer { + if len(setting.SSH.KeygenPath) == 0 { fnName = "calcFingerprintNative" fp, err = calcFingerprintNative(publicKeyContent) } else { diff --git a/models/asymkey/ssh_key_verify.go b/models/asymkey/ssh_key_verify.go index d6c16eb467..708196c668 100644 --- a/models/asymkey/ssh_key_verify.go +++ b/models/asymkey/ssh_key_verify.go @@ -29,10 +29,15 @@ func VerifySSHKey(ownerID int64, fingerprint, token, signature string) (string, return "", ErrKeyNotExist{} } - if err := sshsig.Verify(bytes.NewBuffer([]byte(token)), []byte(signature), []byte(key.Content), "gitea"); err != nil { - log.Error("Unable to validate token signature. Error: %v", err) - return "", ErrSSHInvalidTokenSignature{ - Fingerprint: key.Fingerprint, + err = sshsig.Verify(bytes.NewBuffer([]byte(token)), []byte(signature), []byte(key.Content), "gitea") + if err != nil { + // edge case for Windows based shells that will add CR LF if piped to ssh-keygen command + // see https://github.com/PowerShell/PowerShell/issues/5974 + if sshsig.Verify(bytes.NewBuffer([]byte(token+"\r\n")), []byte(signature), []byte(key.Content), "gitea") != nil { + log.Error("Unable to validate token signature. Error: %v", err) + return "", ErrSSHInvalidTokenSignature{ + Fingerprint: key.Fingerprint, + } } } diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index 9c419eff69..633012451a 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -626,10 +626,14 @@ func (err ErrOAuthApplicationNotFound) Unwrap() error { return util.ErrNotExist } -// GetActiveOAuth2ProviderSources returns all actived LoginOAuth2 sources -func GetActiveOAuth2ProviderSources() ([]*Source, error) { +// GetOAuth2ProviderSources returns all actived LoginOAuth2 sources +func GetOAuth2ProviderSources(onlyActive bool) ([]*Source, error) { sources := make([]*Source, 0, 1) - if err := db.GetEngine(db.DefaultContext).Where("is_active = ? and type = ?", true, OAuth2).Find(&sources); err != nil { + sess := db.GetEngine(db.DefaultContext) + if onlyActive { + sess = sess.Where("is_active = ?", true) + } + if err := sess.Where("type = ?", OAuth2).Find(&sources); err != nil { return nil, err } return sources, nil diff --git a/models/auth/source.go b/models/auth/source.go index 0a904b7772..88253a4b39 100644 --- a/models/auth/source.go +++ b/models/auth/source.go @@ -232,7 +232,7 @@ func CreateSource(source *Source) error { err = registerableSource.RegisterSource() if err != nil { // remove the AuthSource in case of errors while registering configuration - if _, err := db.GetEngine(db.DefaultContext).Delete(source); err != nil { + if _, err := db.GetEngine(db.DefaultContext).ID(source.ID).Delete(new(Source)); err != nil { log.Error("CreateSource: Error while wrapOpenIDConnectInitializeError: %v", err) } } @@ -275,9 +275,6 @@ func ActiveSources(tp Type) ([]*Source, error) { // IsSSPIEnabled returns true if there is at least one activated login // source of type LoginSSPI func IsSSPIEnabled() bool { - if !db.HasEngine { - return false - } sources, err := ActiveSources(SSPI) if err != nil { log.Error("ActiveSources: %v", err) diff --git a/models/avatars/avatar.go b/models/avatars/avatar.go index e40aa3f542..b0bfa44089 100644 --- a/models/avatars/avatar.go +++ b/models/avatars/avatar.go @@ -5,18 +5,20 @@ package avatars import ( "context" + "fmt" "net/url" "path" "strconv" "strings" - "sync" + "sync/atomic" "code.gitea.io/gitea/models/db" - system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + + "strk.kbt.io/projects/go/libravatar" ) const ( @@ -36,24 +38,54 @@ func init() { db.RegisterModel(new(EmailHash)) } -var ( +type avatarSettingStruct struct { defaultAvatarLink string - once sync.Once -) + gravatarSource string + gravatarSourceURL *url.URL + libravatar *libravatar.Libravatar +} -// DefaultAvatarLink the default avatar link -func DefaultAvatarLink() string { - once.Do(func() { +var avatarSettingAtomic atomic.Pointer[avatarSettingStruct] + +func loadAvatarSetting() (*avatarSettingStruct, error) { + s := avatarSettingAtomic.Load() + if s == nil || s.gravatarSource != setting.GravatarSource { + s = &avatarSettingStruct{} u, err := url.Parse(setting.AppSubURL) if err != nil { - log.Error("Can not parse AppSubURL: %v", err) - return + return nil, fmt.Errorf("unable to parse AppSubURL: %w", err) } u.Path = path.Join(u.Path, "/assets/img/avatar_default.png") - defaultAvatarLink = u.String() - }) - return defaultAvatarLink + s.defaultAvatarLink = u.String() + + s.gravatarSourceURL, err = url.Parse(setting.GravatarSource) + if err != nil { + return nil, fmt.Errorf("unable to parse GravatarSource %q: %w", setting.GravatarSource, err) + } + + s.libravatar = libravatar.New() + if s.gravatarSourceURL.Scheme == "https" { + s.libravatar.SetUseHTTPS(true) + s.libravatar.SetSecureFallbackHost(s.gravatarSourceURL.Host) + } else { + s.libravatar.SetUseHTTPS(false) + s.libravatar.SetFallbackHost(s.gravatarSourceURL.Host) + } + + avatarSettingAtomic.Store(s) + } + return s, nil +} + +// DefaultAvatarLink the default avatar link +func DefaultAvatarLink() string { + a, err := loadAvatarSetting() + if err != nil { + log.Error("Failed to loadAvatarSetting: %v", err) + return "" + } + return a.defaultAvatarLink } // HashEmail hashes email address to MD5 string. https://en.gravatar.com/site/implement/hash/ @@ -76,7 +108,11 @@ func GetEmailForHash(md5Sum string) (string, error) { // LibravatarURL returns the URL for the given email. Slow due to the DNS lookup. // This function should only be called if a federated avatar service is enabled. func LibravatarURL(email string) (*url.URL, error) { - urlStr, err := system_model.LibravatarService.FromEmail(email) + a, err := loadAvatarSetting() + if err != nil { + return nil, err + } + urlStr, err := a.libravatar.FromEmail(email) if err != nil { log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err) return nil, err @@ -153,15 +189,13 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final return DefaultAvatarLink() } - disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar, - setting.GetDefaultDisableGravatar(), - ) + avatarSetting, err := loadAvatarSetting() + if err != nil { + return DefaultAvatarLink() + } - enableFederatedAvatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureEnableFederatedAvatar, - setting.GetDefaultEnableFederatedAvatar(disableGravatar)) - - var err error - if enableFederatedAvatar && system_model.LibravatarService != nil { + enableFederatedAvatar := setting.Config().Picture.EnableFederatedAvatar.Value(ctx) + if enableFederatedAvatar { emailHash := saveEmailHash(email) if final { // for final link, we can spend more time on slow external query @@ -179,9 +213,10 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final return urlStr } + disableGravatar := setting.Config().Picture.DisableGravatar.Value(ctx) if !disableGravatar { // copy GravatarSourceURL, because we will modify its Path. - avatarURLCopy := *system_model.GravatarSourceURL + avatarURLCopy := *avatarSetting.gravatarSourceURL avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email)) return generateRecognizedAvatarURL(avatarURLCopy, size) } diff --git a/models/avatars/avatar_test.go b/models/avatars/avatar_test.go index 59daaeb669..c8f7a6574b 100644 --- a/models/avatars/avatar_test.go +++ b/models/avatars/avatar_test.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/db" system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/setting/config" "github.com/stretchr/testify/assert" ) @@ -17,19 +18,16 @@ import ( const gravatarSource = "https://secure.gravatar.com/avatar/" func disableGravatar(t *testing.T) { - err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureEnableFederatedAvatar, "false") + err := system_model.SetSettings(db.DefaultContext, map[string]string{setting.Config().Picture.EnableFederatedAvatar.DynKey(): "false"}) assert.NoError(t, err) - err = system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "true") + err = system_model.SetSettings(db.DefaultContext, map[string]string{setting.Config().Picture.DisableGravatar.DynKey(): "true"}) assert.NoError(t, err) - system_model.LibravatarService = nil } func enableGravatar(t *testing.T) { - err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false") + err := system_model.SetSettings(db.DefaultContext, map[string]string{setting.Config().Picture.DisableGravatar.DynKey(): "false"}) assert.NoError(t, err) setting.GravatarSource = gravatarSource - err = system_model.Init(db.DefaultContext) - assert.NoError(t, err) } func TestHashEmail(t *testing.T) { @@ -47,10 +45,12 @@ func TestSizedAvatarLink(t *testing.T) { setting.AppSubURL = "/testsuburl" disableGravatar(t) + config.GetDynGetter().InvalidateCache() assert.Equal(t, "/testsuburl/assets/img/avatar_default.png", avatars_model.GenerateEmailAvatarFastLink(db.DefaultContext, "gitea@example.com", 100)) enableGravatar(t) + config.GetDynGetter().InvalidateCache() assert.Equal(t, "https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100", avatars_model.GenerateEmailAvatarFastLink(db.DefaultContext, "gitea@example.com", 100), diff --git a/models/db/context.go b/models/db/context.go index 521857fae8..9f72b43555 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -178,6 +178,15 @@ func GetByBean(ctx context.Context, bean any) (bool, error) { return GetEngine(ctx).Get(bean) } +func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) { + if !cond.IsValid() { + return false, ErrConditionRequired{} + } + + var bean T + return GetEngine(ctx).Where(cond).NoAutoCondition().Exist(&bean) +} + // DeleteByBean deletes all records according non-empty fields of the bean as conditions. func DeleteByBean(ctx context.Context, bean any) (int64, error) { return GetEngine(ctx).Delete(bean) diff --git a/models/db/engine.go b/models/db/engine.go index 182d8cd993..99906813ca 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -27,9 +27,6 @@ var ( x *xorm.Engine tables []any initFuncs []func() error - - // HasEngine specifies if we have a xorm.Engine - HasEngine bool ) // Engine represents a xorm engine or session. diff --git a/models/db/error.go b/models/db/error.go index 665e970e17..f601a15c01 100644 --- a/models/db/error.go +++ b/models/db/error.go @@ -72,3 +72,21 @@ func (err ErrNotExist) Error() string { func (err ErrNotExist) Unwrap() error { return util.ErrNotExist } + +// ErrConditionRequired represents an error which require condition. +type ErrConditionRequired struct{} + +// IsErrConditionRequired checks if an error is an ErrConditionRequired +func IsErrConditionRequired(err error) bool { + _, ok := err.(ErrConditionRequired) + return ok +} + +func (err ErrConditionRequired) Error() string { + return "condition is required" +} + +// Unwrap unwraps this as a ErrNotExist err +func (err ErrConditionRequired) Unwrap() error { + return util.ErrInvalidArgument +} diff --git a/models/error.go b/models/error.go index b7bb967b73..83dfe29805 100644 --- a/models/error.go +++ b/models/error.go @@ -57,6 +57,21 @@ func (err ErrUserOwnPackages) Error() string { return fmt.Sprintf("user still has ownership of packages [uid: %d]", err.UID) } +// ErrDeleteLastAdminUser represents a "DeleteLastAdminUser" kind of error. +type ErrDeleteLastAdminUser struct { + UID int64 +} + +// IsErrDeleteLastAdminUser checks if an error is a ErrDeleteLastAdminUser. +func IsErrDeleteLastAdminUser(err error) bool { + _, ok := err.(ErrDeleteLastAdminUser) + return ok +} + +func (err ErrDeleteLastAdminUser) Error() string { + return fmt.Sprintf("can not delete the last admin user [uid: %d]", err.UID) +} + // ErrNoPendingRepoTransfer is an error type for repositories without a pending // transfer request type ErrNoPendingRepoTransfer struct { diff --git a/models/fixture_generation.go b/models/fixture_generation.go index abf880ee8e..d342919895 100644 --- a/models/fixture_generation.go +++ b/models/fixture_generation.go @@ -40,7 +40,9 @@ func GetYamlFixturesAccess() (string, error) { fmt.Fprintf(&b, " user_id: %d\n", a.UserID) fmt.Fprintf(&b, " repo_id: %d\n", a.RepoID) fmt.Fprintf(&b, " mode: %d\n", a.Mode) - fmt.Fprintf(&b, "\n") + if i < len(accesses)-1 { + fmt.Fprintf(&b, "\n") + } } return b.String(), nil diff --git a/models/fixture_test.go b/models/fixture_test.go index 8a28db8164..b70fdb536d 100644 --- a/models/fixture_test.go +++ b/models/fixture_test.go @@ -22,12 +22,13 @@ func TestFixtureGeneration(t *testing.T) { if !assert.NoError(t, err) { return } - bytes, err := os.ReadFile(filepath.Join(unittest.FixturesDir(), name+".yml")) + p := filepath.Join(unittest.FixturesDir(), name+".yml") + bytes, err := os.ReadFile(p) if !assert.NoError(t, err) { return } data := string(util.NormalizeEOL(bytes)) - assert.True(t, data == expected, "Differences detected for %s.yml", name) + assert.EqualValues(t, expected, data, "Differences detected for %s", p) } test(GetYamlFixturesAccess, "access") diff --git a/models/fixtures/access.yml b/models/fixtures/access.yml index 446502843e..1bb6a9a8ac 100644 --- a/models/fixtures/access.yml +++ b/models/fixtures/access.yml @@ -135,4 +135,3 @@ user_id: 31 repo_id: 28 mode: 4 - diff --git a/models/fixtures/access_token.yml b/models/fixtures/access_token.yml index 791b3da1c4..0744255f66 100644 --- a/models/fixtures/access_token.yml +++ b/models/fixtures/access_token.yml @@ -2,7 +2,7 @@ id: 1 uid: 1 name: Token A - #token: d2c6c1ba3890b309189a8e618c72a162e4efbf36 + # token: d2c6c1ba3890b309189a8e618c72a162e4efbf36 token_hash: 2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f token_salt: QuSiZr1byZ token_last_eight: e4efbf36 @@ -13,7 +13,7 @@ id: 2 uid: 1 name: Token B - #token: 4c6f36e6cf498e2a448662f915d932c09c5a146c + # token: 4c6f36e6cf498e2a448662f915d932c09c5a146c token_hash: 1a0e32a231ebbd582dc626c1543a42d3c63d4fa76c07c72862721467c55e8f81c923d60700f0528b5f5f443f055559d3a279 token_salt: Lfwopukrq5 token_last_eight: 9c5a146c @@ -24,10 +24,10 @@ id: 3 uid: 2 name: Token A - #token: 90a18faa671dc43924b795806ffe4fd169d28c91 + # token: 90a18faa671dc43924b795806ffe4fd169d28c91 token_hash: d6d404048048812d9e911d93aefbe94fc768d4876fdf75e3bef0bdc67828e0af422846d3056f2f25ec35c51dc92075685ec5 token_salt: 99ArgXKlQQ token_last_eight: 69d28c91 created_unix: 946687980 updated_unix: 946687980 -#commented out tokens so you can see what they are in plaintext + # commented out tokens so you can see what they are in plaintext diff --git a/models/fixtures/attachment.yml b/models/fixtures/attachment.yml index 9ad43fa2b7..7882d8bff2 100644 --- a/models/fixtures/attachment.yml +++ b/models/fixtures/attachment.yml @@ -140,3 +140,16 @@ download_count: 0 size: 0 created_unix: 946684800 + +- + id: 12 + uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22 + repo_id: 2 + issue_id: 0 + release_id: 11 + uploader_id: 2 + comment_id: 0 + name: README.md + download_count: 0 + size: 0 + created_unix: 946684800 diff --git a/models/fixtures/comment.yml b/models/fixtures/comment.yml index bd64680c8c..74fc716180 100644 --- a/models/fixtures/comment.yml +++ b/models/fixtures/comment.yml @@ -66,3 +66,20 @@ tree_path: "README.md" created_unix: 946684812 invalidated: true + +- + id: 8 + type: 0 # comment + poster_id: 2 + issue_id: 4 # in repo_id 2 + content: "comment in private pository" + created_unix: 946684811 + updated_unix: 946684811 + +- + id: 9 + type: 22 # review + poster_id: 2 + issue_id: 2 # in repo_id 1 + review_id: 20 + created_unix: 946684810 diff --git a/models/fixtures/commit_status_index.yml b/models/fixtures/commit_status_index.yml index 3f252e87ef..f63343b042 100644 --- a/models/fixtures/commit_status_index.yml +++ b/models/fixtures/commit_status_index.yml @@ -2,4 +2,4 @@ id: 1 repo_id: 1 sha: "1234123412341234123412341234123412341234" - max_index: 5 \ No newline at end of file + max_index: 5 diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml index fa72f9b647..0c9b6ff406 100644 --- a/models/fixtures/issue.yml +++ b/models/fixtures/issue.yml @@ -61,7 +61,7 @@ priority: 0 is_closed: true is_pull: false - num_comments: 0 + num_comments: 1 created_unix: 946684830 updated_unix: 978307200 is_locked: false @@ -321,3 +321,20 @@ created_unix: 946684830 updated_unix: 978307200 is_locked: false + +- + id: 20 + repo_id: 23 + index: 1 + poster_id: 2 + original_author_id: 0 + name: issue for pr + content: content + milestone_id: 0 + priority: 0 + is_closed: false + is_pull: true + num_comments: 0 + created_unix: 978307210 + updated_unix: 978307210 + is_locked: false diff --git a/models/fixtures/public_key.yml b/models/fixtures/public_key.yml index 08f5c349a7..ae620ee2d1 100644 --- a/models/fixtures/public_key.yml +++ b/models/fixtures/public_key.yml @@ -8,4 +8,4 @@ type: 1 created_unix: 1559593109 updated_unix: 1565224552 - login_source_id: 0 \ No newline at end of file + login_source_id: 0 diff --git a/models/fixtures/pull_request.yml b/models/fixtures/pull_request.yml index e5589ac703..560674c370 100644 --- a/models/fixtures/pull_request.yml +++ b/models/fixtures/pull_request.yml @@ -89,3 +89,12 @@ base_branch: main merge_base: cbff181af4c9c7fee3cf6c106699e07d9a3f54e6 has_merged: false + +- + id: 8 + type: 0 # gitea pull request + status: 2 # mergable + issue_id: 20 + index: 1 + head_repo_id: 23 + base_repo_id: 23 diff --git a/models/fixtures/reaction.yml b/models/fixtures/reaction.yml index 4925935fe6..ee571a73a4 100644 --- a/models/fixtures/reaction.yml +++ b/models/fixtures/reaction.yml @@ -1,5 +1,5 @@ - - id: 1 #issue reaction + id: 1 # issue reaction type: zzz # not allowed reaction (added before allowed reaction list has changed) issue_id: 1 comment_id: 0 @@ -7,7 +7,7 @@ created_unix: 1573248001 - - id: 2 #issue reaction + id: 2 # issue reaction type: zzz # not allowed reaction (added before allowed reaction list has changed) issue_id: 1 comment_id: 0 @@ -15,7 +15,7 @@ created_unix: 1573248002 - - id: 3 #issue reaction + id: 3 # issue reaction type: eyes # allowed reaction issue_id: 1 comment_id: 0 @@ -23,7 +23,7 @@ created_unix: 1573248003 - - id: 4 #comment reaction + id: 4 # comment reaction type: laugh # allowed reaction issue_id: 1 comment_id: 2 @@ -31,7 +31,7 @@ created_unix: 1573248004 - - id: 5 #comment reaction + id: 5 # comment reaction type: laugh # allowed reaction issue_id: 1 comment_id: 2 diff --git a/models/fixtures/release.yml b/models/fixtures/release.yml index 4ed7df440d..372a79509f 100644 --- a/models/fixtures/release.yml +++ b/models/fixtures/release.yml @@ -136,3 +136,17 @@ is_prerelease: false is_tag: false created_unix: 946684803 + +- id: 11 + repo_id: 2 + publisher_id: 2 + tag_name: "v1.1" + lower_tag_name: "v1.1" + target: "" + title: "v1.1" + sha1: "205ac761f3326a7ebe416e8673760016450b5cec" + num_commits: 2 + is_draft: false + is_prerelease: false + is_tag: false + created_unix: 946684803 diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml index c22eb8c2a2..98b1f47401 100644 --- a/models/fixtures/repo_unit.yml +++ b/models/fixtures/repo_unit.yml @@ -649,3 +649,10 @@ repo_id: 49 type: 2 created_unix: 946684810 + +- + id: 98 + repo_id: 59 + type: 1 + config: "{}" + created_unix: 946684810 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index c63b7ebd48..f4e8376735 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -679,7 +679,7 @@ num_forks: 0 num_issues: 0 num_closed_issues: 0 - num_pulls: 0 + num_pulls: 1 num_closed_pulls: 0 num_milestones: 0 num_closed_milestones: 0 @@ -1693,3 +1693,16 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + +- + id: 59 + owner_id: 2 + owner_name: user2 + lower_name: test_commit_revert + name: test_commit_revert + default_branch: main + is_empty: false + is_archived: false + is_private: true + status: 0 + num_issues: 0 diff --git a/models/fixtures/review.yml b/models/fixtures/review.yml index dda13dc468..ac97e24c2b 100644 --- a/models/fixtures/review.yml +++ b/models/fixtures/review.yml @@ -132,3 +132,50 @@ content: "singular review from org6 and final review for this pr" updated_unix: 946684831 created_unix: 946684831 + +- + id: 16 + type: 4 + reviewer_id: 20 + issue_id: 20 + content: "review request for user20" + updated_unix: 946684832 + created_unix: 946684832 + +- + id: 17 + type: 1 + reviewer_id: 20 + issue_id: 20 + content: "review approved by user20" + updated_unix: 946684833 + created_unix: 946684833 + +- + id: 18 + type: 4 + reviewer_id: 0 + reviewer_team_id: 5 + issue_id: 20 + content: "review request for team5" + updated_unix: 946684834 + created_unix: 946684834 + +- + id: 19 + type: 4 + reviewer_id: 15 + reviewer_team_id: 0 + issue_id: 20 + content: "review request for user15" + updated_unix: 946684835 + created_unix: 946684835 + +- + id: 20 + type: 22 + reviewer_id: 1 + issue_id: 2 + content: "Review Comment" + updated_unix: 946684810 + created_unix: 946684810 diff --git a/models/fixtures/team.yml b/models/fixtures/team.yml index 65326eedbf..295e51e39c 100644 --- a/models/fixtures/team.yml +++ b/models/fixtures/team.yml @@ -93,7 +93,7 @@ name: review_team authorize: 1 # read num_repos: 1 - num_members: 2 + num_members: 3 includes_all_repositories: false can_create_org_repo: false diff --git a/models/fixtures/team_repo.yml b/models/fixtures/team_repo.yml index a523a90b20..8497720892 100644 --- a/models/fixtures/team_repo.yml +++ b/models/fixtures/team_repo.yml @@ -62,4 +62,4 @@ id: 11 org_id: 17 team_id: 9 - repo_id: 24 \ No newline at end of file + repo_id: 24 diff --git a/models/fixtures/team_user.yml b/models/fixtures/team_user.yml index feace5f2a5..9142fe609a 100644 --- a/models/fixtures/team_user.yml +++ b/models/fixtures/team_user.yml @@ -123,3 +123,9 @@ org_id: 36 team_id: 20 uid: 5 + +- + id: 22 + org_id: 17 + team_id: 9 + uid: 15 diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index fd51379816..79fbb981f6 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -66,7 +66,7 @@ num_followers: 2 num_following: 1 num_stars: 2 - num_repos: 14 + num_repos: 15 num_teams: 0 num_members: 0 visibility: 0 diff --git a/models/fixtures/watch.yml b/models/fixtures/watch.yml index c29f6bb65a..1950ac99e7 100644 --- a/models/fixtures/watch.yml +++ b/models/fixtures/watch.yml @@ -26,4 +26,4 @@ id: 5 user_id: 11 repo_id: 1 - mode: 3 # auto + mode: 3 # auto diff --git a/models/git/branch.go b/models/git/branch.go index 6d50fb9fb6..db02fc9b28 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -205,10 +205,9 @@ func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64 }) } -// UpdateBranch updates the branch information in the database. If the branch exist, it will update latest commit of this branch information -// If it doest not exist, insert a new record into database -func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) error { - cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName). +// UpdateBranch updates the branch information in the database. +func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) (int64, error) { + return db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName). Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix"). Update(&Branch{ CommitID: commit.ID.String(), @@ -217,21 +216,6 @@ func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), IsDeleted: false, }) - if err != nil { - return err - } - if cnt > 0 { - return nil - } - - return db.Insert(ctx, &Branch{ - RepoID: repoID, - Name: branchName, - CommitID: commit.ID.String(), - CommitMessage: commit.Summary(), - PusherID: pusherID, - CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), - }) } // AddDeletedBranch adds a deleted branch to the database @@ -299,7 +283,7 @@ func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch * } // RenameBranch rename a branch -func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) { +func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(ctx context.Context, isDefault bool) error) (err error) { ctx, committer, err := db.TxContext(ctx) if err != nil { return err @@ -308,6 +292,17 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str sess := db.GetEngine(ctx) + var branch Branch + exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch) + if err != nil { + return err + } else if !exist || branch.IsDeleted { + return ErrBranchNotExist{ + RepoID: repo.ID, + BranchName: from, + } + } + // 1. update branch in database if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{ Name: to, @@ -363,7 +358,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str } // 5. do git action - if err = gitAction(isDefault); err != nil { + if err = gitAction(ctx, isDefault); err != nil { return err } diff --git a/models/git/branch_list.go b/models/git/branch_list.go index b5c1301a1d..2efe495264 100644 --- a/models/git/branch_list.go +++ b/models/git/branch_list.go @@ -73,7 +73,7 @@ type FindBranchOptions struct { Keyword string } -func (opts *FindBranchOptions) Cond() builder.Cond { +func (opts FindBranchOptions) ToConds() builder.Cond { cond := builder.NewCond() if opts.RepoID > 0 { cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) @@ -92,7 +92,7 @@ func (opts *FindBranchOptions) Cond() builder.Cond { } func CountBranches(ctx context.Context, opts FindBranchOptions) (int64, error) { - return db.GetEngine(ctx).Where(opts.Cond()).Count(&Branch{}) + return db.GetEngine(ctx).Where(opts.ToConds()).Count(&Branch{}) } func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session { @@ -108,7 +108,7 @@ func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session { } func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, error) { - sess := db.GetEngine(ctx).Where(opts.Cond()) + sess := db.GetEngine(ctx).Where(opts.ToConds()) if opts.PageSize > 0 && !opts.IsListAll() { sess = db.SetSessionPagination(sess, &opts.ListOptions) } @@ -119,7 +119,7 @@ func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, erro } func FindBranchNames(ctx context.Context, opts FindBranchOptions) ([]string, error) { - sess := db.GetEngine(ctx).Select("name").Where(opts.Cond()) + sess := db.GetEngine(ctx).Select("name").Where(opts.ToConds()) if opts.PageSize > 0 && !opts.IsListAll() { sess = db.SetSessionPagination(sess, &opts.ListOptions) } diff --git a/models/git/branch_test.go b/models/git/branch_test.go index ba69026927..8f41b85d3f 100644 --- a/models/git/branch_test.go +++ b/models/git/branch_test.go @@ -4,6 +4,7 @@ package git_test import ( + "context" "testing" "code.gitea.io/gitea/models/db" @@ -37,7 +38,7 @@ func TestAddDeletedBranch(t *testing.T) { }, } - err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.PusherID, secondBranch.Name, commit) + _, err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.PusherID, secondBranch.Name, commit) assert.NoError(t, err) } @@ -133,7 +134,7 @@ func TestRenameBranch(t *testing.T) { }, git_model.WhitelistOptions{})) assert.NoError(t, committer.Commit()) - assert.NoError(t, git_model.RenameBranch(db.DefaultContext, repo1, "master", "main", func(isDefault bool) error { + assert.NoError(t, git_model.RenameBranch(db.DefaultContext, repo1, "master", "main", func(ctx context.Context, isDefault bool) error { _isDefault = isDefault return nil })) diff --git a/models/git/commit_status.go b/models/git/commit_status.go index 24fee8c3b4..446a2ddd5b 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -25,7 +25,6 @@ import ( "code.gitea.io/gitea/modules/translation" "xorm.io/builder" - "xorm.io/xorm" ) // CommitStatus holds a single Status of a single Commit @@ -220,60 +219,58 @@ func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus { // CommitStatusOptions holds the options for query commit statuses type CommitStatusOptions struct { db.ListOptions + RepoID int64 + SHA string State string SortType string } -// GetCommitStatuses returns all statuses for a given commit. -func GetCommitStatuses(ctx context.Context, repo *repo_model.Repository, sha string, opts *CommitStatusOptions) ([]*CommitStatus, int64, error) { - if opts.Page <= 0 { - opts.Page = 1 - } - if opts.PageSize <= 0 { - opts.Page = setting.ItemsPerPage +func (opts *CommitStatusOptions) ToConds() builder.Cond { + var cond builder.Cond = builder.Eq{ + "repo_id": opts.RepoID, + "sha": opts.SHA, } - countSession := listCommitStatusesStatement(ctx, repo, sha, opts) - countSession = db.SetSessionPagination(countSession, opts) - maxResults, err := countSession.Count(new(CommitStatus)) - if err != nil { - log.Error("Count PRs: %v", err) - return nil, maxResults, err - } - - statuses := make([]*CommitStatus, 0, opts.PageSize) - findSession := listCommitStatusesStatement(ctx, repo, sha, opts) - findSession = db.SetSessionPagination(findSession, opts) - sortCommitStatusesSession(findSession, opts.SortType) - return statuses, maxResults, findSession.Find(&statuses) -} - -func listCommitStatusesStatement(ctx context.Context, repo *repo_model.Repository, sha string, opts *CommitStatusOptions) *xorm.Session { - sess := db.GetEngine(ctx).Where("repo_id = ?", repo.ID).And("sha = ?", sha) switch opts.State { case "pending", "success", "error", "failure", "warning": - sess.And("state = ?", opts.State) + cond = cond.And(builder.Eq{ + "state": opts.State, + }) } - return sess + + return cond } -func sortCommitStatusesSession(sess *xorm.Session, sortType string) { - switch sortType { +func (opts *CommitStatusOptions) ToOrders() string { + switch opts.SortType { case "oldest": - sess.Asc("created_unix") + return "created_unix ASC" case "recentupdate": - sess.Desc("updated_unix") + return "updated_unix DESC" case "leastupdate": - sess.Asc("updated_unix") + return "updated_unix ASC" case "leastindex": - sess.Desc("index") + return "`index` DESC" case "highestindex": - sess.Asc("index") + return "`index` ASC" default: - sess.Desc("created_unix") + return "created_unix DESC" } } +// GetCommitStatuses returns all statuses for a given commit. +func GetCommitStatuses(ctx context.Context, opts *CommitStatusOptions) ([]*CommitStatus, int64, error) { + sess := db.GetEngine(ctx). + Where(opts.ToConds()). + OrderBy(opts.ToOrders()) + + db.SetSessionPagination(sess, opts) + + statuses := make([]*CommitStatus, 0, opts.PageSize) + count, err := sess.FindAndCount(&statuses) + return statuses, count, err +} + // CommitStatusIndex represents a table for commit status index type CommitStatusIndex struct { ID int64 @@ -514,7 +511,7 @@ func ConvertFromGitCommit(ctx context.Context, commits []*git.Commit, repo *repo user_model.ValidateCommitsWithEmails(ctx, commits), repo.GetTrustModel(), func(user *user_model.User) (bool, error) { - return repo_model.IsOwnerMemberCollaborator(repo, user.ID) + return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID) }, ), repo, diff --git a/models/git/commit_status_test.go b/models/git/commit_status_test.go index a86941a0fe..d82c2bc5ea 100644 --- a/models/git/commit_status_test.go +++ b/models/git/commit_status_test.go @@ -22,7 +22,11 @@ func TestGetCommitStatuses(t *testing.T) { sha1 := "1234123412341234123412341234123412341234" - statuses, maxResults, err := git_model.GetCommitStatuses(db.DefaultContext, repo1, sha1, &git_model.CommitStatusOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 50}}) + statuses, maxResults, err := git_model.GetCommitStatuses(db.DefaultContext, &git_model.CommitStatusOptions{ + ListOptions: db.ListOptions{Page: 1, PageSize: 50}, + RepoID: repo1.ID, + SHA: sha1, + }) assert.NoError(t, err) assert.Equal(t, int(maxResults), 5) assert.Len(t, statuses, 5) @@ -31,6 +35,10 @@ func TestGetCommitStatuses(t *testing.T) { assert.Equal(t, structs.CommitStatusPending, statuses[0].State) assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[0].APIURL(db.DefaultContext)) + assert.Equal(t, "cov/awesomeness", statuses[1].Context) + assert.Equal(t, structs.CommitStatusWarning, statuses[1].State) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[1].APIURL(db.DefaultContext)) + assert.Equal(t, "cov/awesomeness", statuses[2].Context) assert.Equal(t, structs.CommitStatusSuccess, statuses[2].State) assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[2].APIURL(db.DefaultContext)) @@ -42,4 +50,13 @@ func TestGetCommitStatuses(t *testing.T) { assert.Equal(t, "deploy/awesomeness", statuses[4].Context) assert.Equal(t, structs.CommitStatusError, statuses[4].State) assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/statuses/1234123412341234123412341234123412341234", statuses[4].APIURL(db.DefaultContext)) + + statuses, maxResults, err = git_model.GetCommitStatuses(db.DefaultContext, &git_model.CommitStatusOptions{ + ListOptions: db.ListOptions{Page: 2, PageSize: 50}, + RepoID: repo1.ID, + SHA: sha1, + }) + assert.NoError(t, err) + assert.Equal(t, int(maxResults), 5) + assert.Empty(t, statuses) } diff --git a/models/issues/comment.go b/models/issues/comment.go index 66b3a8527f..8ee4e9acd6 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -246,7 +246,7 @@ type Comment struct { NewTitle string OldRef string NewRef string - DependentIssueID int64 + DependentIssueID int64 `xorm:"index"` // This is used by issue_service.deleteIssue DependentIssue *Issue `xorm:"-"` CommitID int64 @@ -342,7 +342,7 @@ func (c *Comment) AfterLoad(session *xorm.Session) { // LoadPoster loads comment poster func (c *Comment) LoadPoster(ctx context.Context) (err error) { - if c.PosterID <= 0 || c.Poster != nil { + if c.Poster != nil { return nil } @@ -371,42 +371,42 @@ func (c *Comment) AfterDelete(ctx context.Context) { } // HTMLURL formats a URL-string to the issue-comment -func (c *Comment) HTMLURL() string { - err := c.LoadIssue(db.DefaultContext) +func (c *Comment) HTMLURL(ctx context.Context) string { + err := c.LoadIssue(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" } - err = c.Issue.LoadRepo(db.DefaultContext) + err = c.Issue.LoadRepo(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" } - return c.Issue.HTMLURL() + c.hashLink() + return c.Issue.HTMLURL() + c.hashLink(ctx) } // Link formats a relative URL-string to the issue-comment -func (c *Comment) Link() string { - err := c.LoadIssue(db.DefaultContext) +func (c *Comment) Link(ctx context.Context) string { + err := c.LoadIssue(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" } - err = c.Issue.LoadRepo(db.DefaultContext) + err = c.Issue.LoadRepo(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" } - return c.Issue.Link() + c.hashLink() + return c.Issue.Link() + c.hashLink(ctx) } -func (c *Comment) hashLink() string { +func (c *Comment) hashLink(ctx context.Context) string { if c.Type == CommentTypeCode { if c.ReviewID == 0 { return "/files#" + c.HashTag() } if c.Review == nil { - if err := c.LoadReview(); err != nil { + if err := c.LoadReview(ctx); err != nil { log.Warn("LoadReview(%d): %v", c.ReviewID, err) return "/files#" + c.HashTag() } @@ -419,13 +419,13 @@ func (c *Comment) hashLink() string { } // APIURL formats a API-string to the issue-comment -func (c *Comment) APIURL() string { - err := c.LoadIssue(db.DefaultContext) +func (c *Comment) APIURL(ctx context.Context) string { + err := c.LoadIssue(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" } - err = c.Issue.LoadRepo(db.DefaultContext) + err = c.Issue.LoadRepo(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" @@ -435,8 +435,8 @@ func (c *Comment) APIURL() string { } // IssueURL formats a URL-string to the issue -func (c *Comment) IssueURL() string { - err := c.LoadIssue(db.DefaultContext) +func (c *Comment) IssueURL(ctx context.Context) string { + err := c.LoadIssue(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" @@ -446,7 +446,7 @@ func (c *Comment) IssueURL() string { return "" } - err = c.Issue.LoadRepo(db.DefaultContext) + err = c.Issue.LoadRepo(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" @@ -455,14 +455,14 @@ func (c *Comment) IssueURL() string { } // PRURL formats a URL-string to the pull-request -func (c *Comment) PRURL() string { - err := c.LoadIssue(db.DefaultContext) +func (c *Comment) PRURL(ctx context.Context) string { + err := c.LoadIssue(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" } - err = c.Issue.LoadRepo(db.DefaultContext) + err = c.Issue.LoadRepo(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" @@ -490,9 +490,9 @@ func (c *Comment) EventTag() string { } // LoadLabel if comment.Type is CommentTypeLabel, then load Label -func (c *Comment) LoadLabel() error { +func (c *Comment) LoadLabel(ctx context.Context) error { var label Label - has, err := db.GetEngine(db.DefaultContext).ID(c.LabelID).Get(&label) + has, err := db.GetEngine(ctx).ID(c.LabelID).Get(&label) if err != nil { return err } else if has { @@ -506,10 +506,10 @@ func (c *Comment) LoadLabel() error { } // LoadProject if comment.Type is CommentTypeProject, then load project. -func (c *Comment) LoadProject() error { +func (c *Comment) LoadProject(ctx context.Context) error { if c.OldProjectID > 0 { var oldProject project_model.Project - has, err := db.GetEngine(db.DefaultContext).ID(c.OldProjectID).Get(&oldProject) + has, err := db.GetEngine(ctx).ID(c.OldProjectID).Get(&oldProject) if err != nil { return err } else if has { @@ -519,7 +519,7 @@ func (c *Comment) LoadProject() error { if c.ProjectID > 0 { var project project_model.Project - has, err := db.GetEngine(db.DefaultContext).ID(c.ProjectID).Get(&project) + has, err := db.GetEngine(ctx).ID(c.ProjectID).Get(&project) if err != nil { return err } else if has { @@ -569,8 +569,8 @@ func (c *Comment) LoadAttachments(ctx context.Context) error { } // UpdateAttachments update attachments by UUIDs for the comment -func (c *Comment) UpdateAttachments(uuids []string) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func (c *Comment) UpdateAttachments(ctx context.Context, uuids []string) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -591,11 +591,11 @@ func (c *Comment) UpdateAttachments(uuids []string) error { } // LoadAssigneeUserAndTeam if comment.Type is CommentTypeAssignees, then load assignees -func (c *Comment) LoadAssigneeUserAndTeam() error { +func (c *Comment) LoadAssigneeUserAndTeam(ctx context.Context) error { var err error if c.AssigneeID > 0 && c.Assignee == nil { - c.Assignee, err = user_model.GetUserByID(db.DefaultContext, c.AssigneeID) + c.Assignee, err = user_model.GetUserByID(ctx, c.AssigneeID) if err != nil { if !user_model.IsErrUserNotExist(err) { return err @@ -603,20 +603,20 @@ func (c *Comment) LoadAssigneeUserAndTeam() error { c.Assignee = user_model.NewGhostUser() } } else if c.AssigneeTeamID > 0 && c.AssigneeTeam == nil { - if err = c.LoadIssue(db.DefaultContext); err != nil { + if err = c.LoadIssue(ctx); err != nil { return err } - if err = c.Issue.LoadRepo(db.DefaultContext); err != nil { + if err = c.Issue.LoadRepo(ctx); err != nil { return err } - if err = c.Issue.Repo.LoadOwner(db.DefaultContext); err != nil { + if err = c.Issue.Repo.LoadOwner(ctx); err != nil { return err } if c.Issue.Repo.Owner.IsOrganization() { - c.AssigneeTeam, err = organization.GetTeamByID(db.DefaultContext, c.AssigneeTeamID) + c.AssigneeTeam, err = organization.GetTeamByID(ctx, c.AssigneeTeamID) if err != nil && !organization.IsErrTeamNotExist(err) { return err } @@ -626,11 +626,11 @@ func (c *Comment) LoadAssigneeUserAndTeam() error { } // LoadResolveDoer if comment.Type is CommentTypeCode and ResolveDoerID not zero, then load resolveDoer -func (c *Comment) LoadResolveDoer() (err error) { +func (c *Comment) LoadResolveDoer(ctx context.Context) (err error) { if c.ResolveDoerID == 0 || c.Type != CommentTypeCode { return nil } - c.ResolveDoer, err = user_model.GetUserByID(db.DefaultContext, c.ResolveDoerID) + c.ResolveDoer, err = user_model.GetUserByID(ctx, c.ResolveDoerID) if err != nil { if user_model.IsErrUserNotExist(err) { c.ResolveDoer = user_model.NewGhostUser() @@ -646,11 +646,11 @@ func (c *Comment) IsResolved() bool { } // LoadDepIssueDetails loads Dependent Issue Details -func (c *Comment) LoadDepIssueDetails() (err error) { +func (c *Comment) LoadDepIssueDetails(ctx context.Context) (err error) { if c.DependentIssueID <= 0 || c.DependentIssue != nil { return nil } - c.DependentIssue, err = GetIssueByID(db.DefaultContext, c.DependentIssueID) + c.DependentIssue, err = GetIssueByID(ctx, c.DependentIssueID) return err } @@ -683,13 +683,20 @@ func (c *Comment) loadReactions(ctx context.Context, repo *repo_model.Repository } // LoadReactions loads comment reactions -func (c *Comment) LoadReactions(repo *repo_model.Repository) error { - return c.loadReactions(db.DefaultContext, repo) +func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository) error { + return c.loadReactions(ctx, repo) } func (c *Comment) loadReview(ctx context.Context) (err error) { + if c.ReviewID == 0 { + return nil + } if c.Review == nil { if c.Review, err = GetReviewByID(ctx, c.ReviewID); err != nil { + // review request which has been replaced by actual reviews doesn't exist in database anymore, so ignorem them. + if c.Type == CommentTypeReviewRequest { + return nil + } return err } } @@ -698,8 +705,8 @@ func (c *Comment) loadReview(ctx context.Context) (err error) { } // LoadReview loads the associated review -func (c *Comment) LoadReview() error { - return c.loadReview(db.DefaultContext) +func (c *Comment) LoadReview(ctx context.Context) error { + return c.loadReview(ctx) } // DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes. @@ -719,13 +726,13 @@ func (c *Comment) UnsignedLine() uint64 { } // CodeCommentLink returns the url to a comment in code -func (c *Comment) CodeCommentLink() string { - err := c.LoadIssue(db.DefaultContext) +func (c *Comment) CodeCommentLink(ctx context.Context) string { + err := c.LoadIssue(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("LoadIssue(%d): %v", c.IssueID, err) return "" } - err = c.Issue.LoadRepo(db.DefaultContext) + err = c.Issue.LoadRepo(ctx) if err != nil { // Silently dropping errors :unamused: log.Error("loadRepo(%d): %v", c.Issue.RepoID, err) return "" @@ -1016,6 +1023,7 @@ type FindCommentsOptions struct { Type CommentType IssueIDs []int64 Invalidated util.OptionalBool + IsPull util.OptionalBool } // ToConds implements FindOptions interface @@ -1050,6 +1058,9 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond { if !opts.Invalidated.IsNone() { cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.IsTrue()}) } + if opts.IsPull != util.OptionalBoolNone { + cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.IsTrue()}) + } return cond } @@ -1057,7 +1068,7 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond { func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, error) { comments := make([]*Comment, 0, 10) sess := db.GetEngine(ctx).Where(opts.ToConds()) - if opts.RepoID > 0 { + if opts.RepoID > 0 || opts.IsPull != util.OptionalBoolNone { sess.Join("INNER", "issue", "issue.id = comment.issue_id") } @@ -1074,8 +1085,8 @@ func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, } // CountComments count all comments according options by ignoring pagination -func CountComments(opts *FindCommentsOptions) (int64, error) { - sess := db.GetEngine(db.DefaultContext).Where(opts.ToConds()) +func CountComments(ctx context.Context, opts *FindCommentsOptions) (int64, error) { + sess := db.GetEngine(ctx).Where(opts.ToConds()) if opts.RepoID > 0 { sess.Join("INNER", "issue", "issue.id = comment.issue_id") } @@ -1089,8 +1100,8 @@ func UpdateCommentInvalidate(ctx context.Context, c *Comment) error { } // UpdateComment updates information of comment. -func UpdateComment(c *Comment, doer *user_model.User) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func UpdateComment(ctx context.Context, c *Comment, doer *user_model.User) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -1147,16 +1158,11 @@ func DeleteComment(ctx context.Context, comment *Comment) error { } // UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id -func UpdateCommentsMigrationsByType(tp structs.GitServiceType, originalAuthorID string, posterID int64) error { - _, err := db.GetEngine(db.DefaultContext).Table("comment"). - Where(builder.In("issue_id", - builder.Select("issue.id"). - From("issue"). - InnerJoin("repository", "issue.repo_id = repository.id"). - Where(builder.Eq{ - "repository.original_service_type": tp, - }), - )). +func UpdateCommentsMigrationsByType(ctx context.Context, tp structs.GitServiceType, originalAuthorID string, posterID int64) error { + _, err := db.GetEngine(ctx).Table("comment"). + Join("INNER", "issue", "issue.id = comment.issue_id"). + Join("INNER", "repository", "issue.repo_id = repository.id"). + Where("repository.original_service_type = ?", tp). And("comment.original_author_id = ?", originalAuthorID). Update(map[string]any{ "poster_id": posterID, @@ -1250,7 +1256,7 @@ func (c *Comment) HasOriginalAuthor() bool { } // InsertIssueComments inserts many comments of issues. -func InsertIssueComments(comments []*Comment) error { +func InsertIssueComments(ctx context.Context, comments []*Comment) error { if len(comments) == 0 { return nil } @@ -1260,7 +1266,7 @@ func InsertIssueComments(comments []*Comment) error { issueIDs.Add(comment.IssueID) } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go index d447d7542c..27f6694134 100644 --- a/models/issues/comment_code.go +++ b/models/issues/comment_code.go @@ -99,19 +99,21 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu comments[n] = comment n++ - if err := comment.LoadResolveDoer(); err != nil { + if err := comment.LoadResolveDoer(ctx); err != nil { return nil, err } - if err := comment.LoadReactions(issue.Repo); err != nil { + if err := comment.LoadReactions(ctx, issue.Repo); err != nil { return nil, err } var err error if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: issue.Repo.Link(), - Metas: issue.Repo.ComposeMetas(), + Ctx: ctx, + Links: markup.Links{ + Base: issue.Repo.Link(), + }, + Metas: issue.Repo.ComposeMetas(), }, comment.Content); err != nil { return nil, err } diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go index 6f1d350eb4..30a437ea50 100644 --- a/models/issues/comment_list.go +++ b/models/issues/comment_list.go @@ -10,6 +10,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/log" ) // CommentList defines a list of comments @@ -224,6 +225,10 @@ func (comments CommentList) loadAssignees(ctx context.Context) error { for _, comment := range comments { comment.Assignee = assignees[comment.AssigneeID] + if comment.Assignee == nil { + comment.AssigneeID = user_model.GhostUserID + comment.Assignee = user_model.NewGhostUser() + } } return nil } @@ -422,37 +427,19 @@ func (comments CommentList) loadReviews(ctx context.Context) error { reviewIDs := comments.getReviewIDs() reviews := make(map[int64]*Review, len(reviewIDs)) - left := len(reviewIDs) - for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } - rows, err := db.GetEngine(ctx). - In("id", reviewIDs[:limit]). - Rows(new(Review)) - if err != nil { - return err - } - - for rows.Next() { - var review Review - err = rows.Scan(&review) - if err != nil { - _ = rows.Close() - return err - } - - reviews[review.ID] = &review - } - _ = rows.Close() - - left -= limit - reviewIDs = reviewIDs[limit:] + if err := db.GetEngine(ctx).In("id", reviewIDs).Find(&reviews); err != nil { + return err } for _, comment := range comments { comment.Review = reviews[comment.ReviewID] + if comment.Review == nil { + // review request which has been replaced by actual reviews doesn't exist in database anymore, so don't log errors for them. + if comment.ReviewID > 0 && comment.Type != CommentTypeReviewRequest { + log.Error("comment with review id [%d] but has no review record", comment.ReviewID) + } + continue + } // If the comment dismisses a review, we need to load the reviewer to show whose review has been dismissed. // Otherwise, the reviewer is the poster of the comment, so we don't need to load it. diff --git a/models/issues/comment_test.go b/models/issues/comment_test.go index 90db476571..c5bbfdedc2 100644 --- a/models/issues/comment_test.go +++ b/models/issues/comment_test.go @@ -89,7 +89,7 @@ func TestMigrate_InsertIssueComments(t *testing.T) { Reactions: []*issues_model.Reaction{reaction}, } - err := issues_model.InsertIssueComments([]*issues_model.Comment{comment}) + err := issues_model.InsertIssueComments(db.DefaultContext, []*issues_model.Comment{comment}) assert.NoError(t, err) issueModified := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) diff --git a/models/issues/content_history.go b/models/issues/content_history.go index cc06b184d7..31c80d2cea 100644 --- a/models/issues/content_history.go +++ b/models/issues/content_history.go @@ -161,20 +161,20 @@ func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int6 } for _, item := range res { - item.UserAvatarLink = avatars.GenerateUserAvatarFastLink(item.UserName, 0) + if item.UserID > 0 { + item.UserAvatarLink = avatars.GenerateUserAvatarFastLink(item.UserName, 0) + } else { + item.UserAvatarLink = avatars.DefaultAvatarLink() + } } return res, nil } // HasIssueContentHistory check if a ContentHistory entry exists func HasIssueContentHistory(dbCtx context.Context, issueID, commentID int64) (bool, error) { - exists, err := db.GetEngine(dbCtx).Cols("id").Exist(&ContentHistory{ - IssueID: issueID, - CommentID: commentID, - }) + exists, err := db.GetEngine(dbCtx).Where(builder.Eq{"issue_id": issueID, "comment_id": commentID}).Exist(&ContentHistory{}) if err != nil { - log.Error("can not fetch issue content history. err=%v", err) - return false, err + return false, fmt.Errorf("can not check issue content history. err: %w", err) } return exists, err } @@ -218,9 +218,9 @@ func GetIssueContentHistoryByID(dbCtx context.Context, id int64) (*ContentHistor } // GetIssueContentHistoryAndPrev get a history and the previous non-deleted history (to compare) -func GetIssueContentHistoryAndPrev(dbCtx context.Context, id int64) (history, prevHistory *ContentHistory, err error) { +func GetIssueContentHistoryAndPrev(dbCtx context.Context, issueID, id int64) (history, prevHistory *ContentHistory, err error) { history = &ContentHistory{} - has, err := db.GetEngine(dbCtx).ID(id).Get(history) + has, err := db.GetEngine(dbCtx).Where("id=? AND issue_id=?", id, issueID).Get(history) if err != nil { log.Error("failed to get issue content history %v. err=%v", id, err) return nil, nil, err diff --git a/models/issues/content_history_test.go b/models/issues/content_history_test.go index 53638e967f..1caa73a948 100644 --- a/models/issues/content_history_test.go +++ b/models/issues/content_history_test.go @@ -58,13 +58,13 @@ func TestContentHistory(t *testing.T) { hasHistory2, _ := issues_model.HasIssueContentHistory(dbCtx, 10, 1) assert.False(t, hasHistory2) - h6, h6Prev, _ := issues_model.GetIssueContentHistoryAndPrev(dbCtx, 6) + h6, h6Prev, _ := issues_model.GetIssueContentHistoryAndPrev(dbCtx, 10, 6) assert.EqualValues(t, 6, h6.ID) assert.EqualValues(t, 5, h6Prev.ID) // soft-delete _ = issues_model.SoftDeleteIssueContentHistory(dbCtx, 5) - h6, h6Prev, _ = issues_model.GetIssueContentHistoryAndPrev(dbCtx, 6) + h6, h6Prev, _ = issues_model.GetIssueContentHistoryAndPrev(dbCtx, 10, 6) assert.EqualValues(t, 6, h6.ID) assert.EqualValues(t, 4, h6Prev.ID) @@ -78,3 +78,22 @@ func TestContentHistory(t *testing.T) { assert.EqualValues(t, 7, list2[1].HistoryID) assert.EqualValues(t, 4, list2[2].HistoryID) } + +func TestHasIssueContentHistoryForCommentOnly(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + _ = db.TruncateBeans(db.DefaultContext, &issues_model.ContentHistory{}) + + hasHistory1, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 10, 0) + assert.False(t, hasHistory1) + hasHistory2, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 10, 100) + assert.False(t, hasHistory2) + + _ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 10, 100, timeutil.TimeStampNow(), "c-a", true) + _ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 10, 100, timeutil.TimeStampNow().Add(5), "c-b", false) + + hasHistory1, _ = issues_model.HasIssueContentHistory(db.DefaultContext, 10, 0) + assert.False(t, hasHistory1) + hasHistory2, _ = issues_model.HasIssueContentHistory(db.DefaultContext, 10, 100) + assert.True(t, hasHistory2) +} diff --git a/models/issues/issue.go b/models/issues/issue.go index 341ec8547a..9755ece702 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -200,20 +200,6 @@ func (issue *Issue) IsTimetrackerEnabled(ctx context.Context) bool { return issue.Repo.IsTimetrackerEnabled(ctx) } -// GetPullRequest returns the issue pull request -func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) { - if !issue.IsPull { - return nil, fmt.Errorf("Issue is not a pull request") - } - - pr, err = GetPullRequestByIssueID(db.DefaultContext, issue.ID) - if err != nil { - return nil, err - } - pr.Issue = issue - return pr, err -} - // LoadPoster loads poster func (issue *Issue) LoadPoster(ctx context.Context) (err error) { if issue.Poster == nil && issue.PosterID != 0 { diff --git a/models/issues/issue_index.go b/models/issues/issue_index.go index b480cc683f..16274d0ef0 100644 --- a/models/issues/issue_index.go +++ b/models/issues/issue_index.go @@ -3,12 +3,16 @@ package issues -import "code.gitea.io/gitea/models/db" +import ( + "context" + + "code.gitea.io/gitea/models/db" +) // RecalculateIssueIndexForRepo create issue_index for repo if not exist and // update it based on highest index of existing issues assigned to a repo -func RecalculateIssueIndexForRepo(repoID int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func RecalculateIssueIndexForRepo(ctx context.Context, repoID int64) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/issues/issue_label.go b/models/issues/issue_label.go index 119a13adf2..0a2ffdb35f 100644 --- a/models/issues/issue_label.go +++ b/models/issues/issue_label.go @@ -54,6 +54,8 @@ func newIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *user_m return err } + issue.Labels = append(issue.Labels, label) + return updateLabelCols(ctx, label, "num_issues", "num_closed_issue") } @@ -122,6 +124,11 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us if err = issue.LoadRepo(ctx); err != nil { return err } + + if err = issue.LoadLabels(ctx); err != nil { + return err + } + for _, l := range labels { // Don't add already present labels and invalid labels if HasIssueLabel(ctx, issue.ID, l.ID) || @@ -129,6 +136,10 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us continue } + if err = RemoveDuplicateExclusiveIssueLabels(ctx, issue, l, doer); err != nil { + return err + } + if err = newIssueLabel(ctx, issue, l, doer); err != nil { return fmt.Errorf("newIssueLabel: %w", err) } diff --git a/models/issues/issue_label_test.go b/models/issues/issue_label_test.go new file mode 100644 index 0000000000..0d991b7c4f --- /dev/null +++ b/models/issues/issue_label_test.go @@ -0,0 +1,28 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package issues_test + +import ( + "testing" + + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" +) + +func TestNewIssueLabelsScope(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 18}) + label1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 7}) + label2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 8}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + assert.NoError(t, issues_model.NewIssueLabels(issue, []*issues_model.Label{label1, label2}, doer)) + + assert.Len(t, issue.Labels, 1) + assert.Equal(t, label2.ID, issue.Labels[0].ID) +} diff --git a/models/issues/issue_list.go b/models/issues/issue_list.go index a932ac2554..f07dae89fd 100644 --- a/models/issues/issue_list.go +++ b/models/issues/issue_list.go @@ -370,6 +370,9 @@ func (issues IssueList) LoadPullRequests(ctx context.Context) error { for _, issue := range issues { issue.PullRequest = pullRequestMaps[issue.ID] + if issue.PullRequest != nil { + issue.PullRequest.Issue = issue + } } return nil } diff --git a/models/issues/issue_project.go b/models/issues/issue_project.go index ed249527bf..a87e821368 100644 --- a/models/issues/issue_project.go +++ b/models/issues/issue_project.go @@ -100,8 +100,8 @@ func LoadIssuesFromBoardList(ctx context.Context, bs project_model.BoardList) (m } // ChangeProjectAssign changes the project associated with an issue -func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go index 5d40b44704..5c05ead687 100644 --- a/models/issues/issue_search.go +++ b/models/issues/issue_search.go @@ -362,14 +362,21 @@ func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64) From("team_user"). Where(builder.Eq{"team_user.uid": reviewRequestedID}) + // if the review is approved or rejected, it should not be shown in the review requested list + maxReview := builder.Select("MAX(r.id)"). + From("review as r"). + Where(builder.In("r.type", []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest})). + GroupBy("r.issue_id, r.reviewer_id, r.reviewer_team_id") + subQuery := builder.Select("review.issue_id"). From("review"). Where(builder.And( - builder.In("review.type", []ReviewType{ReviewTypeRequest, ReviewTypeReject, ReviewTypeApprove}), + builder.Eq{"review.type": ReviewTypeRequest}, builder.Or( builder.Eq{"review.reviewer_id": reviewRequestedID}, builder.In("review.reviewer_team_id", existInTeamQuery), ), + builder.In("review.id", maxReview), )) return sess.Where("issue.poster_id <> ?", reviewRequestedID). And(builder.In("issue.id", subQuery)) diff --git a/models/issues/issue_stats.go b/models/issues/issue_stats.go index d01ee44462..99ca19f804 100644 --- a/models/issues/issue_stats.go +++ b/models/issues/issue_stats.go @@ -80,9 +80,9 @@ func CountIssues(ctx context.Context, opts *IssuesOptions) (int64, error) { } // GetIssueStats returns issue statistic information by given conditions. -func GetIssueStats(opts *IssuesOptions) (*IssueStats, error) { +func GetIssueStats(ctx context.Context, opts *IssuesOptions) (*IssueStats, error) { if len(opts.IssueIDs) <= MaxQueryParameters { - return getIssueStatsChunk(opts, opts.IssueIDs) + return getIssueStatsChunk(ctx, opts, opts.IssueIDs) } // If too long a list of IDs is provided, we get the statistics in @@ -95,7 +95,7 @@ func GetIssueStats(opts *IssuesOptions) (*IssueStats, error) { if chunk > len(opts.IssueIDs) { chunk = len(opts.IssueIDs) } - stats, err := getIssueStatsChunk(opts, opts.IssueIDs[i:chunk]) + stats, err := getIssueStatsChunk(ctx, opts, opts.IssueIDs[i:chunk]) if err != nil { return nil, err } @@ -112,10 +112,10 @@ func GetIssueStats(opts *IssuesOptions) (*IssueStats, error) { return accum, nil } -func getIssueStatsChunk(opts *IssuesOptions, issueIDs []int64) (*IssueStats, error) { +func getIssueStatsChunk(ctx context.Context, opts *IssuesOptions, issueIDs []int64) (*IssueStats, error) { stats := &IssueStats{} - sess := db.GetEngine(db.DefaultContext). + sess := db.GetEngine(ctx). Join("INNER", "repository", "`issue`.repo_id = `repository`.id") var err error diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go index b7fa7eff1c..5993e918e7 100644 --- a/models/issues/issue_test.go +++ b/models/issues/issue_test.go @@ -253,7 +253,7 @@ func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *is Title: title, Content: content, } - err := issues_model.NewIssue(repo, &issue, nil, nil) + err := issues_model.NewIssue(db.DefaultContext, repo, &issue, nil, nil) assert.NoError(t, err) has, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Get(&newIssue) @@ -369,7 +369,7 @@ func TestCorrectIssueStats(t *testing.T) { // Now we will call the GetIssueStats with these IDs and if working, // get the correct stats back. - issueStats, err := issues_model.GetIssueStats(&issues_model.IssuesOptions{ + issueStats, err := issues_model.GetIssueStats(db.DefaultContext, &issues_model.IssuesOptions{ RepoIDs: []int64{1}, IssueIDs: ids, }) @@ -403,7 +403,7 @@ func TestCountIssues(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) count, err := issues_model.CountIssues(db.DefaultContext, &issues_model.IssuesOptions{}) assert.NoError(t, err) - assert.EqualValues(t, 19, count) + assert.EqualValues(t, 20, count) } func TestIssueLoadAttributes(t *testing.T) { diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go index 9607b21a67..090f501c8f 100644 --- a/models/issues/issue_update.go +++ b/models/issues/issue_update.go @@ -165,8 +165,8 @@ func ChangeIssueTitle(ctx context.Context, issue *Issue, doer *user_model.User, } // ChangeIssueRef changes the branch of this issue, as the given user. -func ChangeIssueRef(issue *Issue, doer *user_model.User, oldRef string) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeIssueRef(ctx context.Context, issue *Issue, doer *user_model.User, oldRef string) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -215,8 +215,8 @@ func AddDeletePRBranchComment(ctx context.Context, doer *user_model.User, repo * } // UpdateIssueAttachments update attachments by UUIDs for the issue -func UpdateIssueAttachments(issueID int64, uuids []string) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func UpdateIssueAttachments(ctx context.Context, issueID int64, uuids []string) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -235,8 +235,8 @@ func UpdateIssueAttachments(issueID int64, uuids []string) (err error) { } // ChangeIssueContent changes issue content, as the given user. -func ChangeIssueContent(issue *Issue, doer *user_model.User, content string) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeIssueContent(ctx context.Context, issue *Issue, doer *user_model.User, content string) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -381,8 +381,8 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue } // NewIssue creates new issue with labels for repository. -func NewIssue(repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -432,8 +432,8 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo // UpdateIssueByAPI updates all allowed fields of given issue. // If the issue status is changed a statusChangeComment is returned // similarly if the title is changed the titleChanged bool is set to true -func UpdateIssueByAPI(issue *Issue, doer *user_model.User) (statusChangeComment *Comment, titleChanged bool, err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func UpdateIssueByAPI(ctx context.Context, issue *Issue, doer *user_model.User) (statusChangeComment *Comment, titleChanged bool, err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, false, err } @@ -486,12 +486,12 @@ func UpdateIssueByAPI(issue *Issue, doer *user_model.User) (statusChangeComment } // UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it. -func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) { +func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) { // if the deadline hasn't changed do nothing if issue.DeadlineUnix == deadlineUnix { return nil } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -669,8 +669,8 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u } // UpdateIssuesMigrationsByType updates all migrated repositories' issues from gitServiceType to replace originalAuthorID to posterID -func UpdateIssuesMigrationsByType(gitServiceType api.GitServiceType, originalAuthorID string, posterID int64) error { - _, err := db.GetEngine(db.DefaultContext).Table("issue"). +func UpdateIssuesMigrationsByType(ctx context.Context, gitServiceType api.GitServiceType, originalAuthorID string, posterID int64) error { + _, err := db.GetEngine(ctx).Table("issue"). Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType). And("original_author_id = ?", originalAuthorID). Update(map[string]any{ @@ -682,8 +682,8 @@ func UpdateIssuesMigrationsByType(gitServiceType api.GitServiceType, originalAut } // UpdateReactionsMigrationsByType updates all migrated repositories' reactions from gitServiceType to replace originalAuthorID to posterID -func UpdateReactionsMigrationsByType(gitServiceType api.GitServiceType, originalAuthorID string, userID int64) error { - _, err := db.GetEngine(db.DefaultContext).Table("reaction"). +func UpdateReactionsMigrationsByType(ctx context.Context, gitServiceType api.GitServiceType, originalAuthorID string, userID int64) error { + _, err := db.GetEngine(ctx).Table("reaction"). Where("original_author_id = ?", originalAuthorID). And(migratedIssueCond(gitServiceType)). Update(map[string]any{ @@ -696,85 +696,100 @@ func UpdateReactionsMigrationsByType(gitServiceType api.GitServiceType, original // DeleteIssuesByRepoID deletes issues by repositories id func DeleteIssuesByRepoID(ctx context.Context, repoID int64) (attachmentPaths []string, err error) { - deleteCond := builder.Select("id").From("issue").Where(builder.Eq{"issue.repo_id": repoID}) - + // MariaDB has a performance bug: https://jira.mariadb.org/browse/MDEV-16289 + // so here it uses "DELETE ... WHERE IN" with pre-queried IDs. sess := db.GetEngine(ctx) - // Delete content histories - if _, err = sess.In("issue_id", deleteCond). - Delete(&ContentHistory{}); err != nil { - return nil, err - } - // Delete comments and attachments - if _, err = sess.In("issue_id", deleteCond). - Delete(&Comment{}); err != nil { - return nil, err - } + for { + issueIDs := make([]int64, 0, db.DefaultMaxInSize) - // Dependencies for issues in this repository - if _, err = sess.In("issue_id", deleteCond). - Delete(&IssueDependency{}); err != nil { - return nil, err - } + err := sess.Table(&Issue{}).Where("repo_id = ?", repoID).OrderBy("id").Limit(db.DefaultMaxInSize).Cols("id").Find(&issueIDs) + if err != nil { + return nil, err + } - // Delete dependencies for issues in other repositories - if _, err = sess.In("dependency_id", deleteCond). - Delete(&IssueDependency{}); err != nil { - return nil, err - } + if len(issueIDs) == 0 { + break + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&IssueUser{}); err != nil { - return nil, err - } + // Delete content histories + _, err = sess.In("issue_id", issueIDs).Delete(&ContentHistory{}) + if err != nil { + return nil, err + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&Reaction{}); err != nil { - return nil, err - } + // Delete comments and attachments + _, err = sess.In("issue_id", issueIDs).Delete(&Comment{}) + if err != nil { + return nil, err + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&IssueWatch{}); err != nil { - return nil, err - } + // Dependencies for issues in this repository + _, err = sess.In("issue_id", issueIDs).Delete(&IssueDependency{}) + if err != nil { + return nil, err + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&Stopwatch{}); err != nil { - return nil, err - } + // Delete dependencies for issues in other repositories + _, err = sess.In("dependency_id", issueIDs).Delete(&IssueDependency{}) + if err != nil { + return nil, err + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&TrackedTime{}); err != nil { - return nil, err - } + _, err = sess.In("issue_id", issueIDs).Delete(&IssueUser{}) + if err != nil { + return nil, err + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&project_model.ProjectIssue{}); err != nil { - return nil, err - } + _, err = sess.In("issue_id", issueIDs).Delete(&Reaction{}) + if err != nil { + return nil, err + } - if _, err = sess.In("dependent_issue_id", deleteCond). - Delete(&Comment{}); err != nil { - return nil, err - } + _, err = sess.In("issue_id", issueIDs).Delete(&IssueWatch{}) + if err != nil { + return nil, err + } - var attachments []*repo_model.Attachment - if err = sess.In("issue_id", deleteCond). - Find(&attachments); err != nil { - return nil, err - } + _, err = sess.In("issue_id", issueIDs).Delete(&Stopwatch{}) + if err != nil { + return nil, err + } - for j := range attachments { - attachmentPaths = append(attachmentPaths, attachments[j].RelativePath()) - } + _, err = sess.In("issue_id", issueIDs).Delete(&TrackedTime{}) + if err != nil { + return nil, err + } - if _, err = sess.In("issue_id", deleteCond). - Delete(&repo_model.Attachment{}); err != nil { - return nil, err - } + _, err = sess.In("issue_id", issueIDs).Delete(&project_model.ProjectIssue{}) + if err != nil { + return nil, err + } - if _, err = db.DeleteByBean(ctx, &Issue{RepoID: repoID}); err != nil { - return nil, err + _, err = sess.In("dependent_issue_id", issueIDs).Delete(&Comment{}) + if err != nil { + return nil, err + } + + var attachments []*repo_model.Attachment + err = sess.In("issue_id", issueIDs).Find(&attachments) + if err != nil { + return nil, err + } + + for j := range attachments { + attachmentPaths = append(attachmentPaths, attachments[j].RelativePath()) + } + + _, err = sess.In("issue_id", issueIDs).Delete(&repo_model.Attachment{}) + if err != nil { + return nil, err + } + + _, err = sess.In("id", issueIDs).Delete(&Issue{}) + if err != nil { + return nil, err + } } return attachmentPaths, err @@ -809,7 +824,7 @@ func DeleteOrphanedIssues(ctx context.Context) error { // Remove issue attachment files. for i := range attachmentPaths { - system_model.RemoveAllWithNotice(db.DefaultContext, "Delete issue attachment", attachmentPaths[i]) + system_model.RemoveAllWithNotice(ctx, "Delete issue attachment", attachmentPaths[i]) } return nil } diff --git a/models/issues/issue_user.go b/models/issues/issue_user.go index d053b1d543..24bb74648d 100644 --- a/models/issues/issue_user.go +++ b/models/issues/issue_user.go @@ -15,7 +15,7 @@ import ( type IssueUser struct { ID int64 `xorm:"pk autoincr"` UID int64 `xorm:"INDEX"` // User ID. - IssueID int64 + IssueID int64 `xorm:"INDEX"` IsRead bool IsMentioned bool } diff --git a/models/issues/issue_xref.go b/models/issues/issue_xref.go index a1086f9e81..77ef53a013 100644 --- a/models/issues/issue_xref.go +++ b/models/issues/issue_xref.go @@ -252,22 +252,22 @@ func (c *Comment) neuterCrossReferences(ctx context.Context) error { } // LoadRefComment loads comment that created this reference from database -func (c *Comment) LoadRefComment() (err error) { +func (c *Comment) LoadRefComment(ctx context.Context) (err error) { if c.RefComment != nil { return nil } - c.RefComment, err = GetCommentByID(db.DefaultContext, c.RefCommentID) + c.RefComment, err = GetCommentByID(ctx, c.RefCommentID) return err } // LoadRefIssue loads comment that created this reference from database -func (c *Comment) LoadRefIssue() (err error) { +func (c *Comment) LoadRefIssue(ctx context.Context) (err error) { if c.RefIssue != nil { return nil } - c.RefIssue, err = GetIssueByID(db.DefaultContext, c.RefIssueID) + c.RefIssue, err = GetIssueByID(ctx, c.RefIssueID) if err == nil { - err = c.RefIssue.LoadRepo(db.DefaultContext) + err = c.RefIssue.LoadRepo(ctx) } return err } @@ -278,21 +278,21 @@ func CommentTypeIsRef(t CommentType) bool { } // RefCommentLink returns the relative URL for the comment that created this reference -func (c *Comment) RefCommentLink() string { +func (c *Comment) RefCommentLink(ctx context.Context) string { // Edge case for when the reference is inside the title or the description of the referring issue if c.RefCommentID == 0 { - return c.RefIssueLink() + return c.RefIssueLink(ctx) } - if err := c.LoadRefComment(); err != nil { // Silently dropping errors :unamused: + if err := c.LoadRefComment(ctx); err != nil { // Silently dropping errors :unamused: log.Error("LoadRefComment(%d): %v", c.RefCommentID, err) return "" } - return c.RefComment.Link() + return c.RefComment.Link(ctx) } // RefIssueLink returns the relative URL of the issue where this reference was created -func (c *Comment) RefIssueLink() string { - if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: +func (c *Comment) RefIssueLink(ctx context.Context) string { + if err := c.LoadRefIssue(ctx); err != nil { // Silently dropping errors :unamused: log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err) return "" } @@ -300,8 +300,8 @@ func (c *Comment) RefIssueLink() string { } // RefIssueTitle returns the title of the issue where this reference was created -func (c *Comment) RefIssueTitle() string { - if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: +func (c *Comment) RefIssueTitle(ctx context.Context) string { + if err := c.LoadRefIssue(ctx); err != nil { // Silently dropping errors :unamused: log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err) return "" } @@ -309,8 +309,8 @@ func (c *Comment) RefIssueTitle() string { } // RefIssueIdent returns the user friendly identity (e.g. "#1234") of the issue where this reference was created -func (c *Comment) RefIssueIdent() string { - if err := c.LoadRefIssue(); err != nil { // Silently dropping errors :unamused: +func (c *Comment) RefIssueIdent(ctx context.Context) string { + if err := c.LoadRefIssue(ctx); err != nil { // Silently dropping errors :unamused: log.Error("LoadRefIssue(%d): %v", c.RefCommentID, err) return "" } diff --git a/models/issues/pull.go b/models/issues/pull.go index 1c163ecca4..b6ac47aad2 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -311,7 +311,7 @@ func (pr *PullRequest) LoadRequestedReviewers(ctx context.Context) error { return nil } - reviews, err := GetReviewsByIssueID(pr.Issue.ID) + reviews, err := GetReviewsByIssueID(ctx, pr.Issue.ID) if err != nil { return err } @@ -706,8 +706,8 @@ func (pr *PullRequest) UpdateColsIfNotMerged(ctx context.Context, cols ...string // IsWorkInProgress determine if the Pull Request is a Work In Progress by its title // Issue must be set before this method can be called. -func (pr *PullRequest) IsWorkInProgress() bool { - if err := pr.LoadIssue(db.DefaultContext); err != nil { +func (pr *PullRequest) IsWorkInProgress(ctx context.Context) bool { + if err := pr.LoadIssue(ctx); err != nil { log.Error("LoadIssue: %v", err) return false } @@ -810,14 +810,14 @@ func UpdateAllowEdits(ctx context.Context, pr *PullRequest) error { } // Mergeable returns if the pullrequest is mergeable. -func (pr *PullRequest) Mergeable() bool { +func (pr *PullRequest) Mergeable(ctx context.Context) bool { // If a pull request isn't mergable if it's: // - Being conflict checked. // - Has a conflict. // - Received a error while being conflict checked. // - Is a work-in-progress pull request. return pr.Status != PullRequestStatusChecking && pr.Status != PullRequestStatusConflict && - pr.Status != PullRequestStatusError && !pr.IsWorkInProgress() + pr.Status != PullRequestStatusError && !pr.IsWorkInProgress(ctx) } // HasEnoughApprovals returns true if pr has enough granted approvals. @@ -887,82 +887,6 @@ func MergeBlockedByOutdatedBranch(protectBranch *git_model.ProtectedBranch, pr * return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0 } -func PullRequestCodeOwnersReview(ctx context.Context, pull *Issue, pr *PullRequest) error { - files := []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"} - - if pr.IsWorkInProgress() { - return nil - } - - if err := pr.LoadBaseRepo(ctx); err != nil { - return err - } - - repo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath()) - if err != nil { - return err - } - defer repo.Close() - - branch, err := repo.GetDefaultBranch() - if err != nil { - return err - } - - commit, err := repo.GetBranchCommit(branch) - if err != nil { - return err - } - - var data string - for _, file := range files { - if blob, err := commit.GetBlobByPath(file); err == nil { - data, err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize) - if err == nil { - break - } - } - } - - rules, _ := GetCodeOwnersFromContent(ctx, data) - changedFiles, err := repo.GetFilesChangedBetween(git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName()) - if err != nil { - return err - } - - uniqUsers := make(map[int64]*user_model.User) - uniqTeams := make(map[string]*org_model.Team) - for _, rule := range rules { - for _, f := range changedFiles { - if (rule.Rule.MatchString(f) && !rule.Negative) || (!rule.Rule.MatchString(f) && rule.Negative) { - for _, u := range rule.Users { - uniqUsers[u.ID] = u - } - for _, t := range rule.Teams { - uniqTeams[fmt.Sprintf("%d/%d", t.OrgID, t.ID)] = t - } - } - } - } - - for _, u := range uniqUsers { - if u.ID != pull.Poster.ID { - if _, err := AddReviewRequest(ctx, pull, u, pull.Poster); err != nil { - log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err) - return err - } - } - } - for _, t := range uniqTeams { - if _, err := AddTeamReviewRequest(ctx, pull, t, pull.Poster); err != nil { - log.Warn("Failed add assignee team: %s to PR review: %s#%d, error: %s", t.Name, pr.BaseRepo.Name, pr.ID, err) - return err - } - } - - return nil -} - // GetCodeOwnersFromContent returns the code owners configuration // Return empty slice if files missing // Return warning messages on parsing errors diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go index c209386e2e..2ee69cd323 100644 --- a/models/issues/pull_list.go +++ b/models/issues/pull_list.go @@ -212,3 +212,12 @@ func HasMergedPullRequestInRepo(ctx context.Context, repoID, posterID int64) (bo Limit(1). Get(new(Issue)) } + +// GetPullRequestByIssueIDs returns all pull requests by issue ids +func GetPullRequestByIssueIDs(ctx context.Context, issueIDs []int64) (PullRequestList, error) { + prs := make([]*PullRequest, 0, len(issueIDs)) + return prs, db.GetEngine(ctx). + Where("issue_id > 0"). + In("issue_id", issueIDs). + Find(&prs) +} diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go index 3636263c46..7307de9002 100644 --- a/models/issues/pull_test.go +++ b/models/issues/pull_test.go @@ -260,13 +260,13 @@ func TestPullRequest_IsWorkInProgress(t *testing.T) { pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) pr.LoadIssue(db.DefaultContext) - assert.False(t, pr.IsWorkInProgress()) + assert.False(t, pr.IsWorkInProgress(db.DefaultContext)) pr.Issue.Title = "WIP: " + pr.Issue.Title - assert.True(t, pr.IsWorkInProgress()) + assert.True(t, pr.IsWorkInProgress(db.DefaultContext)) pr.Issue.Title = "[wip]: " + pr.Issue.Title - assert.True(t, pr.IsWorkInProgress()) + assert.True(t, pr.IsWorkInProgress(db.DefaultContext)) } func TestPullRequest_GetWorkInProgressPrefixWorkInProgress(t *testing.T) { diff --git a/models/issues/reaction.go b/models/issues/reaction.go index 28da696366..bb47cf24ca 100644 --- a/models/issues/reaction.go +++ b/models/issues/reaction.go @@ -71,11 +71,11 @@ type Reaction struct { } // LoadUser load user of reaction -func (r *Reaction) LoadUser() (*user_model.User, error) { +func (r *Reaction) LoadUser(ctx context.Context) (*user_model.User, error) { if r.User != nil { return r.User, nil } - user, err := user_model.GetUserByID(db.DefaultContext, r.UserID) + user, err := user_model.GetUserByID(ctx, r.UserID) if err != nil { return nil, err } @@ -141,16 +141,16 @@ func (opts *FindReactionsOptions) toConds() builder.Cond { } // FindCommentReactions returns a ReactionList of all reactions from an comment -func FindCommentReactions(issueID, commentID int64) (ReactionList, int64, error) { - return FindReactions(db.DefaultContext, FindReactionsOptions{ +func FindCommentReactions(ctx context.Context, issueID, commentID int64) (ReactionList, int64, error) { + return FindReactions(ctx, FindReactionsOptions{ IssueID: issueID, CommentID: commentID, }) } // FindIssueReactions returns a ReactionList of all reactions from an issue -func FindIssueReactions(issueID int64, listOptions db.ListOptions) (ReactionList, int64, error) { - return FindReactions(db.DefaultContext, FindReactionsOptions{ +func FindIssueReactions(ctx context.Context, issueID int64, listOptions db.ListOptions) (ReactionList, int64, error) { + return FindReactions(ctx, FindReactionsOptions{ ListOptions: listOptions, IssueID: issueID, CommentID: -1, @@ -218,12 +218,12 @@ type ReactionOptions struct { } // CreateReaction creates reaction for issue or comment. -func CreateReaction(opts *ReactionOptions) (*Reaction, error) { +func CreateReaction(ctx context.Context, opts *ReactionOptions) (*Reaction, error) { if !setting.UI.ReactionsLookup.Contains(opts.Type) { return nil, ErrForbiddenIssueReaction{opts.Type} } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, err } @@ -241,8 +241,8 @@ func CreateReaction(opts *ReactionOptions) (*Reaction, error) { } // CreateIssueReaction creates a reaction on issue. -func CreateIssueReaction(doerID, issueID int64, content string) (*Reaction, error) { - return CreateReaction(&ReactionOptions{ +func CreateIssueReaction(ctx context.Context, doerID, issueID int64, content string) (*Reaction, error) { + return CreateReaction(ctx, &ReactionOptions{ Type: content, DoerID: doerID, IssueID: issueID, @@ -250,8 +250,8 @@ func CreateIssueReaction(doerID, issueID int64, content string) (*Reaction, erro } // CreateCommentReaction creates a reaction on comment. -func CreateCommentReaction(doerID, issueID, commentID int64, content string) (*Reaction, error) { - return CreateReaction(&ReactionOptions{ +func CreateCommentReaction(ctx context.Context, doerID, issueID, commentID int64, content string) (*Reaction, error) { + return CreateReaction(ctx, &ReactionOptions{ Type: content, DoerID: doerID, IssueID: issueID, @@ -279,8 +279,8 @@ func DeleteReaction(ctx context.Context, opts *ReactionOptions) error { } // DeleteIssueReaction deletes a reaction on issue. -func DeleteIssueReaction(doerID, issueID int64, content string) error { - return DeleteReaction(db.DefaultContext, &ReactionOptions{ +func DeleteIssueReaction(ctx context.Context, doerID, issueID int64, content string) error { + return DeleteReaction(ctx, &ReactionOptions{ Type: content, DoerID: doerID, IssueID: issueID, @@ -289,8 +289,8 @@ func DeleteIssueReaction(doerID, issueID int64, content string) error { } // DeleteCommentReaction deletes a reaction on comment. -func DeleteCommentReaction(doerID, issueID, commentID int64, content string) error { - return DeleteReaction(db.DefaultContext, &ReactionOptions{ +func DeleteCommentReaction(ctx context.Context, doerID, issueID, commentID int64, content string) error { + return DeleteReaction(ctx, &ReactionOptions{ Type: content, DoerID: doerID, IssueID: issueID, diff --git a/models/issues/reaction_test.go b/models/issues/reaction_test.go index ceb7f2c2a6..5dc8e1a5f3 100644 --- a/models/issues/reaction_test.go +++ b/models/issues/reaction_test.go @@ -20,9 +20,9 @@ func addReaction(t *testing.T, doerID, issueID, commentID int64, content string) var reaction *issues_model.Reaction var err error if commentID == 0 { - reaction, err = issues_model.CreateIssueReaction(doerID, issueID, content) + reaction, err = issues_model.CreateIssueReaction(db.DefaultContext, doerID, issueID, content) } else { - reaction, err = issues_model.CreateCommentReaction(doerID, issueID, commentID, content) + reaction, err = issues_model.CreateCommentReaction(db.DefaultContext, doerID, issueID, commentID, content) } assert.NoError(t, err) assert.NotNil(t, reaction) @@ -49,7 +49,7 @@ func TestIssueAddDuplicateReaction(t *testing.T) { addReaction(t, user1.ID, issue1ID, 0, "heart") - reaction, err := issues_model.CreateReaction(&issues_model.ReactionOptions{ + reaction, err := issues_model.CreateReaction(db.DefaultContext, &issues_model.ReactionOptions{ DoerID: user1.ID, IssueID: issue1ID, Type: "heart", @@ -70,7 +70,7 @@ func TestIssueDeleteReaction(t *testing.T) { addReaction(t, user1.ID, issue1ID, 0, "heart") - err := issues_model.DeleteIssueReaction(user1.ID, issue1ID, "heart") + err := issues_model.DeleteIssueReaction(db.DefaultContext, user1.ID, issue1ID, "heart") assert.NoError(t, err) unittest.AssertNotExistsBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID}) @@ -168,7 +168,7 @@ func TestIssueCommentReactionCount(t *testing.T) { var comment1ID int64 = 1 addReaction(t, user1.ID, issue1ID, comment1ID, "heart") - assert.NoError(t, issues_model.DeleteCommentReaction(user1.ID, issue1ID, comment1ID, "heart")) + assert.NoError(t, issues_model.DeleteCommentReaction(db.DefaultContext, user1.ID, issue1ID, comment1ID, "heart")) unittest.AssertNotExistsBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue1ID, CommentID: comment1ID}) } diff --git a/models/issues/review.go b/models/issues/review.go index 931f1a2ba7..718708ec59 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -159,6 +159,14 @@ func (r *Review) LoadReviewer(ctx context.Context) (err error) { return err } r.Reviewer, err = user_model.GetPossibleUserByID(ctx, r.ReviewerID) + if err != nil { + if !user_model.IsErrUserNotExist(err) { + return fmt.Errorf("GetPossibleUserByID [%d]: %w", r.ReviewerID, err) + } + r.ReviewerID = user_model.GhostUserID + r.Reviewer = user_model.NewGhostUser() + return nil + } return err } @@ -231,25 +239,32 @@ type CreateReviewOptions struct { } // IsOfficialReviewer check if at least one of the provided reviewers can make official reviews in issue (counts towards required approvals) -func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewers ...*user_model.User) (bool, error) { - pr, err := GetPullRequestByIssueID(ctx, issue.ID) - if err != nil { +func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewer *user_model.User) (bool, error) { + if err := issue.LoadPullRequest(ctx); err != nil { return false, err } + pr := issue.PullRequest rule, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) if err != nil { return false, err } if rule == nil { - return false, nil + // if no rule is found, then user with write access can make official reviews + err := pr.LoadBaseRepo(ctx) + if err != nil { + return false, err + } + writeAccess, err := access_model.HasAccessUnit(ctx, reviewer, pr.BaseRepo, unit.TypeCode, perm.AccessModeWrite) + if err != nil { + return false, err + } + return writeAccess, nil } - for _, reviewer := range reviewers { - official, err := git_model.IsUserOfficialReviewer(ctx, rule, reviewer) - if official || err != nil { - return official, err - } + official, err := git_model.IsUserOfficialReviewer(ctx, rule, reviewer) + if official || err != nil { + return official, err } return false, nil @@ -257,11 +272,10 @@ func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewers ...*user_mo // IsOfficialReviewerTeam check if reviewer in this team can make official reviews in issue (counts towards required approvals) func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organization.Team) (bool, error) { - pr, err := GetPullRequestByIssueID(ctx, issue.ID) - if err != nil { + if err := issue.LoadPullRequest(ctx); err != nil { return false, err } - pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pr.BaseRepoID, pr.BaseBranch) + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, issue.PullRequest.BaseRepoID, issue.PullRequest.BaseBranch) if err != nil { return false, err } @@ -278,8 +292,14 @@ func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio // CreateReview creates a new review based on opts func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error) { + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return nil, err + } + defer committer.Close() + sess := db.GetEngine(ctx) + review := &Review{ - Type: opts.Type, Issue: opts.Issue, IssueID: opts.Issue.ID, Reviewer: opts.Reviewer, @@ -289,15 +309,39 @@ func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error CommitID: opts.CommitID, Stale: opts.Stale, } + if opts.Reviewer != nil { + review.Type = opts.Type review.ReviewerID = opts.Reviewer.ID - } else { - if review.Type != ReviewTypeRequest { - review.Type = ReviewTypeRequest + + reviewCond := builder.Eq{"reviewer_id": opts.Reviewer.ID, "issue_id": opts.Issue.ID} + // make sure user review requests are cleared + if opts.Type != ReviewTypePending { + if _, err := sess.Where(reviewCond.And(builder.Eq{"type": ReviewTypeRequest})).Delete(new(Review)); err != nil { + return nil, err + } } + // make sure if the created review gets dismissed no old review surface + // other types can be ignored, as they don't affect branch protection + if opts.Type == ReviewTypeApprove || opts.Type == ReviewTypeReject { + if _, err := sess.Where(reviewCond.And(builder.In("type", ReviewTypeApprove, ReviewTypeReject))). + Cols("dismissed").Update(&Review{Dismissed: true}); err != nil { + return nil, err + } + } + + } else if opts.ReviewerTeam != nil { + review.Type = ReviewTypeRequest review.ReviewerTeamID = opts.ReviewerTeam.ID + + } else { + return nil, fmt.Errorf("provide either reviewer or reviewer team") } - return review, db.Insert(ctx, review) + + if _, err := sess.Insert(review); err != nil { + return nil, err + } + return review, committer.Commit() } // GetCurrentReview returns the current pending review of reviewer for given issue @@ -322,8 +366,8 @@ func GetCurrentReview(ctx context.Context, reviewer *user_model.User, issue *Iss } // ReviewExists returns whether a review exists for a particular line of code in the PR -func ReviewExists(issue *Issue, treePath string, line int64) (bool, error) { - return db.GetEngine(db.DefaultContext).Cols("id").Exist(&Comment{IssueID: issue.ID, TreePath: treePath, Line: line, Type: CommentTypeCode}) +func ReviewExists(ctx context.Context, issue *Issue, treePath string, line int64) (bool, error) { + return db.GetEngine(ctx).Cols("id").Exist(&Comment{IssueID: issue.ID, TreePath: treePath, Line: line, Type: CommentTypeCode}) } // ContentEmptyErr represents an content empty error @@ -340,8 +384,8 @@ func IsContentEmptyErr(err error) bool { } // SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist -func SubmitReview(doer *user_model.User, issue *Issue, reviewType ReviewType, content, commitID string, stale bool, attachmentUUIDs []string) (*Review, *Comment, error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func SubmitReview(ctx context.Context, doer *user_model.User, issue *Issue, reviewType ReviewType, content, commitID string, stale bool, attachmentUUIDs []string) (*Review, *Comment, error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return nil, nil, err } @@ -454,8 +498,10 @@ func SubmitReview(doer *user_model.User, issue *Issue, reviewType ReviewType, co func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*Review, error) { review := new(Review) - has, err := db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_id = ? AND original_author_id = 0 AND type in (?, ?, ?))", - issueID, userID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest). + has, err := db.GetEngine(ctx).Where( + builder.In("type", ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest). + And(builder.Eq{"issue_id": issueID, "reviewer_id": userID, "original_author_id": 0})). + Desc("id"). Get(review) if err != nil { return nil, err @@ -469,13 +515,13 @@ func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*R } // GetTeamReviewerByIssueIDAndTeamID get the latest review request of reviewer team for a pull request -func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int64) (review *Review, err error) { - review = new(Review) +func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int64) (*Review, error) { + review := new(Review) - var has bool - if has, err = db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = ?)", - issueID, teamID). - Get(review); err != nil { + has, err := db.GetEngine(ctx).Where(builder.Eq{"issue_id": issueID, "reviewer_team_id": teamID}). + Desc("id"). + Get(review) + if err != nil { return nil, err } @@ -487,15 +533,15 @@ func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int6 } // MarkReviewsAsStale marks existing reviews as stale -func MarkReviewsAsStale(issueID int64) (err error) { - _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `review` SET stale=? WHERE issue_id=?", true, issueID) +func MarkReviewsAsStale(ctx context.Context, issueID int64) (err error) { + _, err = db.GetEngine(ctx).Exec("UPDATE `review` SET stale=? WHERE issue_id=?", true, issueID) return err } // MarkReviewsAsNotStale marks existing reviews as not stale for a giving commit SHA -func MarkReviewsAsNotStale(issueID int64, commitID string) (err error) { - _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `review` SET stale=? WHERE issue_id=? AND commit_id=?", false, issueID, commitID) +func MarkReviewsAsNotStale(ctx context.Context, issueID int64, commitID string) (err error) { + _, err = db.GetEngine(ctx).Exec("UPDATE `review` SET stale=? WHERE issue_id=? AND commit_id=?", false, issueID, commitID) return err } @@ -518,8 +564,8 @@ func DismissReview(ctx context.Context, review *Review, isDismiss bool) (err err } // InsertReviews inserts review and review comments -func InsertReviews(reviews []*Review) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func InsertReviews(ctx context.Context, reviews []*Review) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -578,7 +624,9 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo return nil, nil } - official, err := IsOfficialReviewer(ctx, issue, reviewer, doer) + // if the reviewer is an official reviewer, + // remove the official flag in the all previous reviews + official, err := IsOfficialReviewer(ctx, issue, reviewer) if err != nil { return nil, err } else if official { @@ -611,6 +659,9 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo return nil, err } + // func caller use the created comment to retrieve created review too. + comment.Review = review + return comment, committer.Commit() } @@ -794,7 +845,7 @@ func RemoveTeamReviewRequest(ctx context.Context, issue *Issue, reviewer *organi } // MarkConversation Add or remove Conversation mark for a code comment -func MarkConversation(comment *Comment, doer *user_model.User, isResolve bool) (err error) { +func MarkConversation(ctx context.Context, comment *Comment, doer *user_model.User, isResolve bool) (err error) { if comment.Type != CommentTypeCode { return nil } @@ -804,7 +855,7 @@ func MarkConversation(comment *Comment, doer *user_model.User, isResolve bool) ( return nil } - if _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", doer.ID, comment.ID); err != nil { + if _, err = db.GetEngine(ctx).Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", doer.ID, comment.ID); err != nil { return err } } else { @@ -812,7 +863,7 @@ func MarkConversation(comment *Comment, doer *user_model.User, isResolve bool) ( return nil } - if _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", 0, comment.ID); err != nil { + if _, err = db.GetEngine(ctx).Exec("UPDATE `comment` SET resolve_doer_id=? WHERE id=?", 0, comment.ID); err != nil { return err } } @@ -822,24 +873,24 @@ func MarkConversation(comment *Comment, doer *user_model.User, isResolve bool) ( // CanMarkConversation Add or remove Conversation mark for a code comment permission check // the PR writer , offfcial reviewer and poster can do it -func CanMarkConversation(issue *Issue, doer *user_model.User) (permResult bool, err error) { +func CanMarkConversation(ctx context.Context, issue *Issue, doer *user_model.User) (permResult bool, err error) { if doer == nil || issue == nil { return false, fmt.Errorf("issue or doer is nil") } if doer.ID != issue.PosterID { - if err = issue.LoadRepo(db.DefaultContext); err != nil { + if err = issue.LoadRepo(ctx); err != nil { return false, err } - p, err := access_model.GetUserRepoPermission(db.DefaultContext, issue.Repo, doer) + p, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) if err != nil { return false, err } permResult = p.CanAccess(perm.AccessModeWrite, unit.TypePullRequests) if !permResult { - if permResult, err = IsOfficialReviewer(db.DefaultContext, issue, doer); err != nil { + if permResult, err = IsOfficialReviewer(ctx, issue, doer); err != nil { return false, err } } @@ -853,8 +904,8 @@ func CanMarkConversation(issue *Issue, doer *user_model.User) (permResult bool, } // DeleteReview delete a review and it's code comments -func DeleteReview(r *Review) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func DeleteReview(ctx context.Context, r *Review) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -889,6 +940,16 @@ func DeleteReview(r *Review) error { return err } + opts = FindCommentsOptions{ + Type: CommentTypeDismissReview, + IssueID: r.IssueID, + ReviewID: r.ID, + } + + if _, err := sess.Where(opts.ToConds()).Delete(new(Comment)); err != nil { + return err + } + if _, err := sess.ID(r.ID).Delete(new(Review)); err != nil { return err } @@ -903,7 +964,7 @@ func DeleteReview(r *Review) error { } // GetCodeCommentsCount return count of CodeComments a Review has -func (r *Review) GetCodeCommentsCount() int { +func (r *Review) GetCodeCommentsCount(ctx context.Context) int { opts := FindCommentsOptions{ Type: CommentTypeCode, IssueID: r.IssueID, @@ -914,7 +975,7 @@ func (r *Review) GetCodeCommentsCount() int { conds = conds.And(builder.Eq{"invalidated": false}) } - count, err := db.GetEngine(db.DefaultContext).Where(conds).Count(new(Comment)) + count, err := db.GetEngine(ctx).Where(conds).Count(new(Comment)) if err != nil { return 0 } @@ -922,18 +983,18 @@ func (r *Review) GetCodeCommentsCount() int { } // HTMLURL formats a URL-string to the related review issue-comment -func (r *Review) HTMLURL() string { +func (r *Review) HTMLURL(ctx context.Context) string { opts := FindCommentsOptions{ Type: CommentTypeReview, IssueID: r.IssueID, ReviewID: r.ID, } comment := new(Comment) - has, err := db.GetEngine(db.DefaultContext).Where(opts.ToConds()).Get(comment) + has, err := db.GetEngine(ctx).Where(opts.ToConds()).Get(comment) if err != nil || !has { return "" } - return comment.HTMLURL() + return comment.HTMLURL(ctx) } // RemapExternalUser ExternalUserRemappable interface @@ -954,8 +1015,8 @@ func (r *Review) GetExternalName() string { return r.OriginalAuthor } func (r *Review) GetExternalID() int64 { return r.OriginalAuthorID } // UpdateReviewsMigrationsByType updates reviews' migrations information via given git service type and original id and poster id -func UpdateReviewsMigrationsByType(tp structs.GitServiceType, originalAuthorID string, posterID int64) error { - _, err := db.GetEngine(db.DefaultContext).Table("review"). +func UpdateReviewsMigrationsByType(ctx context.Context, tp structs.GitServiceType, originalAuthorID string, posterID int64) error { + _, err := db.GetEngine(ctx).Table("review"). Where("original_author_id = ?", originalAuthorID). And(migratedIssueCond(tp)). Update(map[string]any{ diff --git a/models/issues/review_list.go b/models/issues/review_list.go index 9f50d8e09d..ed3d0bd028 100644 --- a/models/issues/review_list.go +++ b/models/issues/review_list.go @@ -126,16 +126,16 @@ func FindLatestReviews(ctx context.Context, opts FindReviewOptions) (ReviewList, } // CountReviews returns count of reviews passing FindReviewOptions -func CountReviews(opts FindReviewOptions) (int64, error) { - return db.GetEngine(db.DefaultContext).Where(opts.toCond()).Count(&Review{}) +func CountReviews(ctx context.Context, opts FindReviewOptions) (int64, error) { + return db.GetEngine(ctx).Where(opts.toCond()).Count(&Review{}) } // GetReviewersFromOriginalAuthorsByIssueID gets the latest review of each original authors for a pull request -func GetReviewersFromOriginalAuthorsByIssueID(issueID int64) (ReviewList, error) { +func GetReviewersFromOriginalAuthorsByIssueID(ctx context.Context, issueID int64) (ReviewList, error) { reviews := make([]*Review, 0, 10) // Get latest review of each reviewer, sorted in order they were made - if err := db.GetEngine(db.DefaultContext).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND original_author_id <> 0 GROUP BY issue_id, original_author_id) ORDER BY review.updated_unix ASC", + if err := db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND original_author_id <> 0 GROUP BY issue_id, original_author_id) ORDER BY review.updated_unix ASC", issueID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest). Find(&reviews); err != nil { return nil, err @@ -145,10 +145,10 @@ func GetReviewersFromOriginalAuthorsByIssueID(issueID int64) (ReviewList, error) } // GetReviewsByIssueID gets the latest review of each reviewer for a pull request -func GetReviewsByIssueID(issueID int64) (ReviewList, error) { +func GetReviewsByIssueID(ctx context.Context, issueID int64) (ReviewList, error) { reviews := make([]*Review, 0, 10) - sess := db.GetEngine(db.DefaultContext) + sess := db.GetEngine(ctx) // Get latest review of each reviewer, sorted in order they were made if err := sess.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = 0 AND type in (?, ?, ?) AND dismissed = ? AND original_author_id = 0 GROUP BY issue_id, reviewer_id) ORDER BY review.updated_unix ASC", diff --git a/models/issues/review_test.go b/models/issues/review_test.go index 8f0e773f4c..1868cb1bfa 100644 --- a/models/issues/review_test.go +++ b/models/issues/review_test.go @@ -8,6 +8,7 @@ import ( "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -144,7 +145,7 @@ func TestGetReviewersByIssueID(t *testing.T) { UpdatedUnix: 946684814, }) - allReviews, err := issues_model.GetReviewsByIssueID(issue.ID) + allReviews, err := issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID) assert.NoError(t, err) for _, review := range allReviews { assert.NoError(t, review.LoadReviewer(db.DefaultContext)) @@ -157,7 +158,7 @@ func TestGetReviewersByIssueID(t *testing.T) { } } - allReviews, err = issues_model.GetReviewsByIssueID(issue.ID) + allReviews, err = issues_model.GetReviewsByIssueID(db.DefaultContext, issue.ID) assert.NoError(t, err) assert.NoError(t, allReviews.LoadReviewers(db.DefaultContext)) if assert.Len(t, allReviews, 3) { @@ -248,7 +249,7 @@ func TestDeleteReview(t *testing.T) { }) assert.NoError(t, err) - assert.NoError(t, issues_model.DeleteReview(review2)) + assert.NoError(t, issues_model.DeleteReview(db.DefaultContext, review2)) _, err = issues_model.GetReviewByID(db.DefaultContext, review2.ID) assert.Error(t, err) @@ -258,3 +259,32 @@ func TestDeleteReview(t *testing.T) { assert.NoError(t, err) assert.True(t, review1.Official) } + +func TestDeleteDismissedReview(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + review, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{ + Content: "reject", + Type: issues_model.ReviewTypeReject, + Official: false, + Issue: issue, + Reviewer: user, + }) + assert.NoError(t, err) + assert.NoError(t, issues_model.DismissReview(db.DefaultContext, review, true)) + comment, err := issues_model.CreateComment(db.DefaultContext, &issues_model.CreateCommentOptions{ + Type: issues_model.CommentTypeDismissReview, + Doer: user, + Repo: repo, + Issue: issue, + ReviewID: review.ID, + Content: "dismiss", + }) + assert.NoError(t, err) + unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID}) + assert.NoError(t, issues_model.DeleteReview(db.DefaultContext, review)) + unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: comment.ID}) +} diff --git a/models/migrations/base/db_test.go b/models/migrations/base/db_test.go index 4d61b758cf..80bf00b22a 100644 --- a/models/migrations/base/db_test.go +++ b/models/migrations/base/db_test.go @@ -36,12 +36,14 @@ func Test_DropTableColumns(t *testing.T) { "updated_unix", } + x.SetMapper(names.GonicMapper{}) + for i := range columns { - x.SetMapper(names.GonicMapper{}) if err := x.Sync(new(DropTest)); err != nil { t.Errorf("unable to create DropTest table: %v", err) return } + sess := x.NewSession() if err := sess.Begin(); err != nil { sess.Close() @@ -64,7 +66,6 @@ func Test_DropTableColumns(t *testing.T) { return } for j := range columns[i+1:] { - x.SetMapper(names.GonicMapper{}) if err := x.Sync(new(DropTest)); err != nil { t.Errorf("unable to create DropTest table: %v", err) return diff --git a/models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml b/models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml new file mode 100644 index 0000000000..f95d47916b --- /dev/null +++ b/models/migrations/fixtures/Test_AddIssueResourceIndexTable/issue.yml @@ -0,0 +1,4 @@ +- + id: 1 + repo_id: 1 + index: 1 diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml b/models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml new file mode 100644 index 0000000000..056236ba9e --- /dev/null +++ b/models/migrations/fixtures/Test_AddRepoIDForAttachment/attachment.yml @@ -0,0 +1,11 @@ +- + id: 1 + uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 + issue_id: 1 + release_id: 0 + +- + id: 2 + uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12 + issue_id: 0 + release_id: 1 diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml b/models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml new file mode 100644 index 0000000000..7f3255096d --- /dev/null +++ b/models/migrations/fixtures/Test_AddRepoIDForAttachment/issue.yml @@ -0,0 +1,3 @@ +- + id: 1 + repo_id: 1 diff --git a/models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml b/models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml new file mode 100644 index 0000000000..7f3255096d --- /dev/null +++ b/models/migrations/fixtures/Test_AddRepoIDForAttachment/release.yml @@ -0,0 +1,3 @@ +- + id: 1 + repo_id: 1 diff --git a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml index 96104ac04a..b02cb570ec 100644 --- a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml +++ b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/issue_label.yml @@ -26,4 +26,3 @@ id: 5 issue_id: 2 label_id: 87 - diff --git a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml index d651c87d5b..fa9658aa02 100644 --- a/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml +++ b/models/migrations/fixtures/Test_DeleteOrphanedIssueLabels/label.yml @@ -18,7 +18,7 @@ - id: 3 repo_id: 0 - org_id: 3 + org_id: 3 name: orglabel3 color: '#abcdef' num_issues: 0 diff --git a/models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml b/models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml index 094b811d91..0f5a3eb507 100644 --- a/models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml +++ b/models/migrations/fixtures/Test_RemoveInvalidLabels/label.yml @@ -23,4 +23,3 @@ id: 5 repo_id: 3 org_id: 0 - diff --git a/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml b/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml index c02a76e374..ebb73f44de 100644 --- a/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml +++ b/models/migrations/fixtures/Test_StoreWebauthnCredentialIDAsBytes/webauthn_credential.yml @@ -28,4 +28,3 @@ attestation_type: 'fido-u2f' sign_count: 1 clone_warning: false - diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 3524077ea4..020043cfc3 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -534,6 +534,12 @@ var migrations = []Migration{ NewMigration("Add ScheduleID for ActionRun", v1_21.AddScheduleIDForActionRun), // v276 -> v277 NewMigration("Add RemoteAddress to mirrors", v1_21.AddRemoteAddressToMirrors), + // v277 -> v278 + NewMigration("Add Index to issue_user.issue_id", v1_21.AddIndexToIssueUserIssueID), + // v278 -> v279 + NewMigration("Add Index to comment.dependent_issue_id", v1_21.AddIndexToCommentDependentIssueID), + // v279 -> v280 + NewMigration("Add Index to action.user_id", v1_21.AddIndexToActionUserID), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_16/v193_test.go b/models/migrations/v1_16/v193_test.go index 17669a012e..d99bbc2962 100644 --- a/models/migrations/v1_16/v193_test.go +++ b/models/migrations/v1_16/v193_test.go @@ -15,7 +15,6 @@ func Test_AddRepoIDForAttachment(t *testing.T) { type Attachment struct { ID int64 `xorm:"pk autoincr"` UUID string `xorm:"uuid UNIQUE"` - RepoID int64 `xorm:"INDEX"` // this should not be zero IssueID int64 `xorm:"INDEX"` // maybe zero when creating ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating UploaderID int64 `xorm:"INDEX DEFAULT 0"` @@ -44,12 +43,21 @@ func Test_AddRepoIDForAttachment(t *testing.T) { return } - var issueAttachments []*Attachment - err := x.Where("issue_id > 0").Find(&issueAttachments) + type NewAttachment struct { + ID int64 `xorm:"pk autoincr"` + UUID string `xorm:"uuid UNIQUE"` + RepoID int64 `xorm:"INDEX"` // this should not be zero + IssueID int64 `xorm:"INDEX"` // maybe zero when creating + ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating + UploaderID int64 `xorm:"INDEX DEFAULT 0"` + } + + var issueAttachments []*NewAttachment + err := x.Table("attachment").Where("issue_id > 0").Find(&issueAttachments) assert.NoError(t, err) for _, attach := range issueAttachments { - assert.Greater(t, attach.RepoID, 0) - assert.Greater(t, attach.IssueID, 0) + assert.Greater(t, attach.RepoID, int64(0)) + assert.Greater(t, attach.IssueID, int64(0)) var issue Issue has, err := x.ID(attach.IssueID).Get(&issue) assert.NoError(t, err) @@ -57,12 +65,12 @@ func Test_AddRepoIDForAttachment(t *testing.T) { assert.EqualValues(t, attach.RepoID, issue.RepoID) } - var releaseAttachments []*Attachment - err = x.Where("release_id > 0").Find(&releaseAttachments) + var releaseAttachments []*NewAttachment + err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments) assert.NoError(t, err) for _, attach := range releaseAttachments { - assert.Greater(t, attach.RepoID, 0) - assert.Greater(t, attach.IssueID, 0) + assert.Greater(t, attach.RepoID, int64(0)) + assert.Greater(t, attach.ReleaseID, int64(0)) var release Release has, err := x.ID(attach.ReleaseID).Get(&release) assert.NoError(t, err) diff --git a/models/migrations/v1_18/v227.go b/models/migrations/v1_18/v227.go index 78c80f74ec..5fe5dcd0c9 100644 --- a/models/migrations/v1_18/v227.go +++ b/models/migrations/v1_18/v227.go @@ -4,10 +4,6 @@ package v1_18 //nolint import ( - "fmt" - "strconv" - - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "xorm.io/xorm" @@ -22,42 +18,6 @@ type SystemSetting struct { Updated timeutil.TimeStamp `xorm:"updated"` } -func insertSettingsIfNotExist(x *xorm.Engine, sysSettings []*SystemSetting) error { - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - for _, setting := range sysSettings { - exist, err := sess.Table("system_setting").Where("setting_key=?", setting.SettingKey).Exist() - if err != nil { - return err - } - if !exist { - if _, err := sess.Insert(setting); err != nil { - return err - } - } - } - return sess.Commit() -} - func CreateSystemSettingsTable(x *xorm.Engine) error { - if err := x.Sync(new(SystemSetting)); err != nil { - return fmt.Errorf("sync2: %w", err) - } - - // migrate xx to database - sysSettings := []*SystemSetting{ - { - SettingKey: "picture.disable_gravatar", - SettingValue: strconv.FormatBool(setting.DisableGravatar), - }, - { - SettingKey: "picture.enable_federated_avatar", - SettingValue: strconv.FormatBool(setting.EnableFederatedAvatar), - }, - } - - return insertSettingsIfNotExist(x, sysSettings) + return x.Sync(new(SystemSetting)) } diff --git a/models/migrations/v1_21/v263.go b/models/migrations/v1_21/v263.go index 5dccd8bfa0..2c7cbadf0d 100644 --- a/models/migrations/v1_21/v263.go +++ b/models/migrations/v1_21/v263.go @@ -32,7 +32,12 @@ func AddGitSizeAndLFSSizeToRepositoryTable(x *xorm.Engine) error { return err } - _, err = sess.Exec(`UPDATE repository SET git_size = size - lfs_size`) + _, err = sess.Exec(`UPDATE repository SET size = 0 WHERE size IS NULL`) + if err != nil { + return err + } + + _, err = sess.Exec(`UPDATE repository SET git_size = size - lfs_size WHERE size > lfs_size`) if err != nil { return err } diff --git a/models/migrations/v1_21/v266.go b/models/migrations/v1_21/v266.go index df85286c89..79a5f5e14c 100644 --- a/models/migrations/v1_21/v266.go +++ b/models/migrations/v1_21/v266.go @@ -18,9 +18,6 @@ func ReduceCommitStatus(x *xorm.Engine) error { if _, err := sess.Exec(`UPDATE commit_status SET state='pending' WHERE state='running'`); err != nil { return err } - if _, err := sess.Exec(`UPDATE commit_status SET state='failure' WHERE state='warning'`); err != nil { - return err - } return sess.Commit() } diff --git a/models/migrations/v1_21/v277.go b/models/migrations/v1_21/v277.go new file mode 100644 index 0000000000..12529160b7 --- /dev/null +++ b/models/migrations/v1_21/v277.go @@ -0,0 +1,16 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "xorm.io/xorm" +) + +func AddIndexToIssueUserIssueID(x *xorm.Engine) error { + type IssueUser struct { + IssueID int64 `xorm:"INDEX"` + } + + return x.Sync(new(IssueUser)) +} diff --git a/models/migrations/v1_21/v278.go b/models/migrations/v1_21/v278.go new file mode 100644 index 0000000000..d6a462d1e7 --- /dev/null +++ b/models/migrations/v1_21/v278.go @@ -0,0 +1,16 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "xorm.io/xorm" +) + +func AddIndexToCommentDependentIssueID(x *xorm.Engine) error { + type Comment struct { + DependentIssueID int64 `xorm:"index"` + } + + return x.Sync(new(Comment)) +} diff --git a/models/migrations/v1_21/v279.go b/models/migrations/v1_21/v279.go new file mode 100644 index 0000000000..19647225c9 --- /dev/null +++ b/models/migrations/v1_21/v279.go @@ -0,0 +1,16 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "xorm.io/xorm" +) + +func AddIndexToActionUserID(x *xorm.Engine) error { + type Action struct { + UserID int64 `xorm:"INDEX"` + } + + return x.Sync(new(Action)) +} diff --git a/models/organization/team_list.go b/models/organization/team_list.go index efb3104ad5..5b45429acf 100644 --- a/models/organization/team_list.go +++ b/models/organization/team_list.go @@ -77,8 +77,8 @@ func (opts *SearchTeamOptions) toCond() builder.Cond { } // SearchTeam search for teams. Caller is responsible to check permissions. -func SearchTeam(opts *SearchTeamOptions) (TeamList, int64, error) { - sess := db.GetEngine(db.DefaultContext) +func SearchTeam(ctx context.Context, opts *SearchTeamOptions) (TeamList, int64, error) { + sess := db.GetEngine(ctx) opts.SetDefaultValues() cond := opts.toCond() diff --git a/models/organization/team_test.go b/models/organization/team_test.go index c63b83aab7..af69715ca9 100644 --- a/models/organization/team_test.go +++ b/models/organization/team_test.go @@ -142,7 +142,7 @@ func TestGetTeamMembers(t *testing.T) { func TestGetUserTeams(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) test := func(userID int64) { - teams, _, err := organization.SearchTeam(&organization.SearchTeamOptions{UserID: userID}) + teams, _, err := organization.SearchTeam(db.DefaultContext, &organization.SearchTeamOptions{UserID: userID}) assert.NoError(t, err) for _, team := range teams { unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{TeamID: team.ID, UID: userID}) diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go index f849ab5c04..b8ef698d38 100644 --- a/models/packages/descriptor.go +++ b/models/packages/descriptor.go @@ -70,16 +70,26 @@ type PackageFileDescriptor struct { Properties PackagePropertyList } -// PackageWebLink returns the package web link +// PackageWebLink returns the relative package web link func (pd *PackageDescriptor) PackageWebLink() string { return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HomeLink(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName)) } -// FullWebLink returns the package version web link -func (pd *PackageDescriptor) FullWebLink() string { +// VersionWebLink returns the relative package version web link +func (pd *PackageDescriptor) VersionWebLink() string { return fmt.Sprintf("%s/%s", pd.PackageWebLink(), url.PathEscape(pd.Version.LowerVersion)) } +// PackageHTMLURL returns the absolute package HTML URL +func (pd *PackageDescriptor) PackageHTMLURL() string { + return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HTMLURL(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName)) +} + +// VersionHTMLURL returns the absolute package version HTML URL +func (pd *PackageDescriptor) VersionHTMLURL() string { + return fmt.Sprintf("%s/%s", pd.PackageHTMLURL(), url.PathEscape(pd.Version.LowerVersion)) +} + // CalculateBlobSize returns the total blobs size in bytes func (pd *PackageDescriptor) CalculateBlobSize() int64 { size := int64(0) diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 2027b87ecb..bc5b26edd7 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -332,7 +332,6 @@ func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model. // CanBeAssigned return true if user can be assigned to issue or pull requests in repo // Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface. -// FIXME: user could send PullRequest also could be assigned??? func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) { if user.IsOrganization() { return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID) @@ -341,7 +340,8 @@ func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model. if err != nil { return false, err } - return perm.CanAccessAny(perm_model.AccessModeWrite, unit.TypeCode, unit.TypeIssues, unit.TypePullRequests), nil + return perm.CanAccessAny(perm_model.AccessModeWrite, unit.AllRepoUnitTypes...) || + perm.CanAccessAny(perm_model.AccessModeRead, unit.TypePullRequests), nil } // HasAccess returns true if user has access to repo diff --git a/models/project/board.go b/models/project/board.go index 2ab1d5c7b5..3e2d8e0472 100644 --- a/models/project/board.go +++ b/models/project/board.go @@ -69,8 +69,8 @@ func (Board) TableName() string { } // NumIssues return counter of all issues assigned to the board -func (b *Board) NumIssues() int { - c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). +func (b *Board) NumIssues(ctx context.Context) int { + c, err := db.GetEngine(ctx).Table("project_issue"). Where("project_id=?", b.ProjectID). And("project_board_id=?", b.ID). GroupBy("issue_id"). @@ -142,18 +142,18 @@ func createBoardsForProjectsType(ctx context.Context, project *Project) error { } // NewBoard adds a new project board to a given project -func NewBoard(board *Board) error { +func NewBoard(ctx context.Context, board *Board) error { if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) { return fmt.Errorf("bad color code: %s", board.Color) } - _, err := db.GetEngine(db.DefaultContext).Insert(board) + _, err := db.GetEngine(ctx).Insert(board) return err } // DeleteBoardByID removes all issues references to the project board. -func DeleteBoardByID(boardID int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func DeleteBoardByID(ctx context.Context, boardID int64) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -265,8 +265,8 @@ func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) { // SetDefaultBoard represents a board for issues not assigned to one // if boardID is 0 unset default -func SetDefaultBoard(projectID, boardID int64) error { - _, err := db.GetEngine(db.DefaultContext).Where(builder.Eq{ +func SetDefaultBoard(ctx context.Context, projectID, boardID int64) error { + _, err := db.GetEngine(ctx).Where(builder.Eq{ "project_id": projectID, "`default`": true, }).Cols("`default`").Update(&Board{Default: false}) @@ -275,7 +275,7 @@ func SetDefaultBoard(projectID, boardID int64) error { } if boardID > 0 { - _, err = db.GetEngine(db.DefaultContext).ID(boardID).Where(builder.Eq{"project_id": projectID}). + _, err = db.GetEngine(ctx).ID(boardID).Where(builder.Eq{"project_id": projectID}). Cols("`default`").Update(&Board{Default: true}) } @@ -283,9 +283,9 @@ func SetDefaultBoard(projectID, boardID int64) error { } // UpdateBoardSorting update project board sorting -func UpdateBoardSorting(bs BoardList) error { +func UpdateBoardSorting(ctx context.Context, bs BoardList) error { for i := range bs { - _, err := db.GetEngine(db.DefaultContext).ID(bs[i].ID).Cols( + _, err := db.GetEngine(ctx).ID(bs[i].ID).Cols( "sorting", ).Update(bs[i]) if err != nil { diff --git a/models/project/issue.go b/models/project/issue.go index 3269197d6c..ebc9719de5 100644 --- a/models/project/issue.go +++ b/models/project/issue.go @@ -34,8 +34,8 @@ func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error } // NumIssues return counter of all issues assigned to a project -func (p *Project) NumIssues() int { - c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). +func (p *Project) NumIssues(ctx context.Context) int { + c, err := db.GetEngine(ctx).Table("project_issue"). Where("project_id=?", p.ID). GroupBy("issue_id"). Cols("issue_id"). @@ -48,8 +48,8 @@ func (p *Project) NumIssues() int { } // NumClosedIssues return counter of closed issues assigned to a project -func (p *Project) NumClosedIssues() int { - c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). +func (p *Project) NumClosedIssues(ctx context.Context) int { + c, err := db.GetEngine(ctx).Table("project_issue"). Join("INNER", "issue", "project_issue.issue_id=issue.id"). Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true). Cols("issue_id"). @@ -62,8 +62,8 @@ func (p *Project) NumClosedIssues() int { } // NumOpenIssues return counter of open issues assigned to a project -func (p *Project) NumOpenIssues() int { - c, err := db.GetEngine(db.DefaultContext).Table("project_issue"). +func (p *Project) NumOpenIssues(ctx context.Context) int { + c, err := db.GetEngine(ctx).Table("project_issue"). Join("INNER", "issue", "project_issue.issue_id=issue.id"). Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false). Cols("issue_id"). @@ -76,8 +76,8 @@ func (p *Project) NumOpenIssues() int { } // MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column -func MoveIssuesOnProjectBoard(board *Board, sortedIssueIDs map[int64]int64) error { - return db.WithTx(db.DefaultContext, func(ctx context.Context) error { +func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64) error { + return db.WithTx(ctx, func(ctx context.Context) error { sess := db.GetEngine(ctx) issueIDs := make([]int64, 0, len(sortedIssueIDs)) diff --git a/models/project/project.go b/models/project/project.go index 8056aba252..199fd04651 100644 --- a/models/project/project.go +++ b/models/project/project.go @@ -124,9 +124,9 @@ func (p *Project) LoadRepo(ctx context.Context) (err error) { } // Link returns the project's relative URL. -func (p *Project) Link() string { +func (p *Project) Link(ctx context.Context) string { if p.OwnerID > 0 { - err := p.LoadOwner(db.DefaultContext) + err := p.LoadOwner(ctx) if err != nil { log.Error("LoadOwner: %v", err) return "" @@ -134,7 +134,7 @@ func (p *Project) Link() string { return fmt.Sprintf("%s/-/projects/%d", p.Owner.HomeLink(), p.ID) } if p.RepoID > 0 { - err := p.LoadRepo(db.DefaultContext) + err := p.LoadRepo(ctx) if err != nil { log.Error("LoadRepo: %v", err) return "" @@ -261,7 +261,7 @@ func FindProjects(ctx context.Context, opts SearchOptions) ([]*Project, int64, e } // NewProject creates a new Project -func NewProject(p *Project) error { +func NewProject(ctx context.Context, p *Project) error { if !IsBoardTypeValid(p.BoardType) { p.BoardType = BoardTypeNone } @@ -274,7 +274,7 @@ func NewProject(p *Project) error { return util.NewInvalidArgumentErrorf("project type is not valid") } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -311,6 +311,18 @@ func GetProjectByID(ctx context.Context, id int64) (*Project, error) { return p, nil } +// GetProjectForRepoByID returns the projects in a repository +func GetProjectForRepoByID(ctx context.Context, repoID, id int64) (*Project, error) { + p := new(Project) + has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", id, repoID).Get(p) + if err != nil { + return nil, err + } else if !has { + return nil, ErrProjectNotExist{ID: id} + } + return p, nil +} + // UpdateProject updates project properties func UpdateProject(ctx context.Context, p *Project) error { if !IsCardTypeValid(p.CardType) { @@ -348,8 +360,8 @@ func updateRepositoryProjectCount(ctx context.Context, repoID int64) error { } // ChangeProjectStatusByRepoIDAndID toggles a project between opened and closed -func ChangeProjectStatusByRepoIDAndID(repoID, projectID int64, isClosed bool) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeProjectStatusByRepoIDAndID(ctx context.Context, repoID, projectID int64, isClosed bool) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -372,8 +384,8 @@ func ChangeProjectStatusByRepoIDAndID(repoID, projectID int64, isClosed bool) er } // ChangeProjectStatus toggle a project between opened and closed -func ChangeProjectStatus(p *Project, isClosed bool) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func ChangeProjectStatus(ctx context.Context, p *Project, isClosed bool) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/project/project_test.go b/models/project/project_test.go index d1a4715653..6b5bd5b371 100644 --- a/models/project/project_test.go +++ b/models/project/project_test.go @@ -60,7 +60,7 @@ func TestProject(t *testing.T) { CreatorID: 2, } - assert.NoError(t, NewProject(project)) + assert.NoError(t, NewProject(db.DefaultContext, project)) _, err := GetProjectByID(db.DefaultContext, project.ID) assert.NoError(t, err) @@ -74,7 +74,7 @@ func TestProject(t *testing.T) { assert.Equal(t, project.Title, projectFromDB.Title) - assert.NoError(t, ChangeProjectStatus(project, true)) + assert.NoError(t, ChangeProjectStatus(db.DefaultContext, project, true)) // Retrieve from DB afresh to check if it is truly closed projectFromDB, err = GetProjectByID(db.DefaultContext, project.ID) diff --git a/models/repo.go b/models/repo.go index de0f7ee345..82433ca055 100644 --- a/models/repo.go +++ b/models/repo.go @@ -16,7 +16,6 @@ import ( issues_model "code.gitea.io/gitea/models/issues" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" - system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" @@ -24,10 +23,7 @@ import ( // Init initialize model func Init(ctx context.Context) error { - if err := unit.LoadUnitConfig(); err != nil { - return err - } - return system_model.Init(ctx) + return unit.LoadUnitConfig() } type repoChecker struct { diff --git a/models/repo/collaboration.go b/models/repo/collaboration.go index 7989e5bdf9..2018ae2a7d 100644 --- a/models/repo/collaboration.go +++ b/models/repo/collaboration.go @@ -62,8 +62,8 @@ func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOpti } // CountCollaborators returns total number of collaborators for a repository -func CountCollaborators(repoID int64) (int64, error) { - return db.GetEngine(db.DefaultContext).Where("repo_id = ? ", repoID).Count(&Collaboration{}) +func CountCollaborators(ctx context.Context, repoID int64) (int64, error) { + return db.GetEngine(ctx).Where("repo_id = ? ", repoID).Count(&Collaboration{}) } // GetCollaboration get collaboration for a repository id with a user id @@ -138,11 +138,11 @@ func ChangeCollaborationAccessMode(ctx context.Context, repo *Repository, uid in } // IsOwnerMemberCollaborator checks if a provided user is the owner, a collaborator or a member of a team in a repository -func IsOwnerMemberCollaborator(repo *Repository, userID int64) (bool, error) { +func IsOwnerMemberCollaborator(ctx context.Context, repo *Repository, userID int64) (bool, error) { if repo.OwnerID == userID { return true, nil } - teamMember, err := db.GetEngine(db.DefaultContext).Join("INNER", "team_repo", "team_repo.team_id = team_user.team_id"). + teamMember, err := db.GetEngine(ctx).Join("INNER", "team_repo", "team_repo.team_id = team_user.team_id"). Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id"). Where("team_repo.repo_id = ?", repo.ID). And("team_unit.`type` = ?", unit.TypeCode). @@ -154,5 +154,5 @@ func IsOwnerMemberCollaborator(repo *Repository, userID int64) (bool, error) { return true, nil } - return db.GetEngine(db.DefaultContext).Get(&Collaboration{RepoID: repo.ID, UserID: userID}) + return db.GetEngine(ctx).Get(&Collaboration{RepoID: repo.ID, UserID: userID}) } diff --git a/models/repo/collaboration_test.go b/models/repo/collaboration_test.go index a29dfe99a5..38114c307f 100644 --- a/models/repo/collaboration_test.go +++ b/models/repo/collaboration_test.go @@ -89,17 +89,17 @@ func TestRepository_CountCollaborators(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - count, err := repo_model.CountCollaborators(repo1.ID) + count, err := repo_model.CountCollaborators(db.DefaultContext, repo1.ID) assert.NoError(t, err) assert.EqualValues(t, 2, count) repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22}) - count, err = repo_model.CountCollaborators(repo2.ID) + count, err = repo_model.CountCollaborators(db.DefaultContext, repo2.ID) assert.NoError(t, err) assert.EqualValues(t, 2, count) // Non-existent repository. - count, err = repo_model.CountCollaborators(unittest.NonexistentID) + count, err = repo_model.CountCollaborators(db.DefaultContext, unittest.NonexistentID) assert.NoError(t, err) assert.EqualValues(t, 0, count) } @@ -110,31 +110,31 @@ func TestRepository_IsOwnerMemberCollaborator(t *testing.T) { repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // Organisation owner. - actual, err := repo_model.IsOwnerMemberCollaborator(repo1, 2) + actual, err := repo_model.IsOwnerMemberCollaborator(db.DefaultContext, repo1, 2) assert.NoError(t, err) assert.True(t, actual) // Team member. - actual, err = repo_model.IsOwnerMemberCollaborator(repo1, 4) + actual, err = repo_model.IsOwnerMemberCollaborator(db.DefaultContext, repo1, 4) assert.NoError(t, err) assert.True(t, actual) // Normal user. - actual, err = repo_model.IsOwnerMemberCollaborator(repo1, 1) + actual, err = repo_model.IsOwnerMemberCollaborator(db.DefaultContext, repo1, 1) assert.NoError(t, err) assert.False(t, actual) repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) // Collaborator. - actual, err = repo_model.IsOwnerMemberCollaborator(repo2, 4) + actual, err = repo_model.IsOwnerMemberCollaborator(db.DefaultContext, repo2, 4) assert.NoError(t, err) assert.True(t, actual) repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15}) // Repository owner. - actual, err = repo_model.IsOwnerMemberCollaborator(repo3, 2) + actual, err = repo_model.IsOwnerMemberCollaborator(db.DefaultContext, repo3, 2) assert.NoError(t, err) assert.True(t, actual) } diff --git a/models/repo/mirror.go b/models/repo/mirror.go index fffc7577c7..be7b785612 100644 --- a/models/repo/mirror.go +++ b/models/repo/mirror.go @@ -47,12 +47,12 @@ func (m *Mirror) BeforeInsert() { } // GetRepository returns the repository. -func (m *Mirror) GetRepository() *Repository { +func (m *Mirror) GetRepository(ctx context.Context) *Repository { if m.Repo != nil { return m.Repo } var err error - m.Repo, err = GetRepositoryByID(db.DefaultContext, m.RepoID) + m.Repo, err = GetRepositoryByID(ctx, m.RepoID) if err != nil { log.Error("getRepositoryByID[%d]: %v", m.ID, err) } @@ -99,14 +99,14 @@ func TouchMirror(ctx context.Context, m *Mirror) error { } // DeleteMirrorByRepoID deletes a mirror by repoID -func DeleteMirrorByRepoID(repoID int64) error { - _, err := db.GetEngine(db.DefaultContext).Delete(&Mirror{RepoID: repoID}) +func DeleteMirrorByRepoID(ctx context.Context, repoID int64) error { + _, err := db.GetEngine(ctx).Delete(&Mirror{RepoID: repoID}) return err } // MirrorsIterate iterates all mirror repositories. -func MirrorsIterate(limit int, f func(idx int, bean any) error) error { - sess := db.GetEngine(db.DefaultContext). +func MirrorsIterate(ctx context.Context, limit int, f func(idx int, bean any) error) error { + sess := db.GetEngine(ctx). Where("next_update_unix<=?", time.Now().Unix()). And("next_update_unix!=0"). OrderBy("updated_unix ASC") diff --git a/models/repo/release.go b/models/repo/release.go index 0e92474365..067de8a313 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -181,9 +181,9 @@ func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs } // GetRelease returns release by given ID. -func GetRelease(repoID int64, tagName string) (*Release, error) { +func GetRelease(ctx context.Context, repoID int64, tagName string) (*Release, error) { rel := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)} - has, err := db.GetEngine(db.DefaultContext).Get(rel) + has, err := db.GetEngine(ctx).Get(rel) if err != nil { return nil, err } else if !has { @@ -207,6 +207,21 @@ func GetReleaseByID(ctx context.Context, id int64) (*Release, error) { return rel, nil } +// GetReleaseForRepoByID returns release with given ID. +func GetReleaseForRepoByID(ctx context.Context, repoID, id int64) (*Release, error) { + rel := new(Release) + has, err := db.GetEngine(ctx). + Where("id=? AND repo_id=?", id, repoID). + Get(rel) + if err != nil { + return nil, err + } else if !has { + return nil, ErrReleaseNotExist{id, ""} + } + + return rel, nil +} + // FindReleasesOptions describes the conditions to Find releases type FindReleasesOptions struct { db.ListOptions @@ -215,12 +230,18 @@ type FindReleasesOptions struct { IsPreRelease util.OptionalBool IsDraft util.OptionalBool TagNames []string + RepoID int64 HasSha1 util.OptionalBool // useful to find draft releases which are created with existing tags } func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond { + opts.RepoID = repoID + return opts.ToConds() +} + +func (opts *FindReleasesOptions) ToConds() builder.Cond { cond := builder.NewCond() - cond = cond.And(builder.Eq{"repo_id": repoID}) + cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) if !opts.IncludeDrafts { cond = cond.And(builder.Eq{"is_draft": false}) @@ -284,12 +305,12 @@ func GetTagNamesByRepoID(ctx context.Context, repoID int64) ([]string, error) { } // CountReleasesByRepoID returns a number of releases matching FindReleaseOptions and RepoID. -func CountReleasesByRepoID(repoID int64, opts FindReleasesOptions) (int64, error) { - return db.GetEngine(db.DefaultContext).Where(opts.toConds(repoID)).Count(new(Release)) +func CountReleasesByRepoID(ctx context.Context, repoID int64, opts FindReleasesOptions) (int64, error) { + return db.GetEngine(ctx).Where(opts.toConds(repoID)).Count(new(Release)) } // GetLatestReleaseByRepoID returns the latest release for a repository -func GetLatestReleaseByRepoID(repoID int64) (*Release, error) { +func GetLatestReleaseByRepoID(ctx context.Context, repoID int64) (*Release, error) { cond := builder.NewCond(). And(builder.Eq{"repo_id": repoID}). And(builder.Eq{"is_draft": false}). @@ -297,7 +318,7 @@ func GetLatestReleaseByRepoID(repoID int64) (*Release, error) { And(builder.Eq{"is_tag": false}) rel := new(Release) - has, err := db.GetEngine(db.DefaultContext). + has, err := db.GetEngine(ctx). Desc("created_unix", "id"). Where(cond). Get(rel) @@ -442,8 +463,8 @@ func DeleteReleaseByID(ctx context.Context, id int64) error { } // UpdateReleasesMigrationsByType updates all migrated repositories' releases from gitServiceType to replace originalAuthorID to posterID -func UpdateReleasesMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error { - _, err := db.GetEngine(db.DefaultContext).Table("release"). +func UpdateReleasesMigrationsByType(ctx context.Context, gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error { + _, err := db.GetEngine(ctx).Table("release"). Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType). And("original_author_id = ?", originalAuthorID). Update(map[string]any{ @@ -485,8 +506,8 @@ func PushUpdateDeleteTagsContext(ctx context.Context, repo *Repository, tags []s } // PushUpdateDeleteTag must be called for any push actions to delete tag -func PushUpdateDeleteTag(repo *Repository, tagName string) error { - rel, err := GetRelease(repo.ID, tagName) +func PushUpdateDeleteTag(ctx context.Context, repo *Repository, tagName string) error { + rel, err := GetRelease(ctx, repo.ID, tagName) if err != nil { if IsErrReleaseNotExist(err) { return nil @@ -494,14 +515,14 @@ func PushUpdateDeleteTag(repo *Repository, tagName string) error { return fmt.Errorf("GetRelease: %w", err) } if rel.IsTag { - if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).Delete(new(Release)); err != nil { + if _, err = db.GetEngine(ctx).ID(rel.ID).Delete(new(Release)); err != nil { return fmt.Errorf("Delete: %w", err) } } else { rel.IsDraft = true rel.NumCommits = 0 rel.Sha1 = "" - if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil { + if _, err = db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel); err != nil { return fmt.Errorf("Update: %w", err) } } @@ -510,15 +531,15 @@ func PushUpdateDeleteTag(repo *Repository, tagName string) error { } // SaveOrUpdateTag must be called for any push actions to add tag -func SaveOrUpdateTag(repo *Repository, newRel *Release) error { - rel, err := GetRelease(repo.ID, newRel.TagName) +func SaveOrUpdateTag(ctx context.Context, repo *Repository, newRel *Release) error { + rel, err := GetRelease(ctx, repo.ID, newRel.TagName) if err != nil && !IsErrReleaseNotExist(err) { return fmt.Errorf("GetRelease: %w", err) } if rel == nil { rel = newRel - if _, err = db.GetEngine(db.DefaultContext).Insert(rel); err != nil { + if _, err = db.GetEngine(ctx).Insert(rel); err != nil { return fmt.Errorf("InsertOne: %w", err) } } else { @@ -529,7 +550,7 @@ func SaveOrUpdateTag(repo *Repository, newRel *Release) error { if rel.IsTag && newRel.PublisherID > 0 { rel.PublisherID = newRel.PublisherID } - if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil { + if _, err = db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel); err != nil { return fmt.Errorf("Update: %w", err) } } @@ -554,8 +575,8 @@ func (r *Release) GetExternalName() string { return r.OriginalAuthor } func (r *Release) GetExternalID() int64 { return r.OriginalAuthorID } // InsertReleases migrates release -func InsertReleases(rels ...*Release) error { - ctx, committer, err := db.TxContext(db.DefaultContext) +func InsertReleases(ctx context.Context, rels ...*Release) error { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/models/repo/release_test.go b/models/repo/release_test.go index 2a45ab32f3..3643bff7f1 100644 --- a/models/repo/release_test.go +++ b/models/repo/release_test.go @@ -6,6 +6,7 @@ package repo import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" "github.com/stretchr/testify/assert" @@ -21,6 +22,6 @@ func TestMigrate_InsertReleases(t *testing.T) { Attachments: []*Attachment{a}, } - err := InsertReleases(r) + err := InsertReleases(db.DefaultContext, r) assert.NoError(t, err) } diff --git a/models/repo/repo.go b/models/repo/repo.go index 5ebc7bfc24..596a4268c8 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -47,6 +47,14 @@ func (err ErrUserDoesNotHaveAccessToRepo) Unwrap() error { return util.ErrPermissionDenied } +type ErrRepoIsArchived struct { + Repo *Repository +} + +func (err ErrRepoIsArchived) Error() string { + return fmt.Sprintf("%s is archived", err.Repo.LogString()) +} + var ( reservedRepoNames = []string{".", "..", "-"} reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"} @@ -570,8 +578,7 @@ func (repo *Repository) CanEnableEditor() bool { // DescriptionHTML does special handles to description and return HTML string. func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML { desc, err := markup.RenderDescriptionHTML(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: repo.HTMLURL(), + Ctx: ctx, // Don't use Metas to speedup requests }, repo.Description) if err != nil { @@ -594,25 +601,23 @@ func ComposeHTTPSCloneURL(owner, repo string) string { func ComposeSSHCloneURL(ownerName, repoName string) string { sshUser := setting.SSH.User - - // if we have a ipv6 literal we need to put brackets around it - // for the git cloning to work. sshDomain := setting.SSH.Domain - ip := net.ParseIP(setting.SSH.Domain) - if ip != nil && ip.To4() == nil { - sshDomain = "[" + setting.SSH.Domain + "]" + + // non-standard port, it must use full URI + if setting.SSH.Port != 22 { + sshHost := net.JoinHostPort(sshDomain, strconv.Itoa(setting.SSH.Port)) + return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName)) } - if setting.SSH.Port != 22 { - return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, - net.JoinHostPort(setting.SSH.Domain, strconv.Itoa(setting.SSH.Port)), - url.PathEscape(ownerName), - url.PathEscape(repoName)) + // for standard port, it can use a shorter URI (without the port) + sshHost := sshDomain + if ip := net.ParseIP(sshHost); ip != nil && ip.To4() == nil { + sshHost = "[" + sshHost + "]" // for IPv6 address, wrap it with brackets } if setting.Repository.UseCompatSSHURI { - return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshDomain, url.PathEscape(ownerName), url.PathEscape(repoName)) + return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName)) } - return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshDomain, url.PathEscape(ownerName), url.PathEscape(repoName)) + return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName)) } func (repo *Repository) cloneLink(isWiki bool) *CloneLink { @@ -654,6 +659,14 @@ func (repo *Repository) GetTrustModel() TrustModelType { return trustModel } +// MustNotBeArchived returns ErrRepoIsArchived if the repo is archived +func (repo *Repository) MustNotBeArchived() error { + if repo.IsArchived { + return ErrRepoIsArchived{Repo: repo} + } + return nil +} + // __________ .__ __ // \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__. // | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | | diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index a0485ed8d4..b1efec47b3 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -652,12 +652,12 @@ func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) bu userOrgTeamUnitRepoCond("`repository`.id", user.ID, unitType), ) } - cond = cond.Or( - // 4. Repositories that we directly own - builder.Eq{"`repository`.owner_id": user.ID}, + // 4. Repositories that we directly own + cond = cond.Or(builder.Eq{"`repository`.owner_id": user.ID}) + if !user.IsRestricted { // 5. Be able to see all public repos in private organizations that we are an org_user of - userOrgPublicRepoCond(user.ID), - ) + cond = cond.Or(userOrgPublicRepoCond(user.ID)) + } } return cond @@ -716,7 +716,7 @@ func FindUserCodeAccessibleOwnerRepoIDs(ctx context.Context, ownerID int64, user } // GetUserRepositories returns a list of repositories of given user. -func GetUserRepositories(opts *SearchRepoOptions) (RepositoryList, int64, error) { +func GetUserRepositories(ctx context.Context, opts *SearchRepoOptions) (RepositoryList, int64, error) { if len(opts.OrderBy) == 0 { opts.OrderBy = "updated_unix DESC" } @@ -734,7 +734,7 @@ func GetUserRepositories(opts *SearchRepoOptions) (RepositoryList, int64, error) cond = cond.And(builder.In("lower_name", opts.LowerNames)) } - sess := db.GetEngine(db.DefaultContext) + sess := db.GetEngine(ctx) count, err := sess.Where(cond).Count(new(Repository)) if err != nil { diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go index fb021561c3..0def51284d 100644 --- a/models/repo/repo_test.go +++ b/models/repo/repo_test.go @@ -12,6 +12,8 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" @@ -186,3 +188,32 @@ func TestGetRepositoryByURL(t *testing.T) { test(t, "try.gitea.io:user2/repo2.git") }) } + +func TestComposeSSHCloneURL(t *testing.T) { + defer test.MockVariableValue(&setting.SSH, setting.SSH)() + defer test.MockVariableValue(&setting.Repository, setting.Repository)() + + setting.SSH.User = "git" + + // test SSH_DOMAIN + setting.SSH.Domain = "domain" + setting.SSH.Port = 22 + setting.Repository.UseCompatSSHURI = false + assert.Equal(t, "git@domain:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo")) + setting.Repository.UseCompatSSHURI = true + assert.Equal(t, "ssh://git@domain/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo")) + // test SSH_DOMAIN while use non-standard SSH port + setting.SSH.Port = 123 + setting.Repository.UseCompatSSHURI = false + assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo")) + setting.Repository.UseCompatSSHURI = true + assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo")) + + // test IPv6 SSH_DOMAIN + setting.Repository.UseCompatSSHURI = false + setting.SSH.Domain = "::1" + setting.SSH.Port = 22 + assert.Equal(t, "git@[::1]:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo")) + setting.SSH.Port = 123 + assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo")) +} diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index b8804c6df1..1960e67c3e 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -283,29 +283,3 @@ func UpdateRepoUnit(unit *RepoUnit) error { _, err := db.GetEngine(db.DefaultContext).ID(unit.ID).Update(unit) return err } - -// UpdateRepositoryUnits updates a repository's units -func UpdateRepositoryUnits(repo *Repository, units []RepoUnit, deleteUnitTypes []unit.Type) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) - if err != nil { - return err - } - defer committer.Close() - - // Delete existing settings of units before adding again - for _, u := range units { - deleteUnitTypes = append(deleteUnitTypes, u.Type) - } - - if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(RepoUnit)); err != nil { - return err - } - - if len(units) > 0 { - if err = db.Insert(ctx, units); err != nil { - return err - } - } - - return committer.Commit() -} diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go index dd2ef62201..30c9db7474 100644 --- a/models/repo/user_repo.go +++ b/models/repo/user_repo.go @@ -8,6 +8,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" api "code.gitea.io/gitea/modules/structs" @@ -78,7 +79,8 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us if err = e.Table("team_user"). Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id"). Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id"). - Where("`team_repo`.repo_id = ? AND `team_unit`.access_mode >= ?", repo.ID, perm.AccessModeWrite). + Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))", + repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests). Distinct("`team_user`.uid"). Select("`team_user`.uid"). Find(&additionalUserIDs); err != nil { diff --git a/models/secret/secret.go b/models/secret/secret.go index 8df46b6c38..7d7aef8640 100644 --- a/models/secret/secret.go +++ b/models/secret/secret.go @@ -9,7 +9,10 @@ import ( "fmt" "strings" + actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" + actions_module "code.gitea.io/gitea/modules/actions" + "code.gitea.io/gitea/modules/log" secret_module "code.gitea.io/gitea/modules/secret" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" @@ -128,3 +131,39 @@ func UpdateSecret(ctx context.Context, secretID int64, data string) error { } return err } + +func GetSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) (map[string]string, error) { + secrets := map[string]string{} + + secrets["GITHUB_TOKEN"] = task.Token + secrets["GITEA_TOKEN"] = task.Token + + if task.Job.Run.IsForkPullRequest && task.Job.Run.TriggerEvent != actions_module.GithubEventPullRequestTarget { + // ignore secrets for fork pull request, except GITHUB_TOKEN and GITEA_TOKEN which are automatically generated. + // for the tasks triggered by pull_request_target event, they could access the secrets because they will run in the context of the base branch + // see the documentation: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target + return secrets, nil + } + + ownerSecrets, err := FindSecrets(ctx, FindSecretsOptions{OwnerID: task.Job.Run.Repo.OwnerID}) + if err != nil { + log.Error("find secrets of owner %v: %v", task.Job.Run.Repo.OwnerID, err) + return nil, err + } + repoSecrets, err := FindSecrets(ctx, FindSecretsOptions{RepoID: task.Job.Run.RepoID}) + if err != nil { + log.Error("find secrets of repo %v: %v", task.Job.Run.RepoID, err) + return nil, err + } + + for _, secret := range append(ownerSecrets, repoSecrets...) { + v, err := secret_module.DecryptSecret(setting.SecretKey, secret.Data) + if err != nil { + log.Error("decrypt secret %v %q: %v", secret.ID, secret.Name, err) + return nil, err + } + secrets[secret.Name] = v + } + + return secrets, nil +} diff --git a/models/system/notice.go b/models/system/notice.go index 784ad74375..b0c0bfb95d 100644 --- a/models/system/notice.go +++ b/models/system/notice.go @@ -94,28 +94,28 @@ func CountNotices() int64 { } // Notices returns notices in given page. -func Notices(page, pageSize int) ([]*Notice, error) { +func Notices(ctx context.Context, page, pageSize int) ([]*Notice, error) { notices := make([]*Notice, 0, pageSize) - return notices, db.GetEngine(db.DefaultContext). + return notices, db.GetEngine(ctx). Limit(pageSize, (page-1)*pageSize). Desc("created_unix"). Find(¬ices) } // DeleteNotice deletes a system notice by given ID. -func DeleteNotice(id int64) error { - _, err := db.GetEngine(db.DefaultContext).ID(id).Delete(new(Notice)) +func DeleteNotice(ctx context.Context, id int64) error { + _, err := db.GetEngine(ctx).ID(id).Delete(new(Notice)) return err } // DeleteNotices deletes all notices with ID from start to end (inclusive). -func DeleteNotices(start, end int64) error { +func DeleteNotices(ctx context.Context, start, end int64) error { if start == 0 && end == 0 { - _, err := db.GetEngine(db.DefaultContext).Exec("DELETE FROM notice") + _, err := db.GetEngine(ctx).Exec("DELETE FROM notice") return err } - sess := db.GetEngine(db.DefaultContext).Where("id >= ?", start) + sess := db.GetEngine(ctx).Where("id >= ?", start) if end > 0 { sess.And("id <= ?", end) } @@ -124,22 +124,22 @@ func DeleteNotices(start, end int64) error { } // DeleteNoticesByIDs deletes notices by given IDs. -func DeleteNoticesByIDs(ids []int64) error { +func DeleteNoticesByIDs(ctx context.Context, ids []int64) error { if len(ids) == 0 { return nil } - _, err := db.GetEngine(db.DefaultContext). + _, err := db.GetEngine(ctx). In("id", ids). Delete(new(Notice)) return err } // DeleteOldSystemNotices deletes all old system notices from database. -func DeleteOldSystemNotices(olderThan time.Duration) (err error) { +func DeleteOldSystemNotices(ctx context.Context, olderThan time.Duration) (err error) { if olderThan <= 0 { return nil } - _, err = db.GetEngine(db.DefaultContext).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Notice{}) + _, err = db.GetEngine(ctx).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Notice{}) return err } diff --git a/models/system/notice_test.go b/models/system/notice_test.go index 01eb9b57a5..871ffa54ce 100644 --- a/models/system/notice_test.go +++ b/models/system/notice_test.go @@ -55,14 +55,14 @@ func TestCountNotices(t *testing.T) { func TestNotices(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - notices, err := system.Notices(1, 2) + notices, err := system.Notices(db.DefaultContext, 1, 2) assert.NoError(t, err) if assert.Len(t, notices, 2) { assert.Equal(t, int64(3), notices[0].ID) assert.Equal(t, int64(2), notices[1].ID) } - notices, err = system.Notices(2, 2) + notices, err = system.Notices(db.DefaultContext, 2, 2) assert.NoError(t, err) if assert.Len(t, notices, 1) { assert.Equal(t, int64(1), notices[0].ID) @@ -73,7 +73,7 @@ func TestDeleteNotice(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3}) - assert.NoError(t, system.DeleteNotice(3)) + assert.NoError(t, system.DeleteNotice(db.DefaultContext, 3)) unittest.AssertNotExistsBean(t, &system.Notice{ID: 3}) } @@ -84,7 +84,7 @@ func TestDeleteNotices(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3}) - assert.NoError(t, system.DeleteNotices(1, 2)) + assert.NoError(t, system.DeleteNotices(db.DefaultContext, 1, 2)) unittest.AssertNotExistsBean(t, &system.Notice{ID: 1}) unittest.AssertNotExistsBean(t, &system.Notice{ID: 2}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3}) @@ -97,7 +97,7 @@ func TestDeleteNotices2(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3}) - assert.NoError(t, system.DeleteNotices(3, 2)) + assert.NoError(t, system.DeleteNotices(db.DefaultContext, 3, 2)) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3}) @@ -109,7 +109,7 @@ func TestDeleteNoticesByIDs(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3}) - assert.NoError(t, system.DeleteNoticesByIDs([]int64{1, 3})) + assert.NoError(t, system.DeleteNoticesByIDs(db.DefaultContext, []int64{1, 3})) unittest.AssertNotExistsBean(t, &system.Notice{ID: 1}) unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2}) unittest.AssertNotExistsBean(t, &system.Notice{ID: 3}) diff --git a/models/system/setting.go b/models/system/setting.go index 9fffa74e94..cd1d373b98 100644 --- a/models/system/setting.go +++ b/models/system/setting.go @@ -5,26 +5,21 @@ package system import ( "context" - "fmt" - "net/url" - "strconv" - "strings" + "math" + "sync" + "time" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/cache" - setting_module "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting/config" "code.gitea.io/gitea/modules/timeutil" - - "strk.kbt.io/projects/go/libravatar" - "xorm.io/builder" ) -// Setting is a key value store of user settings type Setting struct { ID int64 `xorm:"pk autoincr"` - SettingKey string `xorm:"varchar(255) unique"` // ensure key is always lowercase + SettingKey string `xorm:"varchar(255) unique"` // key should be lowercase SettingValue string `xorm:"text"` - Version int `xorm:"version"` // prevent to override + Version int `xorm:"version"` Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` } @@ -34,281 +29,121 @@ func (s *Setting) TableName() string { return "system_setting" } -func (s *Setting) GetValueBool() bool { - if s == nil { - return false - } - - b, _ := strconv.ParseBool(s.SettingValue) - return b -} - func init() { db.RegisterModel(new(Setting)) } -// ErrSettingIsNotExist represents an error that a setting is not exist with special key -type ErrSettingIsNotExist struct { - Key string -} +const keyRevision = "revision" -// Error implements error -func (err ErrSettingIsNotExist) Error() string { - return fmt.Sprintf("System setting[%s] is not exist", err.Key) -} - -// IsErrSettingIsNotExist return true if err is ErrSettingIsNotExist -func IsErrSettingIsNotExist(err error) bool { - _, ok := err.(ErrSettingIsNotExist) - return ok -} - -// ErrDataExpired represents an error that update a record which has been updated by another thread -type ErrDataExpired struct { - Key string -} - -// Error implements error -func (err ErrDataExpired) Error() string { - return fmt.Sprintf("System setting[%s] has been updated by another thread", err.Key) -} - -// IsErrDataExpired return true if err is ErrDataExpired -func IsErrDataExpired(err error) bool { - _, ok := err.(ErrDataExpired) - return ok -} - -// GetSetting returns specific setting without using the cache -func GetSetting(ctx context.Context, key string) (*Setting, error) { - v, err := GetSettings(ctx, []string{key}) - if err != nil { - return nil, err - } - if len(v) == 0 { - return nil, ErrSettingIsNotExist{key} - } - return v[strings.ToLower(key)], nil -} - -const contextCacheKey = "system_setting" - -// GetSettingWithCache returns the setting value via the key -func GetSettingWithCache(ctx context.Context, key, defaultVal string) (string, error) { - return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { - return cache.GetString(genSettingCacheKey(key), func() (string, error) { - res, err := GetSetting(ctx, key) - if err != nil { - if IsErrSettingIsNotExist(err) { - return defaultVal, nil - } - return "", err - } - return res.SettingValue, nil - }) - }) -} - -// GetSettingBool return bool value of setting, -// none existing keys and errors are ignored and result in false -func GetSettingBool(ctx context.Context, key string, defaultVal bool) (bool, error) { - s, err := GetSetting(ctx, key) - switch { - case err == nil: - v, _ := strconv.ParseBool(s.SettingValue) - return v, nil - case IsErrSettingIsNotExist(err): - return defaultVal, nil - default: - return false, err - } -} - -func GetSettingWithCacheBool(ctx context.Context, key string, defaultVal bool) bool { - s, _ := GetSettingWithCache(ctx, key, strconv.FormatBool(defaultVal)) - v, _ := strconv.ParseBool(s) - return v -} - -// GetSettings returns specific settings -func GetSettings(ctx context.Context, keys []string) (map[string]*Setting, error) { - for i := 0; i < len(keys); i++ { - keys[i] = strings.ToLower(keys[i]) - } - settings := make([]*Setting, 0, len(keys)) - if err := db.GetEngine(ctx). - Where(builder.In("setting_key", keys)). - Find(&settings); err != nil { - return nil, err - } - settingsMap := make(map[string]*Setting) - for _, s := range settings { - settingsMap[s.SettingKey] = s - } - return settingsMap, nil -} - -type AllSettings map[string]*Setting - -func (settings AllSettings) Get(key string) Setting { - if v, ok := settings[strings.ToLower(key)]; ok { - return *v - } - return Setting{} -} - -func (settings AllSettings) GetBool(key string) bool { - b, _ := strconv.ParseBool(settings.Get(key).SettingValue) - return b -} - -func (settings AllSettings) GetVersion(key string) int { - return settings.Get(key).Version -} - -// GetAllSettings returns all settings from user -func GetAllSettings(ctx context.Context) (AllSettings, error) { - settings := make([]*Setting, 0, 5) - if err := db.GetEngine(ctx). - Find(&settings); err != nil { - return nil, err - } - settingsMap := make(map[string]*Setting) - for _, s := range settings { - settingsMap[s.SettingKey] = s - } - return settingsMap, nil -} - -// DeleteSetting deletes a specific setting for a user -func DeleteSetting(ctx context.Context, setting *Setting) error { - cache.RemoveContextData(ctx, contextCacheKey, setting.SettingKey) - cache.Remove(genSettingCacheKey(setting.SettingKey)) - _, err := db.GetEngine(ctx).Delete(setting) - return err -} - -func SetSettingNoVersion(ctx context.Context, key, value string) error { - s, err := GetSetting(ctx, key) - if IsErrSettingIsNotExist(err) { - return SetSetting(ctx, &Setting{ - SettingKey: key, - SettingValue: value, - }) - } - if err != nil { - return err - } - s.SettingValue = value - return SetSetting(ctx, s) -} - -// SetSetting updates a users' setting for a specific key -func SetSetting(ctx context.Context, setting *Setting) error { - if err := upsertSettingValue(ctx, strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil { - return err - } - - setting.Version++ - - cc := cache.GetCache() - if cc != nil { - if err := cc.Put(genSettingCacheKey(setting.SettingKey), setting.SettingValue, setting_module.CacheService.TTLSeconds()); err != nil { - return err +func GetRevision(ctx context.Context) int { + revision := &Setting{SettingKey: keyRevision} + if has, err := db.GetByBean(ctx, revision); err != nil { + return 0 + } else if !has { + err = db.Insert(ctx, &Setting{SettingKey: keyRevision, Version: 1}) + if err != nil { + return 0 } + return 1 + } else if revision.Version <= 0 || revision.Version >= math.MaxInt-1 { + _, err = db.Exec(ctx, "UPDATE system_setting SET version=1 WHERE setting_key=?", keyRevision) + if err != nil { + return 0 + } + return 1 } - cache.SetContextData(ctx, contextCacheKey, setting.SettingKey, setting.SettingValue) - return nil + return revision.Version } -func upsertSettingValue(parentCtx context.Context, key, value string, version int) error { - return db.WithTx(parentCtx, func(ctx context.Context) error { +func GetAllSettings(ctx context.Context) (revision int, res map[string]string, err error) { + _ = GetRevision(ctx) // prepare the "revision" key ahead + var settings []*Setting + if err := db.GetEngine(ctx). + Find(&settings); err != nil { + return 0, nil, err + } + res = make(map[string]string) + for _, s := range settings { + if s.SettingKey == keyRevision { + revision = s.Version + } + res[s.SettingKey] = s.SettingValue + } + return revision, res, nil +} + +func SetSettings(ctx context.Context, settings map[string]string) error { + _ = GetRevision(ctx) // prepare the "revision" key ahead + return db.WithTx(ctx, func(ctx context.Context) error { e := db.GetEngine(ctx) - - // here we use a general method to do a safe upsert for different databases (and most transaction levels) - // 1. try to UPDATE the record and acquire the transaction write lock - // if UPDATE returns non-zero rows are changed, OK, the setting is saved correctly - // if UPDATE returns "0 rows changed", two possibilities: (a) record doesn't exist (b) value is not changed - // 2. do a SELECT to check if the row exists or not (we already have the transaction lock) - // 3. if the row doesn't exist, do an INSERT (we are still protected by the transaction lock, so it's safe) - // - // to optimize the SELECT in step 2, we can use an extra column like `revision=revision+1` - // to make sure the UPDATE always returns a non-zero value for existing (unchanged) records. - - res, err := e.Exec("UPDATE system_setting SET setting_value=?, version = version+1 WHERE setting_key=? AND version=?", value, key, version) + _, err := db.Exec(ctx, "UPDATE system_setting SET version=version+1 WHERE setting_key=?", keyRevision) if err != nil { return err } - rows, _ := res.RowsAffected() - if rows > 0 { - // the existing row is updated, so we can return - return nil + for k, v := range settings { + res, err := e.Exec("UPDATE system_setting SET version=version+1, setting_value=? WHERE setting_key=?", v, k) + if err != nil { + return err + } + rows, _ := res.RowsAffected() + if rows == 0 { // if no existing row, insert a new row + if _, err = e.Insert(&Setting{SettingKey: k, SettingValue: v}); err != nil { + return err + } + } } - - // in case the value isn't changed, update would return 0 rows changed, so we need this check - has, err := e.Exist(&Setting{SettingKey: key}) - if err != nil { - return err - } - if has { - return ErrDataExpired{Key: key} - } - - // if no existing row, insert a new row - _, err = e.Insert(&Setting{SettingKey: key, SettingValue: value}) - return err + return nil }) } -var ( - GravatarSourceURL *url.URL - LibravatarService *libravatar.Libravatar -) +type dbConfigCachedGetter struct { + mu sync.RWMutex -func Init(ctx context.Context) error { - disableGravatar, err := GetSettingBool(ctx, KeyPictureDisableGravatar, setting_module.GetDefaultDisableGravatar()) - if err != nil { - return err - } - - enableFederatedAvatar, err := GetSettingBool(ctx, KeyPictureEnableFederatedAvatar, setting_module.GetDefaultEnableFederatedAvatar(disableGravatar)) - if err != nil { - return err - } - - if setting_module.OfflineMode { - if !disableGravatar { - if err := SetSettingNoVersion(ctx, KeyPictureDisableGravatar, "true"); err != nil { - return fmt.Errorf("failed to set setting %q: %w", KeyPictureDisableGravatar, err) - } - } - disableGravatar = true - - if enableFederatedAvatar { - if err := SetSettingNoVersion(ctx, KeyPictureEnableFederatedAvatar, "false"); err != nil { - return fmt.Errorf("failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err) - } - } - enableFederatedAvatar = false - } - - if enableFederatedAvatar || !disableGravatar { - var err error - GravatarSourceURL, err = url.Parse(setting_module.GravatarSource) - if err != nil { - return fmt.Errorf("failed to parse Gravatar URL(%s): %w", setting_module.GravatarSource, err) - } - } - - if GravatarSourceURL != nil && enableFederatedAvatar { - LibravatarService = libravatar.New() - if GravatarSourceURL.Scheme == "https" { - LibravatarService.SetUseHTTPS(true) - LibravatarService.SetSecureFallbackHost(GravatarSourceURL.Host) - } else { - LibravatarService.SetUseHTTPS(false) - LibravatarService.SetFallbackHost(GravatarSourceURL.Host) - } - } - return nil + cacheTime time.Time + revision int + settings map[string]string +} + +var _ config.DynKeyGetter = (*dbConfigCachedGetter)(nil) + +func (d *dbConfigCachedGetter) GetValue(ctx context.Context, key string) (v string, has bool) { + d.mu.RLock() + defer d.mu.RUnlock() + v, has = d.settings[key] + return v, has +} + +func (d *dbConfigCachedGetter) GetRevision(ctx context.Context) int { + d.mu.RLock() + cachedDuration := time.Since(d.cacheTime) + cachedRevision := d.revision + d.mu.RUnlock() + + if cachedDuration < time.Second { + return cachedRevision + } + + d.mu.Lock() + defer d.mu.Unlock() + if GetRevision(ctx) != d.revision { + rev, set, err := GetAllSettings(ctx) + if err != nil { + log.Error("Unable to get all settings: %v", err) + } else { + d.revision = rev + d.settings = set + } + } + d.cacheTime = time.Now() + return d.revision +} + +func (d *dbConfigCachedGetter) InvalidateCache() { + d.mu.Lock() + d.cacheTime = time.Time{} + d.mu.Unlock() +} + +func NewDatabaseDynKeyGetter() config.DynKeyGetter { + return &dbConfigCachedGetter{} } diff --git a/models/system/setting_key.go b/models/system/setting_key.go deleted file mode 100644 index ad083ed1ea..0000000000 --- a/models/system/setting_key.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package system - -// enumerate all system setting keys -const ( - KeyPictureDisableGravatar = "picture.disable_gravatar" - KeyPictureEnableFederatedAvatar = "picture.enable_federated_avatar" -) - -// genSettingCacheKey returns the cache key for some configuration -func genSettingCacheKey(key string) string { - return "system.setting." + key -} diff --git a/models/system/setting_test.go b/models/system/setting_test.go index e6997ad783..0316ea144d 100644 --- a/models/system/setting_test.go +++ b/models/system/setting_test.go @@ -4,7 +4,6 @@ package system_test import ( - "strings" "testing" "code.gitea.io/gitea/models/db" @@ -15,43 +14,41 @@ import ( ) func TestSettings(t *testing.T) { - keyName := "server.LFS_LOCKS_PAGING_NUM" + keyName := "test.key" assert.NoError(t, unittest.PrepareTestDatabase()) - newSetting := &system.Setting{SettingKey: keyName, SettingValue: "50"} + assert.NoError(t, db.TruncateBeans(db.DefaultContext, &system.Setting{})) - // create setting - err := system.SetSetting(db.DefaultContext, newSetting) - assert.NoError(t, err) - // test about saving unchanged values - err = system.SetSetting(db.DefaultContext, newSetting) + rev, settings, err := system.GetAllSettings(db.DefaultContext) assert.NoError(t, err) + assert.EqualValues(t, 1, rev) + assert.Len(t, settings, 1) // there is only one "revision" key - // get specific setting - settings, err := system.GetSettings(db.DefaultContext, []string{keyName}) + err = system.SetSettings(db.DefaultContext, map[string]string{keyName: "true"}) assert.NoError(t, err) - assert.Len(t, settings, 1) - assert.EqualValues(t, newSetting.SettingValue, settings[strings.ToLower(keyName)].SettingValue) - - // updated setting - updatedSetting := &system.Setting{SettingKey: keyName, SettingValue: "100", Version: settings[strings.ToLower(keyName)].Version} - err = system.SetSetting(db.DefaultContext, updatedSetting) - assert.NoError(t, err) - - value, err := system.GetSetting(db.DefaultContext, keyName) - assert.NoError(t, err) - assert.EqualValues(t, updatedSetting.SettingValue, value.SettingValue) - - // get all settings - settings, err = system.GetAllSettings(db.DefaultContext) - assert.NoError(t, err) - assert.Len(t, settings, 3) - assert.EqualValues(t, updatedSetting.SettingValue, settings[strings.ToLower(updatedSetting.SettingKey)].SettingValue) - - // delete setting - err = system.DeleteSetting(db.DefaultContext, &system.Setting{SettingKey: strings.ToLower(keyName)}) - assert.NoError(t, err) - settings, err = system.GetAllSettings(db.DefaultContext) + rev, settings, err = system.GetAllSettings(db.DefaultContext) assert.NoError(t, err) + assert.EqualValues(t, 2, rev) assert.Len(t, settings, 2) + assert.EqualValues(t, "true", settings[keyName]) + + err = system.SetSettings(db.DefaultContext, map[string]string{keyName: "false"}) + assert.NoError(t, err) + rev, settings, err = system.GetAllSettings(db.DefaultContext) + assert.NoError(t, err) + assert.EqualValues(t, 3, rev) + assert.Len(t, settings, 2) + assert.EqualValues(t, "false", settings[keyName]) + + // setting the same value should not trigger DuplicateKey error, and the "version" should be increased + setting := &system.Setting{SettingKey: keyName} + _, err = db.GetByBean(db.DefaultContext, setting) + assert.NoError(t, err) + assert.EqualValues(t, 2, setting.Version) + err = system.SetSettings(db.DefaultContext, map[string]string{keyName: "false"}) + assert.NoError(t, err) + setting = &system.Setting{SettingKey: keyName} + _, err = db.GetByBean(db.DefaultContext, setting) + assert.NoError(t, err) + assert.EqualValues(t, 3, setting.Version) } diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index 1ff0fdc25b..7b40a3147b 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -13,11 +13,12 @@ import ( "testing" "code.gitea.io/gitea/models/db" - system_model "code.gitea.io/gitea/models/system" + "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/modules/auth/password/hash" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/setting/config" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" @@ -134,13 +135,11 @@ func MainTest(m *testing.M, testOpts *TestOptions) { setting.IncomingEmail.ReplyToAddress = "incoming+%{token}@localhost" + config.SetDynGetter(system.NewDatabaseDynKeyGetter()) + if err = storage.Init(); err != nil { fatalTestError("storage.Init: %v\n", err) } - if err = system_model.Init(db.DefaultContext); err != nil { - fatalTestError("models.Init: %v\n", err) - } - if err = util.RemoveAll(repoRootPath); err != nil { fatalTestError("util.RemoveAll: %v\n", err) } diff --git a/models/unittest/unit_tests.go b/models/unittest/unit_tests.go index 399040580c..75898436fc 100644 --- a/models/unittest/unit_tests.go +++ b/models/unittest/unit_tests.go @@ -96,7 +96,15 @@ func AssertExistsAndLoadMap(t assert.TestingT, table string, conditions ...any) // GetCount get the count of a bean func GetCount(t assert.TestingT, bean any, conditions ...any) int { e := db.GetEngine(db.DefaultContext) - count, err := whereOrderConditions(e, conditions).Count(bean) + for _, condition := range conditions { + switch cond := condition.(type) { + case *testCond: + e = e.Where(cond.query, cond.args...) + default: + e = e.Where(cond) + } + } + count, err := e.Count(bean) assert.NoError(t, err) return int(count) } @@ -123,8 +131,8 @@ func AssertSuccessfulInsert(t assert.TestingT, beans ...any) { } // AssertCount assert the count of a bean -func AssertCount(t assert.TestingT, bean, expected any) { - assert.EqualValues(t, expected, GetCount(t, bean)) +func AssertCount(t assert.TestingT, bean, expected any) bool { + return assert.EqualValues(t, expected, GetCount(t, bean)) } // AssertInt64InRange assert value is in range [low, high] @@ -142,7 +150,7 @@ func GetCountByCond(t assert.TestingT, tableName string, cond builder.Cond) int6 } // AssertCountByCond test the count of database entries matching bean -func AssertCountByCond(t assert.TestingT, tableName string, cond builder.Cond, expected int) { - assert.EqualValues(t, expected, GetCountByCond(t, tableName, cond), +func AssertCountByCond(t assert.TestingT, tableName string, cond builder.Cond, expected int) bool { + return assert.EqualValues(t, expected, GetCountByCond(t, tableName, cond), "Failed consistency test, the counted bean (of table %s) was %+v", tableName, cond) } diff --git a/models/user/avatar.go b/models/user/avatar.go index 7f996fa79a..8c9abecbea 100644 --- a/models/user/avatar.go +++ b/models/user/avatar.go @@ -13,7 +13,6 @@ import ( "code.gitea.io/gitea/models/avatars" "code.gitea.io/gitea/models/db" - system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/modules/avatar" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -67,9 +66,7 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string { useLocalAvatar := false autoGenerateAvatar := false - disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar, - setting.GetDefaultDisableGravatar(), - ) + disableGravatar := setting.Config().Picture.DisableGravatar.Value(ctx) switch { case u.UseCustomAvatar: diff --git a/models/user/email_address.go b/models/user/email_address.go index f1ed6692cf..226e0ae5a3 100644 --- a/models/user/email_address.go +++ b/models/user/email_address.go @@ -491,7 +491,7 @@ func SearchEmails(ctx context.Context, opts *SearchEmailOptions) ([]*SearchEmail cond = cond.And(builder.Eq{"email_address.is_activated": false}) } - count, err := db.GetEngine(ctx).Join("INNER", "`user`", "`user`.ID = email_address.uid"). + count, err := db.GetEngine(ctx).Join("INNER", "`user`", "`user`.id = email_address.uid"). Where(cond).Count(new(EmailAddress)) if err != nil { return nil, 0, fmt.Errorf("Count: %w", err) @@ -507,7 +507,7 @@ func SearchEmails(ctx context.Context, opts *SearchEmailOptions) ([]*SearchEmail emails := make([]*SearchEmailResult, 0, opts.PageSize) err = db.GetEngine(ctx).Table("email_address"). Select("email_address.*, `user`.name, `user`.full_name"). - Join("INNER", "`user`", "`user`.ID = email_address.uid"). + Join("INNER", "`user`", "`user`.id = email_address.uid"). Where(cond). OrderBy(orderby). Limit(opts.PageSize, (opts.Page-1)*opts.PageSize). diff --git a/models/user/redirect.go b/models/user/redirect.go index 42e991888a..5a40d4df3b 100644 --- a/models/user/redirect.go +++ b/models/user/redirect.go @@ -48,10 +48,10 @@ func init() { } // LookupUserRedirect look up userID if a user has a redirect name -func LookupUserRedirect(userName string) (int64, error) { +func LookupUserRedirect(ctx context.Context, userName string) (int64, error) { userName = strings.ToLower(userName) redirect := &Redirect{LowerName: userName} - if has, err := db.GetEngine(db.DefaultContext).Get(redirect); err != nil { + if has, err := db.GetEngine(ctx).Get(redirect); err != nil { return 0, err } else if !has { return 0, ErrUserRedirectNotExist{Name: userName} diff --git a/models/user/redirect_test.go b/models/user/redirect_test.go index 3d2ea3aeec..484c5a663f 100644 --- a/models/user/redirect_test.go +++ b/models/user/redirect_test.go @@ -6,6 +6,7 @@ package user_test import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -15,10 +16,10 @@ import ( func TestLookupUserRedirect(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - userID, err := user_model.LookupUserRedirect("olduser1") + userID, err := user_model.LookupUserRedirect(db.DefaultContext, "olduser1") assert.NoError(t, err) assert.EqualValues(t, 1, userID) - _, err = user_model.LookupUserRedirect("doesnotexist") + _, err = user_model.LookupUserRedirect(db.DefaultContext, "doesnotexist") assert.True(t, user_model.IsErrUserRedirectNotExist(err)) } diff --git a/models/user/search.go b/models/user/search.go index 0fa278c257..9484bf4425 100644 --- a/models/user/search.go +++ b/models/user/search.go @@ -9,6 +9,7 @@ import ( "strings" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -30,6 +31,8 @@ type SearchUserOptions struct { Actor *User // The user doing the search SearchByEmail bool // Search by email as well as username/full name + SupportedSortOrders container.Set[string] // if not nil, only allow to use the sort orders in this set + IsActive util.OptionalBool IsAdmin util.OptionalBool IsRestricted util.OptionalBool diff --git a/models/user/user.go b/models/user/user.go index 63b95816ce..509547296b 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -705,9 +705,18 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve return committer.Commit() } +// IsLastAdminUser check whether user is the last admin +func IsLastAdminUser(ctx context.Context, user *User) bool { + if user.IsAdmin && CountUsers(ctx, &CountUserFilter{IsAdmin: util.OptionalBoolTrue}) <= 1 { + return true + } + return false +} + // CountUserFilter represent optional filters for CountUsers type CountUserFilter struct { LastLoginSince *int64 + IsAdmin util.OptionalBool } // CountUsers returns number of users. @@ -716,13 +725,25 @@ func CountUsers(ctx context.Context, opts *CountUserFilter) int64 { } func countUsers(ctx context.Context, opts *CountUserFilter) int64 { - sess := db.GetEngine(ctx).Where(builder.Eq{"type": "0"}) + sess := db.GetEngine(ctx) + cond := builder.NewCond() + cond = cond.And(builder.Eq{"type": UserTypeIndividual}) - if opts != nil && opts.LastLoginSince != nil { - sess = sess.Where(builder.Gte{"last_login_unix": *opts.LastLoginSince}) + if opts != nil { + if opts.LastLoginSince != nil { + cond = cond.And(builder.Gte{"last_login_unix": *opts.LastLoginSince}) + } + + if !opts.IsAdmin.IsNone() { + cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.IsTrue()}) + } + } + + count, err := sess.Where(cond).Count(new(User)) + if err != nil { + log.Error("user.countUsers: %v", err) } - count, _ := sess.Count(new(User)) return count } @@ -1205,6 +1226,8 @@ func isUserVisibleToViewerCond(viewer *User) builder.Cond { return builder.Neq{ "`user`.visibility": structs.VisibleTypePrivate, }.Or( + // viewer self + builder.Eq{"`user`.id": viewer.ID}, // viewer's following builder.In("`user`.id", builder. diff --git a/models/user/user_system.go b/models/user/user_system.go index f54f4e3ffb..e0efa9d2ee 100644 --- a/models/user/user_system.go +++ b/models/user/user_system.go @@ -36,6 +36,7 @@ func NewReplaceUser(name string) *User { } const ( + GhostUserID = -1 ActionsUserID = -2 ActionsUserName = "gitea-actions" ActionsFullName = "Gitea Actions" diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go index fc2bbed083..36e1662e67 100644 --- a/models/webhook/webhook.go +++ b/models/webhook/webhook.go @@ -392,39 +392,40 @@ func CreateWebhooks(ctx context.Context, ws []*Webhook) error { return db.Insert(ctx, ws) } -// getWebhook uses argument bean as query condition, -// ID must be specified and do not assign unnecessary fields. -func getWebhook(bean *Webhook) (*Webhook, error) { - has, err := db.GetEngine(db.DefaultContext).Get(bean) +// GetWebhookByID returns webhook of repository by given ID. +func GetWebhookByID(ctx context.Context, id int64) (*Webhook, error) { + bean := new(Webhook) + has, err := db.GetEngine(ctx).ID(id).Get(bean) if err != nil { return nil, err } else if !has { - return nil, ErrWebhookNotExist{ID: bean.ID} + return nil, ErrWebhookNotExist{ID: id} } return bean, nil } -// GetWebhookByID returns webhook of repository by given ID. -func GetWebhookByID(id int64) (*Webhook, error) { - return getWebhook(&Webhook{ - ID: id, - }) -} - // GetWebhookByRepoID returns webhook of repository by given ID. -func GetWebhookByRepoID(repoID, id int64) (*Webhook, error) { - return getWebhook(&Webhook{ - ID: id, - RepoID: repoID, - }) +func GetWebhookByRepoID(ctx context.Context, repoID, id int64) (*Webhook, error) { + webhook := new(Webhook) + has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", id, repoID).Get(webhook) + if err != nil { + return nil, err + } else if !has { + return nil, ErrWebhookNotExist{ID: id} + } + return webhook, nil } // GetWebhookByOwnerID returns webhook of a user or organization by given ID. -func GetWebhookByOwnerID(ownerID, id int64) (*Webhook, error) { - return getWebhook(&Webhook{ - ID: id, - OwnerID: ownerID, - }) +func GetWebhookByOwnerID(ctx context.Context, ownerID, id int64) (*Webhook, error) { + webhook := new(Webhook) + has, err := db.GetEngine(ctx).Where("id=? AND owner_id=?", id, ownerID).Get(webhook) + if err != nil { + return nil, err + } else if !has { + return nil, ErrWebhookNotExist{ID: id} + } + return webhook, nil } // ListWebhookOptions are options to filter webhooks on ListWebhooksByOpts @@ -482,20 +483,20 @@ func UpdateWebhookLastStatus(w *Webhook) error { return err } -// deleteWebhook uses argument bean as query condition, +// DeleteWebhookByID uses argument bean as query condition, // ID must be specified and do not assign unnecessary fields. -func deleteWebhook(bean *Webhook) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func DeleteWebhookByID(ctx context.Context, id int64) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } defer committer.Close() - if count, err := db.DeleteByBean(ctx, bean); err != nil { + if count, err := db.DeleteByID(ctx, id, new(Webhook)); err != nil { return err } else if count == 0 { - return ErrWebhookNotExist{ID: bean.ID} - } else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: bean.ID}); err != nil { + return ErrWebhookNotExist{ID: id} + } else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: id}); err != nil { return err } @@ -503,17 +504,17 @@ func deleteWebhook(bean *Webhook) (err error) { } // DeleteWebhookByRepoID deletes webhook of repository by given ID. -func DeleteWebhookByRepoID(repoID, id int64) error { - return deleteWebhook(&Webhook{ - ID: id, - RepoID: repoID, - }) +func DeleteWebhookByRepoID(ctx context.Context, repoID, id int64) error { + if _, err := GetWebhookByRepoID(ctx, repoID, id); err != nil { + return err + } + return DeleteWebhookByID(ctx, id) } // DeleteWebhookByOwnerID deletes webhook of a user or organization by given ID. -func DeleteWebhookByOwnerID(ownerID, id int64) error { - return deleteWebhook(&Webhook{ - ID: id, - OwnerID: ownerID, - }) +func DeleteWebhookByOwnerID(ctx context.Context, ownerID, id int64) error { + if _, err := GetWebhookByOwnerID(ctx, ownerID, id); err != nil { + return err + } + return DeleteWebhookByID(ctx, id) } diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go index de6568c321..b7d2d3d41a 100644 --- a/models/webhook/webhook_test.go +++ b/models/webhook/webhook_test.go @@ -101,22 +101,22 @@ func TestCreateWebhook(t *testing.T) { func TestGetWebhookByRepoID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - hook, err := GetWebhookByRepoID(1, 1) + hook, err := GetWebhookByRepoID(db.DefaultContext, 1, 1) assert.NoError(t, err) assert.Equal(t, int64(1), hook.ID) - _, err = GetWebhookByRepoID(unittest.NonexistentID, unittest.NonexistentID) + _, err = GetWebhookByRepoID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID) assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } func TestGetWebhookByOwnerID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - hook, err := GetWebhookByOwnerID(3, 3) + hook, err := GetWebhookByOwnerID(db.DefaultContext, 3, 3) assert.NoError(t, err) assert.Equal(t, int64(3), hook.ID) - _, err = GetWebhookByOwnerID(unittest.NonexistentID, unittest.NonexistentID) + _, err = GetWebhookByOwnerID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID) assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } @@ -174,10 +174,10 @@ func TestUpdateWebhook(t *testing.T) { func TestDeleteWebhookByRepoID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2, RepoID: 1}) - assert.NoError(t, DeleteWebhookByRepoID(1, 2)) + assert.NoError(t, DeleteWebhookByRepoID(db.DefaultContext, 1, 2)) unittest.AssertNotExistsBean(t, &Webhook{ID: 2, RepoID: 1}) - err := DeleteWebhookByRepoID(unittest.NonexistentID, unittest.NonexistentID) + err := DeleteWebhookByRepoID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID) assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } @@ -185,10 +185,10 @@ func TestDeleteWebhookByRepoID(t *testing.T) { func TestDeleteWebhookByOwnerID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 3, OwnerID: 3}) - assert.NoError(t, DeleteWebhookByOwnerID(3, 3)) + assert.NoError(t, DeleteWebhookByOwnerID(db.DefaultContext, 3, 3)) unittest.AssertNotExistsBean(t, &Webhook{ID: 3, OwnerID: 3}) - err := DeleteWebhookByOwnerID(unittest.NonexistentID, unittest.NonexistentID) + err := DeleteWebhookByOwnerID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID) assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } diff --git a/modules/actions/github.go b/modules/actions/github.go index 71f81a8903..68116ec83a 100644 --- a/modules/actions/github.go +++ b/modules/actions/github.go @@ -22,8 +22,48 @@ const ( GithubEventRelease = "release" GithubEventPullRequestComment = "pull_request_comment" GithubEventGollum = "gollum" + GithubEventSchedule = "schedule" ) +// IsDefaultBranchWorkflow returns true if the event only triggers workflows on the default branch +func IsDefaultBranchWorkflow(triggedEvent webhook_module.HookEventType) bool { + switch triggedEvent { + case webhook_module.HookEventDelete: + // GitHub "delete" event + // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#delete + return true + case webhook_module.HookEventFork: + // GitHub "fork" event + // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#fork + return true + case webhook_module.HookEventIssueComment: + // GitHub "issue_comment" event + // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issue_comment + return true + case webhook_module.HookEventPullRequestComment: + // GitHub "pull_request_comment" event + // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment + return true + case webhook_module.HookEventWiki: + // GitHub "gollum" event + // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum + return true + case webhook_module.HookEventSchedule: + // GitHub "schedule" event + // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule + return true + case webhook_module.HookEventIssues, + webhook_module.HookEventIssueAssign, + webhook_module.HookEventIssueLabel, + webhook_module.HookEventIssueMilestone: + // Github "issues" event + // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issues + return true + } + + return false +} + // canGithubEventMatch check if the input Github event can match any Gitea event. func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEventType) bool { switch eventName { @@ -51,7 +91,9 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent case webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequestSync, webhook_module.HookEventPullRequestAssign, - webhook_module.HookEventPullRequestLabel: + webhook_module.HookEventPullRequestLabel, + webhook_module.HookEventPullRequestReviewRequest, + webhook_module.HookEventPullRequestMilestone: return true default: @@ -69,6 +111,14 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent return false } + case GithubEventSchedule: + return triggedEvent == webhook_module.HookEventSchedule + + case GithubEventIssueComment: + // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment + return triggedEvent == webhook_module.HookEventIssueComment || + triggedEvent == webhook_module.HookEventPullRequestComment + default: return eventName == string(triggedEvent) } diff --git a/modules/actions/github_test.go b/modules/actions/github_test.go index 4bf55ae03f..6652ff6eac 100644 --- a/modules/actions/github_test.go +++ b/modules/actions/github_test.go @@ -103,6 +103,12 @@ func TestCanGithubEventMatch(t *testing.T) { webhook_module.HookEventCreate, true, }, + { + "create pull request comment", + GithubEventIssueComment, + webhook_module.HookEventPullRequestComment, + true, + }, } for _, tc := range testCases { diff --git a/modules/actions/task_state.go b/modules/actions/task_state.go index cbbc0b357d..31a74be3fd 100644 --- a/modules/actions/task_state.go +++ b/modules/actions/task_state.go @@ -35,9 +35,18 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep { } else if task.Status.IsDone() { preStep.Stopped = task.Stopped preStep.Status = actions_model.StatusFailure + if task.Status.IsSkipped() { + preStep.Status = actions_model.StatusSkipped + } } logIndex += preStep.LogLength + // lastHasRunStep is the last step that has run. + // For example, + // 1. preStep(Success) -> step1(Success) -> step2(Running) -> step3(Waiting) -> postStep(Waiting): lastHasRunStep is step1. + // 2. preStep(Success) -> step1(Success) -> step2(Success) -> step3(Success) -> postStep(Success): lastHasRunStep is step3. + // 3. preStep(Success) -> step1(Success) -> step2(Failure) -> step3 -> postStep(Waiting): lastHasRunStep is step2. + // So its Stopped is the Started of postStep when there are no more steps to run. var lastHasRunStep *actions_model.ActionTaskStep for _, step := range task.Steps { if step.Status.HasRun() { @@ -53,11 +62,15 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep { Name: postStepName, Status: actions_model.StatusWaiting, } - if task.Status.IsDone() { + // If the lastHasRunStep is the last step, or it has failed, postStep has started. + if lastHasRunStep.Status.IsFailure() || lastHasRunStep == task.Steps[len(task.Steps)-1] { postStep.LogIndex = logIndex postStep.LogLength = task.LogLength - postStep.LogIndex - postStep.Status = task.Status postStep.Started = lastHasRunStep.Stopped + postStep.Status = actions_model.StatusRunning + } + if task.Status.IsDone() { + postStep.Status = task.Status postStep.Stopped = task.Stopped } ret := make([]*actions_model.ActionTaskStep, 0, len(task.Steps)+2) diff --git a/modules/actions/task_state_test.go b/modules/actions/task_state_test.go index 3a599fbcbd..28213d781b 100644 --- a/modules/actions/task_state_test.go +++ b/modules/actions/task_state_test.go @@ -103,6 +103,40 @@ func TestFullSteps(t *testing.T) { {Name: postStepName, Status: actions_model.StatusSuccess, LogIndex: 100, LogLength: 0, Started: 10100, Stopped: 10100}, }, }, + { + name: "all steps finished but task is running", + task: &actions_model.ActionTask{ + Steps: []*actions_model.ActionTaskStep{ + {Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090}, + }, + Status: actions_model.StatusRunning, + Started: 10000, + Stopped: 0, + LogLength: 100, + }, + want: []*actions_model.ActionTaskStep{ + {Name: preStepName, Status: actions_model.StatusSuccess, LogIndex: 0, LogLength: 10, Started: 10000, Stopped: 10010}, + {Status: actions_model.StatusSuccess, LogIndex: 10, LogLength: 80, Started: 10010, Stopped: 10090}, + {Name: postStepName, Status: actions_model.StatusRunning, LogIndex: 90, LogLength: 10, Started: 10090, Stopped: 0}, + }, + }, + { + name: "skipped task", + task: &actions_model.ActionTask{ + Steps: []*actions_model.ActionTaskStep{ + {Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, + }, + Status: actions_model.StatusSkipped, + Started: 0, + Stopped: 0, + LogLength: 0, + }, + want: []*actions_model.ActionTaskStep{ + {Name: preStepName, Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, + {Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, + {Name: postStepName, Status: actions_model.StatusSkipped, LogIndex: 0, LogLength: 0, Started: 0, Stopped: 0}, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 408fdb8f8e..595fd8bbb0 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -22,7 +22,7 @@ import ( type DetectedWorkflow struct { EntryName string - TriggerEvent string + TriggerEvent *jobparser.Event Content []byte } @@ -100,6 +100,7 @@ func DetectWorkflows( commit *git.Commit, triggedEvent webhook_module.HookEventType, payload api.Payloader, + detectSchedule bool, ) ([]*DetectedWorkflow, []*DetectedWorkflow, error) { entries, err := ListWorkflows(commit) if err != nil { @@ -114,6 +115,7 @@ func DetectWorkflows( return nil, nil, err } + // one workflow may have multiple events events, err := GetEventsFromContent(content) if err != nil { log.Warn("ignore invalid workflow %q: %v", entry.Name(), err) @@ -122,17 +124,18 @@ func DetectWorkflows( for _, evt := range events { log.Trace("detect workflow %q for event %#v matching %q", entry.Name(), evt, triggedEvent) if evt.IsSchedule() { - dwf := &DetectedWorkflow{ - EntryName: entry.Name(), - TriggerEvent: evt.Name, - Content: content, + if detectSchedule { + dwf := &DetectedWorkflow{ + EntryName: entry.Name(), + TriggerEvent: evt, + Content: content, + } + schedules = append(schedules, dwf) } - schedules = append(schedules, dwf) - } - if detectMatched(gitRepo, commit, triggedEvent, payload, evt) { + } else if detectMatched(gitRepo, commit, triggedEvent, payload, evt) { dwf := &DetectedWorkflow{ EntryName: entry.Name(), - TriggerEvent: evt.Name, + TriggerEvent: evt, Content: content, } workflows = append(workflows, dwf) @@ -143,6 +146,41 @@ func DetectWorkflows( return workflows, schedules, nil } +func DetectScheduledWorkflows(gitRepo *git.Repository, commit *git.Commit) ([]*DetectedWorkflow, error) { + entries, err := ListWorkflows(commit) + if err != nil { + return nil, err + } + + wfs := make([]*DetectedWorkflow, 0, len(entries)) + for _, entry := range entries { + content, err := GetContentFromEntry(entry) + if err != nil { + return nil, err + } + + // one workflow may have multiple events + events, err := GetEventsFromContent(content) + if err != nil { + log.Warn("ignore invalid workflow %q: %v", entry.Name(), err) + continue + } + for _, evt := range events { + if evt.IsSchedule() { + log.Trace("detect scheduled workflow: %q", entry.Name()) + dwf := &DetectedWorkflow{ + EntryName: entry.Name(), + TriggerEvent: evt, + Content: content, + } + wfs = append(wfs, dwf) + } + } + } + + return wfs, nil +} + func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent webhook_module.HookEventType, payload api.Payloader, evt *jobparser.Event) bool { if !canGithubEventMatch(evt.Name, triggedEvent) { return false @@ -153,7 +191,8 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web webhook_module.HookEventCreate, webhook_module.HookEventDelete, webhook_module.HookEventFork, - webhook_module.HookEventWiki: + webhook_module.HookEventWiki, + webhook_module.HookEventSchedule: if len(evt.Acts()) != 0 { log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts()) } @@ -182,7 +221,9 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequestSync, webhook_module.HookEventPullRequestAssign, - webhook_module.HookEventPullRequestLabel: + webhook_module.HookEventPullRequestLabel, + webhook_module.HookEventPullRequestReviewRequest, + webhook_module.HookEventPullRequestMilestone: return matchPullRequestEvent(gitRepo, commit, payload.(*api.PullRequestPayload), evt) case // pull_request_review @@ -358,13 +399,13 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa } else { // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request // Actions with the same name: - // opened, edited, closed, reopened, assigned, unassigned + // opened, edited, closed, reopened, assigned, unassigned, review_requested, review_request_removed, milestoned, demilestoned // Actions need to be converted: // synchronized -> synchronize // label_updated -> labeled // label_cleared -> unlabeled // Unsupported activity types: - // converted_to_draft, ready_for_review, locked, unlocked, review_requested, review_request_removed, auto_merge_enabled, auto_merge_disabled + // converted_to_draft, ready_for_review, locked, unlocked, auto_merge_enabled, auto_merge_disabled, enqueued, dequeued action := prPayload.Action switch action { @@ -400,6 +441,9 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa // all acts conditions should be satisfied for cond, vals := range acts { switch cond { + case "types": + // types have been checked + continue case "branches": refName := git.RefName(prPayload.PullRequest.Base.Ref) patterns, err := workflowpattern.CompilePatterns(vals...) diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index 2d57f19488..c8e1e553fe 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -118,6 +118,13 @@ func TestDetectMatched(t *testing.T) { yamlOn: "on: gollum", expected: true, }, + { + desc: "HookEventSchedue(schedule) matches GithubEventSchedule(schedule)", + triggedEvent: webhook_module.HookEventSchedule, + payload: nil, + yamlOn: "on: schedule", + expected: true, + }, } for _, tc := range testCases { diff --git a/modules/base/tool.go b/modules/base/tool.go index 71dcb83fb4..971b55bf72 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -129,7 +129,7 @@ func CreateTimeLimitCode(data string, minutes int, startInf any) string { // create sha1 encode string sh := sha1.New() - _, _ = sh.Write([]byte(fmt.Sprintf("%s%s%s%s%d", data, setting.SecretKey, startStr, endStr, minutes))) + _, _ = sh.Write([]byte(fmt.Sprintf("%s%s%s%s%d", data, hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), startStr, endStr, minutes))) encoded := hex.EncodeToString(sh.Sum(nil)) code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded) diff --git a/modules/charset/charset.go b/modules/charset/charset.go index 51152142a5..1855446a98 100644 --- a/modules/charset/charset.go +++ b/modules/charset/charset.go @@ -22,17 +22,21 @@ import ( // UTF8BOM is the utf-8 byte-order marker var UTF8BOM = []byte{'\xef', '\xbb', '\xbf'} +type ConvertOpts struct { + KeepBOM bool +} + // ToUTF8WithFallbackReader detects the encoding of content and converts to UTF-8 reader if possible -func ToUTF8WithFallbackReader(rd io.Reader) io.Reader { +func ToUTF8WithFallbackReader(rd io.Reader, opts ConvertOpts) io.Reader { buf := make([]byte, 2048) n, err := util.ReadAtMost(rd, buf) if err != nil { - return io.MultiReader(bytes.NewReader(RemoveBOMIfPresent(buf[:n])), rd) + return io.MultiReader(bytes.NewReader(MaybeRemoveBOM(buf[:n], opts)), rd) } charsetLabel, err := DetectEncoding(buf[:n]) if err != nil || charsetLabel == "UTF-8" { - return io.MultiReader(bytes.NewReader(RemoveBOMIfPresent(buf[:n])), rd) + return io.MultiReader(bytes.NewReader(MaybeRemoveBOM(buf[:n], opts)), rd) } encoding, _ := charset.Lookup(charsetLabel) @@ -42,20 +46,20 @@ func ToUTF8WithFallbackReader(rd io.Reader) io.Reader { return transform.NewReader( io.MultiReader( - bytes.NewReader(RemoveBOMIfPresent(buf[:n])), + bytes.NewReader(MaybeRemoveBOM(buf[:n], opts)), rd, ), encoding.NewDecoder(), ) } -// ToUTF8WithErr converts content to UTF8 encoding -func ToUTF8WithErr(content []byte) (string, error) { +// ToUTF8 converts content to UTF8 encoding +func ToUTF8(content []byte, opts ConvertOpts) (string, error) { charsetLabel, err := DetectEncoding(content) if err != nil { return "", err } else if charsetLabel == "UTF-8" { - return string(RemoveBOMIfPresent(content)), nil + return string(MaybeRemoveBOM(content, opts)), nil } encoding, _ := charset.Lookup(charsetLabel) @@ -70,28 +74,22 @@ func ToUTF8WithErr(content []byte) (string, error) { result = append(result, content[n:]...) } - result = RemoveBOMIfPresent(result) + result = MaybeRemoveBOM(result, opts) return string(result), err } // ToUTF8WithFallback detects the encoding of content and converts to UTF-8 if possible -func ToUTF8WithFallback(content []byte) []byte { - bs, _ := io.ReadAll(ToUTF8WithFallbackReader(bytes.NewReader(content))) +func ToUTF8WithFallback(content []byte, opts ConvertOpts) []byte { + bs, _ := io.ReadAll(ToUTF8WithFallbackReader(bytes.NewReader(content), opts)) return bs } -// ToUTF8 converts content to UTF8 encoding and ignore error -func ToUTF8(content string) string { - res, _ := ToUTF8WithErr([]byte(content)) - return res -} - // ToUTF8DropErrors makes sure the return string is valid utf-8; attempts conversion if possible -func ToUTF8DropErrors(content []byte) []byte { +func ToUTF8DropErrors(content []byte, opts ConvertOpts) []byte { charsetLabel, err := DetectEncoding(content) if err != nil || charsetLabel == "UTF-8" { - return RemoveBOMIfPresent(content) + return MaybeRemoveBOM(content, opts) } encoding, _ := charset.Lookup(charsetLabel) @@ -117,11 +115,14 @@ func ToUTF8DropErrors(content []byte) []byte { } } - return RemoveBOMIfPresent(decoded) + return MaybeRemoveBOM(decoded, opts) } -// RemoveBOMIfPresent removes a UTF-8 BOM from a []byte -func RemoveBOMIfPresent(content []byte) []byte { +// MaybeRemoveBOM removes a UTF-8 BOM from a []byte when opts.KeepBOM is false +func MaybeRemoveBOM(content []byte, opts ConvertOpts) []byte { + if opts.KeepBOM { + return content + } if len(content) > 2 && bytes.Equal(content[0:3], UTF8BOM) { return content[3:] } diff --git a/modules/charset/charset_test.go b/modules/charset/charset_test.go index fc56799b47..829844a976 100644 --- a/modules/charset/charset_test.go +++ b/modules/charset/charset_test.go @@ -30,15 +30,15 @@ func resetDefaultCharsetsOrder() { } } -func TestRemoveBOMIfPresent(t *testing.T) { - res := RemoveBOMIfPresent([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) +func TestMaybeRemoveBOM(t *testing.T) { + res := MaybeRemoveBOM([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{}) assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) - res = RemoveBOMIfPresent([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + res = MaybeRemoveBOM([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{}) assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) } -func TestToUTF8WithErr(t *testing.T) { +func TestToUTF8(t *testing.T) { resetDefaultCharsetsOrder() var res string var err error @@ -47,53 +47,53 @@ func TestToUTF8WithErr(t *testing.T) { // locale, so some conversions might behave differently. For that reason, we don't // depend on particular conversions but in expected behaviors. - res, err = ToUTF8WithErr([]byte{0x41, 0x42, 0x43}) + res, err = ToUTF8([]byte{0x41, 0x42, 0x43}, ConvertOpts{}) assert.NoError(t, err) assert.Equal(t, "ABC", res) // "áéíóú" - res, err = ToUTF8WithErr([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + res, err = ToUTF8([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{}) assert.NoError(t, err) assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res)) // "áéíóú" - res, err = ToUTF8WithErr([]byte{ + res, err = ToUTF8([]byte{ 0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba, - }) + }, ConvertOpts{}) assert.NoError(t, err) assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res)) - res, err = ToUTF8WithErr([]byte{ + res, err = ToUTF8([]byte{ 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e, - }) + }, ConvertOpts{}) assert.NoError(t, err) stringMustStartWith(t, "Hola,", res) stringMustEndWith(t, "AAA.", res) - res, err = ToUTF8WithErr([]byte{ + res, err = ToUTF8([]byte{ 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e, - }) + }, ConvertOpts{}) assert.NoError(t, err) stringMustStartWith(t, "Hola,", res) stringMustEndWith(t, "AAA.", res) - res, err = ToUTF8WithErr([]byte{ + res, err = ToUTF8([]byte{ 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73, 0x41, 0x41, 0x41, 0x2e, - }) + }, ConvertOpts{}) assert.NoError(t, err) stringMustStartWith(t, "Hola,", res) stringMustEndWith(t, "AAA.", res) // Japanese (Shift-JIS) // 日属秘ぞしちゅ。 - res, err = ToUTF8WithErr([]byte{ + res, err = ToUTF8([]byte{ 0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42, - }) + }, ConvertOpts{}) assert.NoError(t, err) assert.Equal(t, []byte{ 0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3, @@ -101,7 +101,7 @@ func TestToUTF8WithErr(t *testing.T) { }, []byte(res)) - res, err = ToUTF8WithErr([]byte{0x00, 0x00, 0x00, 0x00}) + res, err = ToUTF8([]byte{0x00, 0x00, 0x00, 0x00}, ConvertOpts{}) assert.NoError(t, err) assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, []byte(res)) } @@ -109,22 +109,22 @@ func TestToUTF8WithErr(t *testing.T) { func TestToUTF8WithFallback(t *testing.T) { resetDefaultCharsetsOrder() // "ABC" - res := ToUTF8WithFallback([]byte{0x41, 0x42, 0x43}) + res := ToUTF8WithFallback([]byte{0x41, 0x42, 0x43}, ConvertOpts{}) assert.Equal(t, []byte{0x41, 0x42, 0x43}, res) // "áéíóú" - res = ToUTF8WithFallback([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + res = ToUTF8WithFallback([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{}) assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) // UTF8 BOM + "áéíóú" - res = ToUTF8WithFallback([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + res = ToUTF8WithFallback([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{}) assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) // "Hola, así cómo ños" res = ToUTF8WithFallback([]byte{ 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73, - }) + }, ConvertOpts{}) assert.Equal(t, []byte{ 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20, 0xC3, 0xB1, 0x6F, 0x73, @@ -133,126 +133,65 @@ func TestToUTF8WithFallback(t *testing.T) { // "Hola, así cómo " minmatch := []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20} - res = ToUTF8WithFallback([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73}) + res = ToUTF8WithFallback([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73}, ConvertOpts{}) // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those assert.Equal(t, minmatch, res[0:len(minmatch)]) - res = ToUTF8WithFallback([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73}) + res = ToUTF8WithFallback([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73}, ConvertOpts{}) // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those assert.Equal(t, minmatch, res[0:len(minmatch)]) // Japanese (Shift-JIS) // "日属秘ぞしちゅ。" - res = ToUTF8WithFallback([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42}) + res = ToUTF8WithFallback([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42}, ConvertOpts{}) assert.Equal(t, []byte{ 0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3, 0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82, }, res) - res = ToUTF8WithFallback([]byte{0x00, 0x00, 0x00, 0x00}) + res = ToUTF8WithFallback([]byte{0x00, 0x00, 0x00, 0x00}, ConvertOpts{}) assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res) } -func TestToUTF8(t *testing.T) { - resetDefaultCharsetsOrder() - // Note: golang compiler seems so behave differently depending on the current - // locale, so some conversions might behave differently. For that reason, we don't - // depend on particular conversions but in expected behaviors. - - res := ToUTF8(string([]byte{0x41, 0x42, 0x43})) - assert.Equal(t, "ABC", res) - - // "áéíóú" - res = ToUTF8(string([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba})) - assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res)) - - // BOM + "áéíóú" - res = ToUTF8(string([]byte{ - 0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, - 0xc3, 0xba, - })) - assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, []byte(res)) - - // Latin1 - // Hola, así cómo ños - res = ToUTF8(string([]byte{ - 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, - 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73, - })) - assert.Equal(t, []byte{ - 0x48, 0x6f, 0x6c, 0x61, 0x2c, 0x20, 0x61, 0x73, 0xc3, 0xad, 0x20, 0x63, - 0xc3, 0xb3, 0x6d, 0x6f, 0x20, 0xc3, 0xb1, 0x6f, 0x73, - }, []byte(res)) - - // Latin1 - // Hola, así cómo \x07ños - res = ToUTF8(string([]byte{ - 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, - 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73, - })) - // Hola, - bytesMustStartWith(t, []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C}, []byte(res)) - - // This test FAILS - // res = ToUTF8("Hola, así cómo \x81ños") - // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those - // assert.Regexp(t, "^Hola, así cómo", res) - - // Japanese (Shift-JIS) - // 日属秘ぞしちゅ。 - res = ToUTF8(string([]byte{ - 0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, - 0xBF, 0x82, 0xE3, 0x81, 0x42, - })) - assert.Equal(t, []byte{ - 0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3, - 0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82, - }, - []byte(res)) - - res = ToUTF8("\x00\x00\x00\x00") - assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, []byte(res)) -} - func TestToUTF8DropErrors(t *testing.T) { resetDefaultCharsetsOrder() // "ABC" - res := ToUTF8DropErrors([]byte{0x41, 0x42, 0x43}) + res := ToUTF8DropErrors([]byte{0x41, 0x42, 0x43}, ConvertOpts{}) assert.Equal(t, []byte{0x41, 0x42, 0x43}, res) // "áéíóú" - res = ToUTF8DropErrors([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + res = ToUTF8DropErrors([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{}) assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) // UTF8 BOM + "áéíóú" - res = ToUTF8DropErrors([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + res = ToUTF8DropErrors([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{}) assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) // "Hola, así cómo ños" - res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73}) + res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73}, ConvertOpts{}) assert.Equal(t, []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73}, res[:8]) assert.Equal(t, []byte{0x73}, res[len(res)-1:]) // "Hola, así cómo " minmatch := []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20} - res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73}) + res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73}, ConvertOpts{}) // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those assert.Equal(t, minmatch, res[0:len(minmatch)]) - res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73}) + res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73}, ConvertOpts{}) // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those assert.Equal(t, minmatch, res[0:len(minmatch)]) // Japanese (Shift-JIS) // "日属秘ぞしちゅ。" - res = ToUTF8DropErrors([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42}) + res = ToUTF8DropErrors([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42}, ConvertOpts{}) assert.Equal(t, []byte{ 0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3, 0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82, }, res) - res = ToUTF8DropErrors([]byte{0x00, 0x00, 0x00, 0x00}) + res = ToUTF8DropErrors([]byte{0x00, 0x00, 0x00, 0x00}, ConvertOpts{}) assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res) } @@ -302,10 +241,6 @@ func stringMustEndWith(t *testing.T, expected, value string) { assert.Equal(t, expected, value[len(value)-len(expected):]) } -func bytesMustStartWith(t *testing.T, expected, value []byte) { - assert.Equal(t, expected, value[:len(expected)]) -} - func TestToUTF8WithFallbackReader(t *testing.T) { resetDefaultCharsetsOrder() @@ -317,7 +252,7 @@ func TestToUTF8WithFallbackReader(t *testing.T) { } input = input[:testLen] input += "// Выключаем" - rd := ToUTF8WithFallbackReader(bytes.NewReader([]byte(input))) + rd := ToUTF8WithFallbackReader(bytes.NewReader([]byte(input)), ConvertOpts{}) r, _ := io.ReadAll(rd) assert.EqualValuesf(t, input, string(r), "testing string len=%d", testLen) } diff --git a/modules/charset/escape.go b/modules/charset/escape.go index 5608836a45..92e417d1f7 100644 --- a/modules/charset/escape.go +++ b/modules/charset/escape.go @@ -8,11 +8,12 @@ package charset import ( - "bufio" + "html/template" "io" "strings" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/translation" ) @@ -20,20 +21,18 @@ import ( const RuneNBSP = 0xa0 // EscapeControlHTML escapes the unicode control sequences in a provided html document -func EscapeControlHTML(text string, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output string) { +func EscapeControlHTML(html template.HTML, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output template.HTML) { sb := &strings.Builder{} - outputStream := &HTMLStreamerWriter{Writer: sb} - streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer) - - if err := StreamHTML(strings.NewReader(text), streamer); err != nil { - streamer.escaped.HasError = true - log.Error("Error whilst escaping: %v", err) - } - return streamer.escaped, sb.String() + escaped, _ = EscapeControlReader(strings.NewReader(string(html)), sb, locale, allowed...) // err has been handled in EscapeControlReader + return escaped, template.HTML(sb.String()) } -// EscapeControlReaders escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte +// EscapeControlReader escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) { + if !setting.UI.AmbiguousUnicodeDetection { + _, err = io.Copy(writer, reader) + return &EscapeStatus{}, err + } outputStream := &HTMLStreamerWriter{Writer: writer} streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer) @@ -43,41 +42,3 @@ func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation. } return streamer.escaped, err } - -// EscapeControlStringReader escapes the unicode control sequences in a provided reader of string content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte. HTML line breaks are not inserted after every newline by this method. -func EscapeControlStringReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) { - bufRd := bufio.NewReader(reader) - outputStream := &HTMLStreamerWriter{Writer: writer} - streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer) - - for { - line, rdErr := bufRd.ReadString('\n') - if len(line) > 0 { - if err := streamer.Text(line); err != nil { - streamer.escaped.HasError = true - log.Error("Error whilst escaping: %v", err) - return streamer.escaped, err - } - } - if rdErr != nil { - if rdErr != io.EOF { - err = rdErr - } - break - } - } - return streamer.escaped, err -} - -// EscapeControlString escapes the unicode control sequences in a provided string and returns the findings as an EscapeStatus and the escaped string -func EscapeControlString(text string, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output string) { - sb := &strings.Builder{} - outputStream := &HTMLStreamerWriter{Writer: sb} - streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer) - - if err := streamer.Text(text); err != nil { - streamer.escaped.HasError = true - log.Error("Error whilst escaping: %v", err) - } - return streamer.escaped, sb.String() -} diff --git a/modules/charset/escape_stream.go b/modules/charset/escape_stream.go index 03d4cfc0c1..3f08fd94a4 100644 --- a/modules/charset/escape_stream.go +++ b/modules/charset/escape_stream.go @@ -64,7 +64,7 @@ func (e *escapeStreamer) Text(data string) error { until, next = nextIdxs[0]+pos, nextIdxs[1]+pos } - // from pos until until we know that the runes are not \r\t\n or even ' ' + // from pos until we know that the runes are not \r\t\n or even ' ' runes := make([]rune, 0, next-until) positions := make([]int, 0, next-until+1) diff --git a/modules/charset/escape_test.go b/modules/charset/escape_test.go index f63c5c5c52..a353ced631 100644 --- a/modules/charset/escape_test.go +++ b/modules/charset/escape_test.go @@ -4,11 +4,14 @@ package charset import ( - "reflect" "strings" "testing" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" + + "github.com/stretchr/testify/assert" ) type escapeControlTest struct { @@ -132,22 +135,8 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`, }, } -func TestEscapeControlString(t *testing.T) { - for _, tt := range escapeControlTests { - t.Run(tt.name, func(t *testing.T) { - status, result := EscapeControlString(tt.text, &translation.MockLocale{}) - if !reflect.DeepEqual(*status, tt.status) { - t.Errorf("EscapeControlString() status = %v, wanted= %v", status, tt.status) - } - if result != tt.result { - t.Errorf("EscapeControlString()\nresult= %v,\nwanted= %v", result, tt.result) - } - }) - } -} - func TestEscapeControlReader(t *testing.T) { - // lets add some control characters to the tests + // add some control characters to the tests tests := make([]escapeControlTest, 0, len(escapeControlTests)*3) copy(tests, escapeControlTests) @@ -169,29 +158,20 @@ func TestEscapeControlReader(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - input := strings.NewReader(tt.text) output := &strings.Builder{} - status, err := EscapeControlReader(input, output, &translation.MockLocale{}) - result := output.String() - if err != nil { - t.Errorf("EscapeControlReader(): err = %v", err) - } - - if !reflect.DeepEqual(*status, tt.status) { - t.Errorf("EscapeControlReader() status = %v, wanted= %v", status, tt.status) - } - if result != tt.result { - t.Errorf("EscapeControlReader()\nresult= %v,\nwanted= %v", result, tt.result) - } + status, err := EscapeControlReader(strings.NewReader(tt.text), output, &translation.MockLocale{}) + assert.NoError(t, err) + assert.Equal(t, tt.status, *status) + assert.Equal(t, tt.result, output.String()) }) } } -func TestEscapeControlReader_panic(t *testing.T) { - bs := make([]byte, 0, 20479) - bs = append(bs, 'A') - for i := 0; i < 6826; i++ { - bs = append(bs, []byte("—")...) - } - _, _ = EscapeControlString(string(bs), &translation.MockLocale{}) +func TestSettingAmbiguousUnicodeDetection(t *testing.T) { + defer test.MockVariableValue(&setting.UI.AmbiguousUnicodeDetection, true)() + _, out := EscapeControlHTML("a test", &translation.MockLocale{}) + assert.EqualValues(t, `a test`, out) + setting.UI.AmbiguousUnicodeDetection = false + _, out = EscapeControlHTML("a test", &translation.MockLocale{}) + assert.EqualValues(t, `a test`, out) } diff --git a/modules/context/api.go b/modules/context/api.go index 044ec51b56..c6d47e3e8b 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -11,7 +11,6 @@ import ( "net/url" "strings" - "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -205,32 +204,6 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) { } } -// CheckForOTP validates OTP -func (ctx *APIContext) CheckForOTP() { - if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) { - return // Skip 2FA - } - - otpHeader := ctx.Req.Header.Get("X-Gitea-OTP") - twofa, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID) - if err != nil { - if auth.IsErrTwoFactorNotEnrolled(err) { - return // No 2FA enrollment for this user - } - ctx.Error(http.StatusInternalServerError, "GetTwoFactorByUID", err) - return - } - ok, err := twofa.ValidateTOTP(otpHeader) - if err != nil { - ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err) - return - } - if !ok { - ctx.Error(http.StatusUnauthorized, "", nil) - return - } -} - // APIContexter returns apicontext as middleware func APIContexter() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { diff --git a/modules/context/base.go b/modules/context/base.go index 8df1dde866..87da9ff360 100644 --- a/modules/context/base.go +++ b/modules/context/base.go @@ -255,7 +255,7 @@ func (b *Base) Redirect(location string, status ...int) { code = status[0] } - if strings.Contains(location, "://") || strings.HasPrefix(location, "//") { + if strings.HasPrefix(location, "http://") || strings.HasPrefix(location, "https://") || strings.HasPrefix(location, "//") { // Some browsers (Safari) have buggy behavior for Cookie + Cache + External Redirection, eg: /my-path => https://other/path // 1. the first request to "/my-path" contains cookie // 2. some time later, the request to "/my-path" doesn't contain cookie (caused by Prevent web tracking) diff --git a/modules/context/context.go b/modules/context/context.go index 47ad310b09..58b12796e4 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -6,6 +6,7 @@ package context import ( "context" + "encoding/hex" "html" "html/template" "io" @@ -134,7 +135,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context { func Contexter() func(next http.Handler) http.Handler { rnd := templates.HTMLRenderer() csrfOpts := CsrfOptions{ - Secret: setting.SecretKey, + Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), Cookie: setting.CSRFCookieName, SetCookie: true, Secure: setting.SessionConfig.Secure, @@ -157,7 +158,6 @@ func Contexter() func(next http.Handler) http.Handler { ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI() ctx.Data["Link"] = ctx.Link - ctx.Data["locale"] = ctx.Locale // PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules ctx.PageData = map[string]any{} diff --git a/modules/context/org.go b/modules/context/org.go index 7638ffdd3f..c27d07e538 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -46,7 +46,7 @@ func GetOrganizationByParams(ctx *Context) { ctx.Org.Organization, err = organization.GetOrgByName(ctx, orgName) if err != nil { if organization.IsErrOrgNotExist(err) { - redirectUserID, err := user_model.LookupUserRedirect(orgName) + redirectUserID, err := user_model.LookupUserRedirect(ctx, orgName) if err == nil { RedirectToUser(ctx.Base, orgName, redirectUserID) } else if user_model.IsErrUserRedirectNotExist(err) { diff --git a/modules/context/package.go b/modules/context/package.go index c0813fb2da..978ca2aaa5 100644 --- a/modules/context/package.go +++ b/modules/context/package.go @@ -93,7 +93,7 @@ func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, string, any)) } func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.AccessMode, error) { - if setting.Service.RequireSignInView && doer == nil { + if setting.Service.RequireSignInView && (doer == nil || doer.IsGhost()) { return perm.AccessModeNone, nil } diff --git a/modules/context/repo.go b/modules/context/repo.go index 7355dc9af2..dca0791fe3 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -456,7 +456,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc { return nil } - if redirectUserID, err := user_model.LookupUserRedirect(userName); err == nil { + if redirectUserID, err := user_model.LookupUserRedirect(ctx, userName); err == nil { RedirectToUser(ctx.Base, userName, redirectUserID) } else if user_model.IsErrUserRedirectNotExist(err) { ctx.NotFound("GetUserByName", nil) @@ -560,6 +560,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc { ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(unit_model.TypeCode) ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(unit_model.TypeIssues) ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(unit_model.TypePullRequests) + ctx.Data["CanWriteActions"] = ctx.Repo.CanWrite(unit_model.TypeActions) canSignedUserFork, err := repo_module.CanUserForkRepo(ctx, ctx.Doer, ctx.Repo.Repository) if err != nil { @@ -773,6 +774,8 @@ const ( RepoRefBlob ) +const headRefName = "HEAD" + // RepoRef handles repository reference names when the ref name is not // explicitly given func RepoRef() func(*Context) context.CancelFunc { @@ -833,6 +836,14 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string { case RepoRefBranch: ref := getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsBranchExist) if len(ref) == 0 { + + // check if ref is HEAD + parts := strings.Split(path, "/") + if parts[0] == headRefName { + repo.TreePath = strings.Join(parts[1:], "/") + return repo.Repository.DefaultBranch + } + // maybe it's a renamed branch return getRefNameFromPath(ctx, repo, path, func(s string) bool { b, exist, err := git_model.FindRenamedBranch(ctx, repo.Repository.ID, s) @@ -861,6 +872,16 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string { repo.TreePath = strings.Join(parts[1:], "/") return parts[0] } + + if len(parts) > 0 && parts[0] == headRefName { + // HEAD ref points to last default branch commit + commit, err := repo.GitRepo.GetBranchCommit(repo.Repository.DefaultBranch) + if err != nil { + return "" + } + repo.TreePath = strings.Join(parts[1:], "/") + return commit.ID.String() + } case RepoRefBlob: _, err := repo.GitRepo.GetBlob(path) if err != nil { diff --git a/modules/contexttest/context_tests.go b/modules/contexttest/context_tests.go index ea91bc5001..475756ef35 100644 --- a/modules/contexttest/context_tests.go +++ b/modules/contexttest/context_tests.go @@ -7,11 +7,13 @@ package contexttest import ( gocontext "context" "io" + "maps" "net/http" "net/http/httptest" "net/url" "strings" "testing" + "time" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" @@ -35,13 +37,24 @@ func mockRequest(t *testing.T, reqPath string) *http.Request { } requestURL, err := url.Parse(path) assert.NoError(t, err) - req := &http.Request{Method: method, URL: requestURL, Form: url.Values{}} + req := &http.Request{Method: method, URL: requestURL, Form: maps.Clone(requestURL.Query()), Header: http.Header{}} req = req.WithContext(middleware.WithContextData(req.Context())) return req } +type MockContextOption struct { + Render context.Render +} + // MockContext mock context for unit tests -func MockContext(t *testing.T, reqPath string) (*context.Context, *httptest.ResponseRecorder) { +func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*context.Context, *httptest.ResponseRecorder) { + var opt MockContextOption + if len(opts) > 0 { + opt = opts[0] + } + if opt.Render == nil { + opt.Render = &MockRender{} + } resp := httptest.NewRecorder() req := mockRequest(t, reqPath) base, baseCleanUp := context.NewBaseContext(resp, req) @@ -49,8 +62,9 @@ func MockContext(t *testing.T, reqPath string) (*context.Context, *httptest.Resp base.Data = middleware.GetContextData(req.Context()) base.Locale = &translation.MockLocale{} - ctx := context.NewWebContext(base, &MockRender{}, nil) - + ctx := context.NewWebContext(base, opt.Render, nil) + ctx.PageData = map[string]any{} + ctx.Data["PageStartTime"] = time.Now() chiCtx := chi.NewRouteContext() ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx) return ctx, resp diff --git a/modules/doctor/authorizedkeys.go b/modules/doctor/authorizedkeys.go index e4d85c4a18..050a4e7974 100644 --- a/modules/doctor/authorizedkeys.go +++ b/modules/doctor/authorizedkeys.go @@ -33,7 +33,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e return fmt.Errorf("Unable to open authorized_keys file. ERROR: %w", err) } logger.Warn("Unable to open authorized_keys. (ERROR: %v). Attempting to rewrite...", err) - if err = asymkey_model.RewriteAllPublicKeys(); err != nil { + if err = asymkey_model.RewriteAllPublicKeys(ctx); err != nil { logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err) return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err) } @@ -76,7 +76,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e return fmt.Errorf(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized-keys --fix"`) } logger.Warn("authorized_keys is out of date. Attempting rewrite...") - err = asymkey_model.RewriteAllPublicKeys() + err = asymkey_model.RewriteAllPublicKeys(ctx) if err != nil { logger.Critical("Unable to rewrite authorized_keys file. ERROR: %v", err) return fmt.Errorf("Unable to rewrite authorized_keys file. ERROR: %w", err) diff --git a/modules/doctor/dbconsistency.go b/modules/doctor/dbconsistency.go index 541fc736f4..455fbb5c76 100644 --- a/modules/doctor/dbconsistency.go +++ b/modules/doctor/dbconsistency.go @@ -6,6 +6,7 @@ package doctor import ( "context" + actions_model "code.gitea.io/gitea/models/actions" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" @@ -101,7 +102,7 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er }, // find releases without existing repository genericOrphanCheck("Orphaned Releases without existing repository", - "release", "repository", "release.repo_id=repository.id"), + "release", "repository", "`release`.repo_id=repository.id"), // find pulls without existing issues genericOrphanCheck("Orphaned PullRequests without existing issue", "pull_request", "issue", "pull_request.issue_id=issue.id"), @@ -151,6 +152,12 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er Fixer: activities_model.FixActionCreatedUnixString, FixedMessage: "Set to zero", }, + { + Name: "Action Runners without existing owner", + Counter: actions_model.CountRunnersWithoutBelongingOwner, + Fixer: actions_model.FixRunnersWithoutBelongingOwner, + FixedMessage: "Removed", + }, } // TODO: function to recalc all counters @@ -168,9 +175,9 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er // find protected branches without existing repository genericOrphanCheck("Protected Branches without existing repository", "protected_branch", "repository", "protected_branch.repo_id=repository.id"), - // find deleted branches without existing repository - genericOrphanCheck("Deleted Branches without existing repository", - "deleted_branch", "repository", "deleted_branch.repo_id=repository.id"), + // find branches without existing repository + genericOrphanCheck("Branches without existing repository", + "branch", "repository", "branch.repo_id=repository.id"), // find LFS locks without existing repository genericOrphanCheck("LFS locks without existing repository", "lfs_lock", "repository", "lfs_lock.repo_id=repository.id"), diff --git a/modules/doctor/doctor.go b/modules/doctor/doctor.go index ceee322852..559f8e06da 100644 --- a/modules/doctor/doctor.go +++ b/modules/doctor/doctor.go @@ -79,6 +79,7 @@ var Checks []*Check // RunChecks runs the doctor checks for the provided list func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) error { + SortChecks(checks) // the checks output logs by a special logger, they do not use the default logger logger := log.BaseLoggerToGeneralLogger(&doctorCheckLogger{colorize: colorize}) loggerStep := log.BaseLoggerToGeneralLogger(&doctorCheckStepLogger{colorize: colorize}) @@ -104,20 +105,23 @@ func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) err logger.Info("OK") } } - logger.Info("\nAll done.") + logger.Info("\nAll done (checks: %d).", len(checks)) return nil } // Register registers a command with the list func Register(command *Check) { Checks = append(Checks, command) - sort.SliceStable(Checks, func(i, j int) bool { - if Checks[i].Priority == Checks[j].Priority { - return Checks[i].Name < Checks[j].Name +} + +func SortChecks(checks []*Check) { + sort.SliceStable(checks, func(i, j int) bool { + if checks[i].Priority == checks[j].Priority { + return checks[i].Name < checks[j].Name } - if Checks[i].Priority == 0 { + if checks[i].Priority == 0 { return false } - return Checks[i].Priority < Checks[j].Priority + return checks[i].Priority < checks[j].Priority }) } diff --git a/modules/doctor/repository.go b/modules/doctor/repository.go new file mode 100644 index 0000000000..7de018936a --- /dev/null +++ b/modules/doctor/repository.go @@ -0,0 +1,75 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package doctor + +import ( + "context" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/storage" + repo_service "code.gitea.io/gitea/services/repository" + + "xorm.io/builder" +) + +func handleDeleteOrphanedRepos(ctx context.Context, logger log.Logger, autofix bool) error { + test := &consistencyCheck{ + Name: "Repos with no existing owner", + Counter: countOrphanedRepos, + Fixer: deleteOrphanedRepos, + FixedMessage: "Deleted all content related to orphaned repos", + } + return test.Run(ctx, logger, autofix) +} + +// countOrphanedRepos count repository where user of owner_id do not exist +func countOrphanedRepos(ctx context.Context) (int64, error) { + return db.CountOrphanedObjects(ctx, "repository", "user", "repository.owner_id=`user`.id") +} + +// deleteOrphanedRepos delete repository where user of owner_id do not exist +func deleteOrphanedRepos(ctx context.Context) (int64, error) { + if err := storage.Init(); err != nil { + return 0, err + } + + batchSize := db.MaxBatchInsertSize("repository") + e := db.GetEngine(ctx) + var deleted int64 + adminUser := &user_model.User{IsAdmin: true} + + for { + var ids []int64 + if err := e.Table("`repository`"). + Join("LEFT", "`user`", "repository.owner_id=`user`.id"). + Where(builder.IsNull{"`user`.id"}). + Select("`repository`.id").Limit(batchSize).Find(&ids); err != nil { + return deleted, err + } + + // if we don't get ids we have deleted them all + if len(ids) == 0 { + return deleted, nil + } + + for _, id := range ids { + if err := repo_service.DeleteRepositoryDirectly(ctx, adminUser, 0, id, true); err != nil { + return deleted, err + } + deleted++ + } + } +} + +func init() { + Register(&Check{ + Title: "Deleted all content related to orphaned repos", + Name: "delete-orphaned-repos", + IsDefault: false, + Run: handleDeleteOrphanedRepos, + Priority: 4, + }) +} diff --git a/modules/eventsource/manager_run.go b/modules/eventsource/manager_run.go index 2785836b89..bef1a00c5b 100644 --- a/modules/eventsource/manager_run.go +++ b/modules/eventsource/manager_run.go @@ -71,7 +71,7 @@ loop: now := timeutil.TimeStampNow().Add(-2) - uidCounts, err := activities_model.GetUIDsAndNotificationCounts(then, now) + uidCounts, err := activities_model.GetUIDsAndNotificationCounts(ctx, then, now) if err != nil { log.Error("Unable to get UIDcounts: %v", err) } diff --git a/modules/generate/generate.go b/modules/generate/generate.go index ee3c76059b..2d9a3dd902 100644 --- a/modules/generate/generate.go +++ b/modules/generate/generate.go @@ -7,6 +7,7 @@ package generate import ( "crypto/rand" "encoding/base64" + "fmt" "io" "time" @@ -38,19 +39,24 @@ func NewInternalToken() (string, error) { return internalToken, nil } -// NewJwtSecret generates a new value intended to be used for JWT secrets. -func NewJwtSecret() ([]byte, error) { - bytes := make([]byte, 32) - _, err := io.ReadFull(rand.Reader, bytes) - if err != nil { +const defaultJwtSecretLen = 32 + +// DecodeJwtSecretBase64 decodes a base64 encoded jwt secret into bytes, and check its length +func DecodeJwtSecretBase64(src string) ([]byte, error) { + encoding := base64.RawURLEncoding + decoded := make([]byte, encoding.DecodedLen(len(src))+3) + if n, err := encoding.Decode(decoded, []byte(src)); err != nil { return nil, err + } else if n != defaultJwtSecretLen { + return nil, fmt.Errorf("invalid base64 decoded length: %d, expects: %d", n, defaultJwtSecretLen) } - return bytes, nil + return decoded[:defaultJwtSecretLen], nil } -// NewJwtSecretBase64 generates a new base64 encoded value intended to be used for JWT secrets. -func NewJwtSecretBase64() ([]byte, string, error) { - bytes, err := NewJwtSecret() +// NewJwtSecretWithBase64 generates a jwt secret with its base64 encoded value intended to be used for saving into config file +func NewJwtSecretWithBase64() ([]byte, string, error) { + bytes := make([]byte, defaultJwtSecretLen) + _, err := io.ReadFull(rand.Reader, bytes) if err != nil { return nil, "", err } diff --git a/modules/generate/generate_test.go b/modules/generate/generate_test.go new file mode 100644 index 0000000000..af640a60c1 --- /dev/null +++ b/modules/generate/generate_test.go @@ -0,0 +1,34 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package generate + +import ( + "encoding/base64" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecodeJwtSecretBase64(t *testing.T) { + _, err := DecodeJwtSecretBase64("abcd") + assert.ErrorContains(t, err, "invalid base64 decoded length") + _, err = DecodeJwtSecretBase64(strings.Repeat("a", 64)) + assert.ErrorContains(t, err, "invalid base64 decoded length") + + str32 := strings.Repeat("x", 32) + encoded32 := base64.RawURLEncoding.EncodeToString([]byte(str32)) + decoded32, err := DecodeJwtSecretBase64(encoded32) + assert.NoError(t, err) + assert.Equal(t, str32, string(decoded32)) +} + +func TestNewJwtSecretWithBase64(t *testing.T) { + secret, encoded, err := NewJwtSecretWithBase64() + assert.NoError(t, err) + assert.Len(t, secret, 32) + decoded, err := DecodeJwtSecretBase64(encoded) + assert.NoError(t, err) + assert.Equal(t, secret, decoded) +} diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go index 7a44e6295c..d8f4886d06 100644 --- a/modules/git/batch_reader.go +++ b/modules/git/batch_reader.go @@ -203,16 +203,7 @@ headerLoop: } // Discard the rest of the tag - discard := size - n + 1 - for discard > math.MaxInt32 { - _, err := rd.Discard(math.MaxInt32) - if err != nil { - return id, err - } - discard -= math.MaxInt32 - } - _, err := rd.Discard(int(discard)) - return id, err + return id, DiscardFull(rd, size-n+1) } // ReadTreeID reads a tree ID from a cat-file --batch stream, throwing away the rest of the stream. @@ -238,16 +229,7 @@ headerLoop: } // Discard the rest of the commit - discard := size - n + 1 - for discard > math.MaxInt32 { - _, err := rd.Discard(math.MaxInt32) - if err != nil { - return id, err - } - discard -= math.MaxInt32 - } - _, err := rd.Discard(int(discard)) - return id, err + return id, DiscardFull(rd, size-n+1) } // git tree files are a list: @@ -345,3 +327,21 @@ func init() { _, filename, _, _ := runtime.Caller(0) callerPrefix = strings.TrimSuffix(filename, "modules/git/batch_reader.go") } + +func DiscardFull(rd *bufio.Reader, discard int64) error { + if discard > math.MaxInt32 { + n, err := rd.Discard(math.MaxInt32) + discard -= int64(n) + if err != nil { + return err + } + } + for discard > 0 { + n, err := rd.Discard(int(discard)) + discard -= int64(n) + if err != nil { + return err + } + } + return nil +} diff --git a/modules/git/blame.go b/modules/git/blame.go index 6728a6bed8..93c7f184fa 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -11,6 +11,7 @@ import ( "io" "os" "regexp" + "strings" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" @@ -18,8 +19,10 @@ import ( // BlamePart represents block of blame - continuous lines with one sha type BlamePart struct { - Sha string - Lines []string + Sha string + Lines []string + PreviousSha string + PreviousPath string } // BlameReader returns part of file blame one by one @@ -43,30 +46,38 @@ func (r *BlameReader) NextPart() (*BlamePart, error) { var blamePart *BlamePart if r.lastSha != nil { - blamePart = &BlamePart{*r.lastSha, make([]string, 0)} + blamePart = &BlamePart{ + Sha: *r.lastSha, + Lines: make([]string, 0), + } } - var line []byte + var lineBytes []byte var isPrefix bool var err error for err != io.EOF { - line, isPrefix, err = r.bufferedReader.ReadLine() + lineBytes, isPrefix, err = r.bufferedReader.ReadLine() if err != nil && err != io.EOF { return blamePart, err } - if len(line) == 0 { + if len(lineBytes) == 0 { // isPrefix will be false continue } - lines := shaLineRegex.FindSubmatch(line) + line := string(lineBytes) + + lines := shaLineRegex.FindStringSubmatch(line) if lines != nil { - sha1 := string(lines[1]) + sha1 := lines[1] if blamePart == nil { - blamePart = &BlamePart{sha1, make([]string, 0)} + blamePart = &BlamePart{ + Sha: sha1, + Lines: make([]string, 0), + } } if blamePart.Sha != sha1 { @@ -81,9 +92,11 @@ func (r *BlameReader) NextPart() (*BlamePart, error) { return blamePart, nil } } else if line[0] == '\t' { - code := line[1:] - - blamePart.Lines = append(blamePart.Lines, string(code)) + blamePart.Lines = append(blamePart.Lines, line[1:]) + } else if strings.HasPrefix(line, "previous ") { + parts := strings.SplitN(line[len("previous "):], " ", 2) + blamePart.PreviousSha = parts[0] + blamePart.PreviousPath = parts[1] } // need to munch to end of line... diff --git a/modules/git/blame_test.go b/modules/git/blame_test.go index 013350ac2f..040f4e822d 100644 --- a/modules/git/blame_test.go +++ b/modules/git/blame_test.go @@ -24,15 +24,17 @@ func TestReadingBlameOutput(t *testing.T) { parts := []*BlamePart{ { - "72866af952e98d02a73003501836074b286a78f6", - []string{ + Sha: "72866af952e98d02a73003501836074b286a78f6", + Lines: []string{ "# test_repo", "Test repository for testing migration from github to gitea", }, }, { - "f32b0a9dfd09a60f616f29158f772cedd89942d2", - []string{"", "Do not make any changes to this repo it is used for unit testing"}, + Sha: "f32b0a9dfd09a60f616f29158f772cedd89942d2", + Lines: []string{"", "Do not make any changes to this repo it is used for unit testing"}, + PreviousSha: "72866af952e98d02a73003501836074b286a78f6", + PreviousPath: "README.md", }, } @@ -64,16 +66,18 @@ func TestReadingBlameOutput(t *testing.T) { full := []*BlamePart{ { - "af7486bd54cfc39eea97207ca666aa69c9d6df93", - []string{"line", "line"}, + Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93", + Lines: []string{"line", "line"}, }, { - "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", - []string{"changed line"}, + Sha: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", + Lines: []string{"changed line"}, + PreviousSha: "af7486bd54cfc39eea97207ca666aa69c9d6df93", + PreviousPath: "blame.txt", }, { - "af7486bd54cfc39eea97207ca666aa69c9d6df93", - []string{"line", "line", ""}, + Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93", + Lines: []string{"line", "line", ""}, }, } @@ -89,8 +93,8 @@ func TestReadingBlameOutput(t *testing.T) { Bypass: false, Parts: []*BlamePart{ { - "af7486bd54cfc39eea97207ca666aa69c9d6df93", - []string{"line", "line", "changed line", "line", "line", ""}, + Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93", + Lines: []string{"line", "line", "changed line", "line", "line", ""}, }, }, }, diff --git a/modules/git/blob_nogogit.go b/modules/git/blob_nogogit.go index 511332eb50..cc3109b50f 100644 --- a/modules/git/blob_nogogit.go +++ b/modules/git/blob_nogogit.go @@ -9,7 +9,6 @@ import ( "bufio" "bytes" "io" - "math" "code.gitea.io/gitea/modules/log" ) @@ -103,26 +102,17 @@ func (b *blobReader) Read(p []byte) (n int, err error) { // Close implements io.Closer func (b *blobReader) Close() error { - defer b.cancel() - if b.n > 0 { - for b.n > math.MaxInt32 { - n, err := b.rd.Discard(math.MaxInt32) - b.n -= int64(n) - if err != nil { - return err - } - b.n -= math.MaxInt32 - } - n, err := b.rd.Discard(int(b.n)) - b.n -= int64(n) - if err != nil { - return err - } + if b.rd == nil { + return nil } - if b.n == 0 { - _, err := b.rd.Discard(1) - b.n-- + + defer b.cancel() + + if err := DiscardFull(b.rd, b.n+1); err != nil { return err } + + b.rd = nil + return nil } diff --git a/modules/git/command.go b/modules/git/command.go index f095bb18be..9305ef6f92 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -14,7 +14,6 @@ import ( "os/exec" "strings" "time" - "unsafe" "code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions "code.gitea.io/gitea/modules/log" @@ -389,15 +388,11 @@ func (r *runStdError) IsExitCode(code int) bool { return false } -func bytesToString(b []byte) string { - return *(*string)(unsafe.Pointer(&b)) // that's what Golang's strings.Builder.String() does (go/src/strings/builder.go) -} - // RunStdString runs the command with options and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr). func (c *Command) RunStdString(opts *RunOpts) (stdout, stderr string, runErr RunStdError) { stdoutBytes, stderrBytes, err := c.RunStdBytes(opts) - stdout = bytesToString(stdoutBytes) - stderr = bytesToString(stderrBytes) + stdout = util.UnsafeBytesToString(stdoutBytes) + stderr = util.UnsafeBytesToString(stderrBytes) if err != nil { return stdout, stderr, &runStdError{err: err, stderr: stderr} } @@ -432,7 +427,7 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS err := c.Run(newOpts) stderr = stderrBuf.Bytes() if err != nil { - return nil, stderr, &runStdError{err: err, stderr: bytesToString(stderr)} + return nil, stderr, &runStdError{err: err, stderr: util.UnsafeBytesToString(stderr)} } // even if there is no err, there could still be some stderr output return stdoutBuf.Bytes(), stderr, nil diff --git a/modules/git/commit.go b/modules/git/commit.go index b09be25ba0..4ff8f6148f 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -43,8 +43,9 @@ func (c *Commit) Message() string { } // Summary returns first line of commit message. +// The string is forced to be valid UTF8 func (c *Commit) Summary() string { - return strings.Split(strings.TrimSpace(c.CommitMessage), "\n")[0] + return strings.ToValidUTF8(strings.Split(strings.TrimSpace(c.CommitMessage), "\n")[0], "?") } // ParentID returns oid of n-th parent (0-based index). diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go index e469d2cab6..a5d18694f7 100644 --- a/modules/git/commit_info_nogogit.go +++ b/modules/git/commit_info_nogogit.go @@ -151,6 +151,9 @@ func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string, return nil, err } if typ != "commit" { + if err := DiscardFull(batchReader, size+1); err != nil { + return nil, err + } return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID) } c, err = CommitFromReader(commit.repo, MustIDFromString(commitID), io.LimitReader(batchReader, size)) diff --git a/modules/git/foreachref/format_test.go b/modules/git/foreachref/format_test.go index 392a95a4e6..8ff239323c 100644 --- a/modules/git/foreachref/format_test.go +++ b/modules/git/foreachref/format_test.go @@ -49,9 +49,9 @@ func TestFormat_Flag(t *testing.T) { { name: "multiple fields", - givenFormat: foreachref.NewFormat("refname:short", "objecttype", "objectname"), + givenFormat: foreachref.NewFormat("refname:lstrip=2", "objecttype", "objectname"), - wantFlag: "refname:short %(refname:short)%00objecttype %(objecttype)%00objectname %(objectname)%00%00", + wantFlag: "refname:lstrip=2 %(refname:lstrip=2)%00objecttype %(objecttype)%00objectname %(objectname)%00%00", }, } diff --git a/modules/git/git.go b/modules/git/git.go index 12d2f94e51..0095935479 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -39,36 +39,37 @@ var ( gitVersion *version.Version ) -// loadGitVersion returns current Git version from shell. Internal usage only. -func loadGitVersion() (*version.Version, error) { +// loadGitVersion tries to get the current git version and stores it into a global variable +func loadGitVersion() error { // doesn't need RWMutex because it's executed by Init() if gitVersion != nil { - return gitVersion, nil + return nil } stdout, _, runErr := NewCommand(DefaultContext, "version").RunStdString(nil) if runErr != nil { - return nil, runErr + return runErr } - fields := strings.Fields(stdout) + ver, err := parseGitVersionLine(strings.TrimSpace(stdout)) + if err == nil { + gitVersion = ver + } + return err +} + +func parseGitVersionLine(s string) (*version.Version, error) { + fields := strings.Fields(s) if len(fields) < 3 { - return nil, fmt.Errorf("invalid git version output: %s", stdout) + return nil, fmt.Errorf("invalid git version: %q", s) } - var versionString string - - // Handle special case on Windows. - i := strings.Index(fields[2], "windows") - if i >= 1 { - versionString = fields[2][:i-1] - } else { - versionString = fields[2] + // version string is like: "git version 2.29.3" or "git version 2.29.3.windows.1" + versionString := fields[2] + if pos := strings.Index(versionString, "windows"); pos >= 1 { + versionString = versionString[:pos-1] } - - var err error - gitVersion, err = version.NewVersion(versionString) - return gitVersion, err + return version.NewVersion(versionString) } // SetExecutablePath changes the path of git executable and checks the file permission and version. @@ -83,8 +84,7 @@ func SetExecutablePath(path string) error { } GitExecutable = absPath - _, err = loadGitVersion() - if err != nil { + if err = loadGitVersion(); err != nil { return fmt.Errorf("unable to load git version: %w", err) } @@ -105,6 +105,9 @@ func SetExecutablePath(path string) error { return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", gitVersion.Original(), RequiredVersion, moreHint) } + if err = checkGitVersionCompatibility(gitVersion); err != nil { + log.Error("installed git version %s has a known compatibility issue with Gitea: %s, please downgrade (or upgrade) your git", gitVersion.String(), err.Error()) + } return nil } @@ -166,10 +169,6 @@ func InitSimple(ctx context.Context) error { // InitFull initializes git module with version check and change global variables, sync gitconfig. // It should only be called once at the beginning of the program initialization (TestMain/GlobalInitInstalled) as this code makes unsynchronized changes to variables. func InitFull(ctx context.Context) (err error) { - if err = checkInit(); err != nil { - return err - } - if err = InitSimple(ctx); err != nil { return err } @@ -260,19 +259,18 @@ func syncGitConfig() (err error) { } } - // Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user - // however, some docker users and samba users find it difficult to configure their systems so that Gitea's git repositories are owned by the Gitea user. (Possibly Windows Service users - but ownership in this case should really be set correctly on the filesystem.) - // see issue: https://github.com/go-gitea/gitea/issues/19455 - // Fundamentally the problem lies with the uid-gid-mapping mechanism for filesystems in docker on windows (and to a lesser extent samba). - // Docker's configuration mechanism for local filesystems provides no way of setting this mapping and although there is a mechanism for setting this uid through using cifs mounting it is complicated and essentially undocumented - // Thus the owner uid/gid for files on these filesystems will be marked as root. + // Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user. + // However, some docker users and samba users find it difficult to configure their systems correctly, + // so that Gitea's git repositories are owned by the Gitea user. + // (Possibly Windows Service users - but ownership in this case should really be set correctly on the filesystem.) + // See issue: https://github.com/go-gitea/gitea/issues/19455 // As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea, // it is now safe to set "safe.directory=*" for internal usage only. - // Please note: the wildcard "*" is only supported by Git 2.30.4/2.31.3/2.32.2/2.33.3/2.34.3/2.35.3/2.36 and later - // Although only supported by Git 2.30.4/2.31.3/2.32.2/2.33.3/2.34.3/2.35.3/2.36 and later - this setting is tolerated by earlier versions + // Although this setting is only supported by some new git versions, it is also tolerated by earlier versions if err := configAddNonExist("safe.directory", "*"); err != nil { return err } + if runtime.GOOS == "windows" { if err := configSet("core.longpaths", "true"); err != nil { return err @@ -305,8 +303,8 @@ func syncGitConfig() (err error) { // CheckGitVersionAtLeast check git version is at least the constraint version func CheckGitVersionAtLeast(atLeast string) error { - if _, err := loadGitVersion(); err != nil { - return err + if gitVersion == nil { + panic("git module is not initialized") // it shouldn't happen } atLeastVersion, err := version.NewVersion(atLeast) if err != nil { @@ -318,6 +316,21 @@ func CheckGitVersionAtLeast(atLeast string) error { return nil } +func checkGitVersionCompatibility(gitVer *version.Version) error { + badVersions := []struct { + Version *version.Version + Reason string + }{ + {version.Must(version.NewVersion("2.43.1")), "regression bug of GIT_FLUSH"}, + } + for _, bad := range badVersions { + if gitVer.Equal(bad.Version) { + return errors.New(bad.Reason) + } + } + return nil +} + func configSet(key, value string) error { stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) if err != nil && !err.IsExitCode(1) { diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 37ab669ea4..fc92bebe04 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + "github.com/hashicorp/go-version" "github.com/stretchr/testify/assert" ) @@ -93,3 +94,25 @@ func TestSyncConfig(t *testing.T) { assert.True(t, gitConfigContains("[sync-test]")) assert.True(t, gitConfigContains("cfg-key-a = CfgValA")) } + +func TestParseGitVersion(t *testing.T) { + v, err := parseGitVersionLine("git version 2.29.3") + assert.NoError(t, err) + assert.Equal(t, "2.29.3", v.String()) + + v, err = parseGitVersionLine("git version 2.29.3.windows.1") + assert.NoError(t, err) + assert.Equal(t, "2.29.3", v.String()) + + _, err = parseGitVersionLine("git version") + assert.Error(t, err) + + _, err = parseGitVersionLine("git version windows") + assert.Error(t, err) +} + +func TestCheckGitVersionCompatibility(t *testing.T) { + assert.NoError(t, checkGitVersionCompatibility(version.Must(version.NewVersion("2.43.0")))) + assert.ErrorContains(t, checkGitVersionCompatibility(version.Must(version.NewVersion("2.43.1"))), "regression bug of GIT_FLUSH") + assert.NoError(t, checkGitVersionCompatibility(version.Must(version.NewVersion("2.43.2")))) +} diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go index 49390f7c00..2ec6031540 100644 --- a/modules/git/pipeline/lfs_nogogit.go +++ b/modules/git/pipeline/lfs_nogogit.go @@ -170,6 +170,10 @@ func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) { } else { break commitReadingLoop } + default: + if err := git.DiscardFull(batchReader, size+1); err != nil { + return nil, err + } } } } diff --git a/modules/git/repo.go b/modules/git/repo.go index 5681d39c3b..fe1613b666 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -86,7 +86,8 @@ func (repo *Repository) IsEmpty() (bool, error) { Stdout: &output, Stderr: &errbuf, }); err != nil { - if err.Error() == "exit status 1" && errbuf.String() == "" { + if (err.Error() == "exit status 1" && strings.TrimSpace(errbuf.String()) == "") || err.Error() == "exit status 129" { + // git 2.11 exits with 129 if the repo is empty return true, nil } return true, fmt.Errorf("check empty: %w - %s", err, errbuf.String()) @@ -235,7 +236,7 @@ func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error return time.Time{}, err } commitTime := strings.TrimSpace(stdout) - return time.Parse(GitTimeLayout, commitTime) + return time.Parse("Mon Jan _2 15:04:05 2006 -0700", commitTime) } // DivergeObject represents commit count diverging commits @@ -247,7 +248,7 @@ type DivergeObject struct { // GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch func GetDivergingCommits(ctx context.Context, repoPath, baseBranch, targetBranch string) (do DivergeObject, err error) { cmd := NewCommand(ctx, "rev-list", "--count", "--left-right"). - AddDynamicArguments(baseBranch + "..." + targetBranch) + AddDynamicArguments(baseBranch + "..." + targetBranch).AddArguments("--") stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return do, err diff --git a/modules/git/repo_archive.go b/modules/git/repo_archive.go index 2b45a50f19..1bf1aa41b9 100644 --- a/modules/git/repo_archive.go +++ b/modules/git/repo_archive.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "io" + "os" "path/filepath" "strings" ) @@ -62,11 +63,15 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t cmd.AddOptionFormat("--format=%s", format.String()) cmd.AddDynamicArguments(commitID) + // Avoid LFS hooks getting installed because of /etc/gitconfig, which can break pull requests. + env := append(os.Environ(), "GIT_CONFIG_NOSYSTEM=1") + var stderr strings.Builder err := cmd.Run(&RunOpts{ Dir: repo.Path, Stdout: target, Stderr: &stderr, + Env: env, }) if err != nil { return ConcatenateError(err, stderr.String()) diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go index 2b34f117f7..2b58bd9f47 100644 --- a/modules/git/repo_attribute.go +++ b/modules/git/repo_attribute.go @@ -11,6 +11,8 @@ import ( "os" "code.gitea.io/gitea/modules/log" + + "github.com/hashicorp/go-version" ) // CheckAttributeOpts represents the possible options to CheckAttribute @@ -133,7 +135,14 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error { c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree) } - c.env = append(c.env, "GIT_FLUSH=1") + if gitVersion.Equal(version.Must(version.NewVersion("2.43.1"))) { + // https://github.com/go-gitea/gitea/issues/29141 gitea hanging with git 2.43.1 #29141 + // https://lore.kernel.org/git/CABn0oJvg3M_kBW-u=j3QhKnO=6QOzk-YFTgonYw_UvFS1NTX4g@mail.gmail.com/ + // git 2.43.1 has a bug: the GIT_FLUSH polarity is flipped + c.env = append(c.env, "GIT_FLUSH=0") + } else { + c.env = append(c.env, "GIT_FLUSH=1") + } c.cmd.AddDynamicArguments(c.Attributes...) diff --git a/modules/git/repo_base_gogit.go b/modules/git/repo_base_gogit.go index ef59ead900..7a842cd1b6 100644 --- a/modules/git/repo_base_gogit.go +++ b/modules/git/repo_base_gogit.go @@ -14,6 +14,7 @@ import ( gitealog "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/osfs" gogit "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/cache" @@ -56,7 +57,15 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) { return nil, err } } - storage := filesystem.NewStorageWithOptions(fs, cache.NewObjectLRUDefault(), filesystem.Options{KeepDescriptors: true, LargeObjectThreshold: setting.Git.LargeObjectThreshold}) + // the "clone --shared" repo doesn't work well with go-git AlternativeFS, https://github.com/go-git/go-git/issues/1006 + // so use "/" for AlternatesFS, I guess it is the same behavior as current nogogit (no limitation or check for the "objects/info/alternates" paths), trust the "clone" command executed by the server. + var altFs billy.Filesystem + if setting.IsWindows { + altFs = osfs.New(filepath.VolumeName(setting.RepoRootPath) + "\\") // TODO: does it really work for Windows? Need some time to check. + } else { + altFs = osfs.New("/") + } + storage := filesystem.NewStorageWithOptions(fs, cache.NewObjectLRUDefault(), filesystem.Options{KeepDescriptors: true, LargeObjectThreshold: setting.Git.LargeObjectThreshold, AlternatesFS: altFs}) gogitRepo, err := gogit.Open(storage, fs) if err != nil { return nil, err diff --git a/modules/git/repo_base_nogogit.go b/modules/git/repo_base_nogogit.go index 414e4eb1a8..2415982fc6 100644 --- a/modules/git/repo_base_nogogit.go +++ b/modules/git/repo_base_nogogit.go @@ -23,10 +23,12 @@ type Repository struct { gpgSettings *GPGSettings + batchInUse bool batchCancel context.CancelFunc batchReader *bufio.Reader batchWriter WriteCloserError + checkInUse bool checkCancel context.CancelFunc checkReader *bufio.Reader checkWriter WriteCloserError @@ -68,23 +70,28 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) { // CatFileBatch obtains a CatFileBatch for this repository func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) { - if repo.batchCancel == nil || repo.batchReader.Buffered() > 0 { + if repo.batchCancel == nil || repo.batchInUse { log.Debug("Opening temporary cat file batch for: %s", repo.Path) return CatFileBatch(ctx, repo.Path) } - return repo.batchWriter, repo.batchReader, func() {} + repo.batchInUse = true + return repo.batchWriter, repo.batchReader, func() { + repo.batchInUse = false + } } // CatFileBatchCheck obtains a CatFileBatchCheck for this repository func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) { - if repo.checkCancel == nil || repo.checkReader.Buffered() > 0 { - log.Debug("Opening temporary cat file batch-check: %s", repo.Path) + if repo.checkCancel == nil || repo.checkInUse { + log.Debug("Opening temporary cat file batch-check for: %s", repo.Path) return CatFileBatchCheck(ctx, repo.Path) } - return repo.checkWriter, repo.checkReader, func() {} + repo.checkInUse = true + return repo.checkWriter, repo.checkReader, func() { + repo.checkInUse = false + } } -// Close this repository, in particular close the underlying gogitStorage if this is not nil func (repo *Repository) Close() (err error) { if repo == nil { return nil @@ -94,12 +101,14 @@ func (repo *Repository) Close() (err error) { repo.batchReader = nil repo.batchWriter = nil repo.batchCancel = nil + repo.batchInUse = false } if repo.checkCancel != nil { repo.checkCancel() repo.checkCancel = nil repo.checkReader = nil repo.checkWriter = nil + repo.checkInUse = false } repo.LastCommitCache = nil repo.tagCache = nil diff --git a/modules/git/repo_branch_gogit.go b/modules/git/repo_branch_gogit.go index f9896a7a09..1c0d9a18aa 100644 --- a/modules/git/repo_branch_gogit.go +++ b/modules/git/repo_branch_gogit.go @@ -8,6 +8,7 @@ package git import ( "context" + "sort" "strings" "github.com/go-git/go-git/v5/plumbing" @@ -52,32 +53,46 @@ func (repo *Repository) IsBranchExist(name string) bool { // GetBranches returns branches from the repository, skipping "skip" initial branches and // returning at most "limit" branches, or all branches if "limit" is 0. +// Branches are returned with sort of `-commiterdate` as the nogogit +// implementation. This requires full fetch, sort and then the +// skip/limit applies later as gogit returns in undefined order. func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { - var branchNames []string + type BranchData struct { + name string + committerDate int64 + } + var branchData []BranchData - branches, err := repo.gogitRepo.Branches() + branchIter, err := repo.gogitRepo.Branches() if err != nil { return nil, 0, err } - i := 0 - count := 0 - _ = branches.ForEach(func(branch *plumbing.Reference) error { - count++ - if i < skip { - i++ - return nil - } else if limit != 0 && count > skip+limit { + _ = branchIter.ForEach(func(branch *plumbing.Reference) error { + obj, err := repo.gogitRepo.CommitObject(branch.Hash()) + if err != nil { + // skip branch if can't find commit return nil } - branchNames = append(branchNames, strings.TrimPrefix(branch.Name().String(), BranchPrefix)) + branchData = append(branchData, BranchData{strings.TrimPrefix(branch.Name().String(), BranchPrefix), obj.Committer.When.Unix()}) return nil }) - // TODO: Sort? + sort.Slice(branchData, func(i, j int) bool { + return !(branchData[i].committerDate < branchData[j].committerDate) + }) - return branchNames, count, nil + var branchNames []string + maxPos := len(branchData) + if limit > 0 { + maxPos = min(skip+limit, maxPos) + } + for i := skip; i < maxPos; i++ { + branchNames = append(branchNames, branchData[i].name) + } + + return branchNames, len(branchData), nil } // WalkReferences walks all the references from the repository diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 6fc3063629..f69ecb88bc 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -148,6 +148,9 @@ func (repo *Repository) searchCommits(id SHA1, opts SearchCommitsOptions) ([]*Co cmd.AddArguments("--all") } + // interpret search string keywords as string instead of regex + cmd.AddArguments("-F") + // add remaining keywords from search string // note this is done only for command created above if len(opts.Keywords) > 0 { diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go index d5eb723100..73e441b7d9 100644 --- a/modules/git/repo_commit_nogogit.go +++ b/modules/git/repo_commit_nogogit.go @@ -121,8 +121,7 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co return commit, nil default: log.Debug("Unknown typ: %s", typ) - _, err = rd.Discard(int(size) + 1) - if err != nil { + if err := DiscardFull(rd, size+1); err != nil { return nil, err } return nil, ErrNotExist{ diff --git a/modules/git/repo_commitgraph_gogit.go b/modules/git/repo_commitgraph_gogit.go index 8e92c30e54..d3182f15c6 100644 --- a/modules/git/repo_commitgraph_gogit.go +++ b/modules/git/repo_commitgraph_gogit.go @@ -12,7 +12,7 @@ import ( gitealog "code.gitea.io/gitea/modules/log" - "github.com/go-git/go-git/v5/plumbing/format/commitgraph" + commitgraph "github.com/go-git/go-git/v5/plumbing/format/commitgraph/v2" cgobject "github.com/go-git/go-git/v5/plumbing/object/commitgraph" ) diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/repo_language_stats_nogogit.go index 1d94ad6c00..d68d7d210a 100644 --- a/modules/git/repo_language_stats_nogogit.go +++ b/modules/git/repo_language_stats_nogogit.go @@ -6,10 +6,8 @@ package git import ( - "bufio" "bytes" "io" - "math" "strings" "code.gitea.io/gitea/modules/analyze" @@ -168,8 +166,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err return nil, err } content = contentBuf.Bytes() - err = discardFull(batchReader, discard) - if err != nil { + if err := DiscardFull(batchReader, discard); err != nil { return nil, err } } @@ -212,21 +209,3 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err return mergeLanguageStats(sizes), nil } - -func discardFull(rd *bufio.Reader, discard int64) error { - if discard > math.MaxInt32 { - n, err := rd.Discard(math.MaxInt32) - discard -= int64(n) - if err != nil { - return err - } - } - for discard > 0 { - n, err := rd.Discard(int(discard)) - discard -= int64(n) - if err != nil { - return err - } - } - return nil -} diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index ae877f0211..e8c5ce6fb8 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -112,7 +112,9 @@ func (repo *Repository) GetTagWithID(idStr, name string) (*Tag, error) { // GetTagInfos returns all tag infos of the repository. func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) { - forEachRefFmt := foreachref.NewFormat("objecttype", "refname:short", "object", "objectname", "creator", "contents", "contents:signature") + // Generally, refname:short should be equal to refname:lstrip=2 except core.warnAmbiguousRefs is used to select the strict abbreviation mode. + // https://git-scm.com/docs/git-for-each-ref#Documentation/git-for-each-ref.txt-refname + forEachRefFmt := foreachref.NewFormat("objecttype", "refname:lstrip=2", "object", "objectname", "creator", "contents", "contents:signature") stdoutReader, stdoutWriter := io.Pipe() defer stdoutReader.Close() @@ -162,7 +164,7 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) { func parseTagRef(ref map[string]string) (tag *Tag, err error) { tag = &Tag{ Type: ref["objecttype"], - Name: ref["refname:short"], + Name: ref["refname:lstrip=2"], } tag.ID, err = NewIDFromString(ref["objectname"]) @@ -181,11 +183,7 @@ func parseTagRef(ref map[string]string) (tag *Tag, err error) { } } - tag.Tagger, err = newSignatureFromCommitline([]byte(ref["creator"])) - if err != nil { - return nil, fmt.Errorf("parse tagger: %w", err) - } - + tag.Tagger = parseSignatureFromCommitLine(ref["creator"]) tag.Message = ref["contents"] // strip PGP signature if present in contents field pgpStart := strings.Index(tag.Message, beginpgp) diff --git a/modules/git/repo_tag_nogogit.go b/modules/git/repo_tag_nogogit.go index 9080ffcfd7..19023741e3 100644 --- a/modules/git/repo_tag_nogogit.go +++ b/modules/git/repo_tag_nogogit.go @@ -103,6 +103,9 @@ func (repo *Repository) getTag(tagID SHA1, name string) (*Tag, error) { return nil, err } if typ != "tag" { + if err := DiscardFull(rd, size+1); err != nil { + return nil, err + } return nil, ErrNotExist{ID: tagID.String()} } diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index fb6a7cdd1a..8918b37bbc 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -209,8 +209,8 @@ func TestRepository_parseTagRef(t *testing.T) { name: "lightweight tag", givenRef: map[string]string{ - "objecttype": "commit", - "refname:short": "v1.9.1", + "objecttype": "commit", + "refname:lstrip=2": "v1.9.1", // object will be empty for lightweight tags "object": "", "objectname": "ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889", @@ -228,7 +228,7 @@ func TestRepository_parseTagRef(t *testing.T) { ID: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), Object: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), Type: "commit", - Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"), + Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"), Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", Signature: nil, }, @@ -238,8 +238,8 @@ func TestRepository_parseTagRef(t *testing.T) { name: "annotated tag", givenRef: map[string]string{ - "objecttype": "tag", - "refname:short": "v0.0.1", + "objecttype": "tag", + "refname:lstrip=2": "v0.0.1", // object will refer to commit hash for annotated tag "object": "3325fd8a973321fd59455492976c042dde3fd1ca", "objectname": "8c68a1f06fc59c655b7e3905b159d761e91c53c9", @@ -257,7 +257,7 @@ func TestRepository_parseTagRef(t *testing.T) { ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), Type: "tag", - Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"), + Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"), Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", Signature: nil, }, @@ -267,11 +267,11 @@ func TestRepository_parseTagRef(t *testing.T) { name: "annotated tag with signature", givenRef: map[string]string{ - "objecttype": "tag", - "refname:short": "v0.0.1", - "object": "3325fd8a973321fd59455492976c042dde3fd1ca", - "objectname": "8c68a1f06fc59c655b7e3905b159d761e91c53c9", - "creator": "Foo Bar 1565789218 +0300", + "objecttype": "tag", + "refname:lstrip=2": "v0.0.1", + "object": "3325fd8a973321fd59455492976c042dde3fd1ca", + "objectname": "8c68a1f06fc59c655b7e3905b159d761e91c53c9", + "creator": "Foo Bar 1565789218 +0300", "contents": `Add changelog of v1.9.1 (#7859) * add changelog of v1.9.1 @@ -315,7 +315,7 @@ qbHDASXl ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), Type: "tag", - Tagger: parseAuthorLine(t, "Foo Bar 1565789218 +0300"), + Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"), Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md", Signature: &CommitGPGSignature{ Signature: `-----BEGIN PGP SIGNATURE----- @@ -364,14 +364,3 @@ Add changelog of v1.9.1 (#7859) }) } } - -func parseAuthorLine(t *testing.T, committer string) *Signature { - t.Helper() - - sig, err := newSignatureFromCommitline([]byte(committer)) - if err != nil { - t.Fatalf("parse author line '%s': %v", committer, err) - } - - return sig -} diff --git a/modules/git/repo_tree_nogogit.go b/modules/git/repo_tree_nogogit.go index 4fd77df2b8..3ec6fcae92 100644 --- a/modules/git/repo_tree_nogogit.go +++ b/modules/git/repo_tree_nogogit.go @@ -58,6 +58,9 @@ func (repo *Repository) getTree(id SHA1) (*Tree, error) { tree.entriesParsed = true return tree, nil default: + if err := DiscardFull(rd, size+1); err != nil { + return nil, err + } return nil, ErrNotExist{ ID: id.String(), } diff --git a/modules/git/signature.go b/modules/git/signature.go index b5b17f23b0..2ef6dfcf81 100644 --- a/modules/git/signature.go +++ b/modules/git/signature.go @@ -4,7 +4,46 @@ package git -const ( - // GitTimeLayout is the (default) time layout used by git. - GitTimeLayout = "Mon Jan _2 15:04:05 2006 -0700" +import ( + "strconv" + "strings" + "time" + + "code.gitea.io/gitea/modules/log" ) + +// Helper to get a signature from the commit line, which looks like: +// +// full name 1378823654 +0200 +// +// Haven't found the official reference for the standard format yet. +// This function never fails, if the "line" can't be parsed, it returns a default Signature with "zero" time. +func parseSignatureFromCommitLine(line string) *Signature { + sig := &Signature{} + s1, sx, ok1 := strings.Cut(line, " <") + s2, s3, ok2 := strings.Cut(sx, "> ") + if !ok1 || !ok2 { + sig.Name = line + return sig + } + sig.Name, sig.Email = s1, s2 + + if strings.Count(s3, " ") == 1 { + ts, tz, _ := strings.Cut(s3, " ") + seconds, _ := strconv.ParseInt(ts, 10, 64) + if tzTime, err := time.Parse("-0700", tz); err == nil { + sig.When = time.Unix(seconds, 0).In(tzTime.Location()) + } + } else { + // the old gitea code tried to parse the date in a few different formats, but it's not clear why. + // according to public document, only the standard format "timestamp timezone" could be found, so drop other formats. + log.Error("suspicious commit line format: %q", line) + for _, fmt := range []string{"Mon Jan _2 15:04:05 2006 -0700"} { + if t, err := time.Parse(fmt, s3); err == nil { + sig.When = t + break + } + } + } + return sig +} diff --git a/modules/git/signature_gogit.go b/modules/git/signature_gogit.go index c984ad6e20..1fc6aabceb 100644 --- a/modules/git/signature_gogit.go +++ b/modules/git/signature_gogit.go @@ -7,52 +7,8 @@ package git import ( - "bytes" - "strconv" - "strings" - "time" - "github.com/go-git/go-git/v5/plumbing/object" ) // Signature represents the Author or Committer information. type Signature = object.Signature - -// Helper to get a signature from the commit line, which looks like these: -// -// author Patrick Gundlach 1378823654 +0200 -// author Patrick Gundlach Thu, 07 Apr 2005 22:13:13 +0200 -// -// but without the "author " at the beginning (this method should) -// be used for author and committer. -// -// FIXME: include timezone for timestamp! -func newSignatureFromCommitline(line []byte) (_ *Signature, err error) { - sig := new(Signature) - emailStart := bytes.IndexByte(line, '<') - if emailStart > 0 { // Empty name has already occurred, even if it shouldn't - sig.Name = strings.TrimSpace(string(line[:emailStart-1])) - } - emailEnd := bytes.IndexByte(line, '>') - sig.Email = string(line[emailStart+1 : emailEnd]) - - // Check date format. - if len(line) > emailEnd+2 { - firstChar := line[emailEnd+2] - if firstChar >= 48 && firstChar <= 57 { - timestop := bytes.IndexByte(line[emailEnd+2:], ' ') - timestring := string(line[emailEnd+2 : emailEnd+2+timestop]) - seconds, _ := strconv.ParseInt(timestring, 10, 64) - sig.When = time.Unix(seconds, 0) - } else { - sig.When, err = time.Parse(GitTimeLayout, string(line[emailEnd+2:])) - if err != nil { - return nil, err - } - } - } else { - // Fall back to unix 0 time - sig.When = time.Unix(0, 0) - } - return sig, nil -} diff --git a/modules/git/signature_nogogit.go b/modules/git/signature_nogogit.go index 25277f99d5..0d19c0abdc 100644 --- a/modules/git/signature_nogogit.go +++ b/modules/git/signature_nogogit.go @@ -7,21 +7,17 @@ package git import ( - "bytes" "fmt" - "strconv" - "strings" "time" + + "code.gitea.io/gitea/modules/util" ) -// Signature represents the Author or Committer information. +// Signature represents the Author, Committer or Tagger information. type Signature struct { - // Name represents a person name. It is an arbitrary string. - Name string - // Email is an email, but it cannot be assumed to be well-formed. - Email string - // When is the timestamp of the signature. - When time.Time + Name string // the committer name, it can be anything + Email string // the committer email, it can be anything + When time.Time // the timestamp of the signature } func (s *Signature) String() string { @@ -30,71 +26,5 @@ func (s *Signature) String() string { // Decode decodes a byte array representing a signature to signature func (s *Signature) Decode(b []byte) { - sig, _ := newSignatureFromCommitline(b) - s.Email = sig.Email - s.Name = sig.Name - s.When = sig.When -} - -// Helper to get a signature from the commit line, which looks like these: -// -// author Patrick Gundlach 1378823654 +0200 -// author Patrick Gundlach Thu, 07 Apr 2005 22:13:13 +0200 -// -// but without the "author " at the beginning (this method should) -// be used for author and committer. -// FIXME: there are a lot of "return sig, err" (but the err is also nil), that's the old behavior, to avoid breaking -func newSignatureFromCommitline(line []byte) (sig *Signature, err error) { - sig = new(Signature) - emailStart := bytes.LastIndexByte(line, '<') - emailEnd := bytes.LastIndexByte(line, '>') - if emailStart == -1 || emailEnd == -1 || emailEnd < emailStart { - return sig, err - } - - if emailStart > 0 { // Empty name has already occurred, even if it shouldn't - sig.Name = strings.TrimSpace(string(line[:emailStart-1])) - } - sig.Email = string(line[emailStart+1 : emailEnd]) - - hasTime := emailEnd+2 < len(line) - if !hasTime { - return sig, err - } - - // Check date format. - firstChar := line[emailEnd+2] - if firstChar >= 48 && firstChar <= 57 { - idx := bytes.IndexByte(line[emailEnd+2:], ' ') - if idx < 0 { - return sig, err - } - - timestring := string(line[emailEnd+2 : emailEnd+2+idx]) - seconds, _ := strconv.ParseInt(timestring, 10, 64) - sig.When = time.Unix(seconds, 0) - - idx += emailEnd + 3 - if idx >= len(line) || idx+5 > len(line) { - return sig, err - } - - timezone := string(line[idx : idx+5]) - tzhours, err1 := strconv.ParseInt(timezone[0:3], 10, 64) - tzmins, err2 := strconv.ParseInt(timezone[3:], 10, 64) - if err1 != nil || err2 != nil { - return sig, err - } - if tzhours < 0 { - tzmins *= -1 - } - tz := time.FixedZone("", int(tzhours*60*60+tzmins*60)) - sig.When = sig.When.In(tz) - } else { - sig.When, err = time.Parse(GitTimeLayout, string(line[emailEnd+2:])) - if err != nil { - return sig, err - } - } - return sig, err + *s = *parseSignatureFromCommitLine(util.UnsafeBytesToString(b)) } diff --git a/modules/git/signature_test.go b/modules/git/signature_test.go new file mode 100644 index 0000000000..92681feea9 --- /dev/null +++ b/modules/git/signature_test.go @@ -0,0 +1,47 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestParseSignatureFromCommitLine(t *testing.T) { + tests := []struct { + line string + want *Signature + }{ + { + line: "a b 12345 +0100", + want: &Signature{ + Name: "a b", + Email: "c@d.com", + When: time.Unix(12345, 0).In(time.FixedZone("", 3600)), + }, + }, + { + line: "bad line", + want: &Signature{Name: "bad line"}, + }, + { + line: "bad < line", + want: &Signature{Name: "bad < line"}, + }, + { + line: "bad > line", + want: &Signature{Name: "bad > line"}, + }, + { + line: "bad-line ", + want: &Signature{Name: "bad-line "}, + }, + } + for _, test := range tests { + got := parseSignatureFromCommitLine(test.line) + assert.EqualValues(t, test.want, got) + } +} diff --git a/modules/git/tag.go b/modules/git/tag.go index d0ddef64e0..154f1d661a 100644 --- a/modules/git/tag.go +++ b/modules/git/tag.go @@ -7,6 +7,8 @@ import ( "bytes" "sort" "strings" + + "code.gitea.io/gitea/modules/util" ) const ( @@ -57,11 +59,7 @@ l: // A commit can have one or more parents tag.Type = string(line[spacepos+1:]) case "tagger": - sig, err := newSignatureFromCommitline(line[spacepos+1:]) - if err != nil { - return nil, err - } - tag.Tagger = sig + tag.Tagger = parseSignatureFromCommitLine(util.UnsafeBytesToString(line[spacepos+1:])) } nextline += eol + 1 case eol == 0: diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go index ef598d7e91..a1b0acdafa 100644 --- a/modules/git/tree_nogogit.go +++ b/modules/git/tree_nogogit.go @@ -7,7 +7,6 @@ package git import ( "io" - "math" "strings" ) @@ -63,19 +62,8 @@ func (t *Tree) ListEntries() (Entries, error) { } // Not a tree just use ls-tree instead - for sz > math.MaxInt32 { - discarded, err := rd.Discard(math.MaxInt32) - sz -= int64(discarded) - if err != nil { - return nil, err - } - } - for sz > 0 { - discarded, err := rd.Discard(int(sz)) - sz -= int64(discarded) - if err != nil { - return nil, err - } + if err := DiscardFull(rd, sz+1); err != nil { + return nil, err } } diff --git a/modules/git/tree_test.go b/modules/git/tree_test.go new file mode 100644 index 0000000000..6d2b5c84d5 --- /dev/null +++ b/modules/git/tree_test.go @@ -0,0 +1,27 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSubTree_Issue29101(t *testing.T) { + repo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo1_bare")) + assert.NoError(t, err) + defer repo.Close() + + commit, err := repo.GetCommit("ce064814f4a0d337b333e646ece456cd39fab612") + assert.NoError(t, err) + + // old code could produce a different error if called multiple times + for i := 0; i < 10; i++ { + _, err = commit.SubTree("file1.txt") + assert.Error(t, err) + assert.True(t, IsErrNotExist(err)) + } +} diff --git a/modules/gitgraph/graph_models.go b/modules/gitgraph/graph_models.go index 748f7f3075..e48fef8b9d 100644 --- a/modules/gitgraph/graph_models.go +++ b/modules/gitgraph/graph_models.go @@ -116,10 +116,10 @@ func (graph *Graph) LoadAndProcessCommits(ctx context.Context, repository *repo_ c.Verification = asymkey_model.ParseCommitWithSignature(ctx, c.Commit) _ = asymkey_model.CalculateTrustStatus(c.Verification, repository.GetTrustModel(), func(user *user_model.User) (bool, error) { - return repo_model.IsOwnerMemberCollaborator(repository, user.ID) + return repo_model.IsOwnerMemberCollaborator(ctx, repository, user.ID) }, &keyMap) - statuses, _, err := git_model.GetLatestCommitStatus(db.DefaultContext, repository.ID, c.Commit.ID.String(), db.ListOptions{}) + statuses, _, err := git_model.GetLatestCommitStatus(ctx, repository.ID, c.Commit.ID.String(), db.ListOptions{}) if err != nil { log.Error("GetLatestCommitStatus: %v", err) } else { diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go index b1fd6da76d..67c97a6e7e 100644 --- a/modules/graceful/manager_unix.go +++ b/modules/graceful/manager_unix.go @@ -118,7 +118,15 @@ func (g *Manager) start(ctx context.Context) { defer close(startupDone) // Wait till we're done getting all of the listeners and then close // the unused ones - g.createServerWaitGroup.Wait() + func() { + // FIXME: there is a fundamental design problem of the "manager" and the "wait group". + // If nothing has started, the "Wait" just panics: sync: WaitGroup is reused before previous Wait has returned + // There is no clear solution besides a complete rewriting of the "manager" + defer func() { + _ = recover() + }() + g.createServerWaitGroup.Wait() + }() // Ignore the error here there's not much we can do with it // They're logged in the CloseProvidedListeners function _ = CloseProvidedListeners() diff --git a/modules/graceful/manager_windows.go b/modules/graceful/manager_windows.go index f676f86d04..6bcae9f747 100644 --- a/modules/graceful/manager_windows.go +++ b/modules/graceful/manager_windows.go @@ -227,7 +227,15 @@ func (g *Manager) awaitServer(limit time.Duration) bool { c := make(chan struct{}) go func() { defer close(c) - g.createServerWaitGroup.Wait() + func() { + // FIXME: there is a fundamental design problem of the "manager" and the "wait group". + // If nothing has started, the "Wait" just panics: sync: WaitGroup is reused before previous Wait has returned + // There is no clear solution besides a complete rewriting of the "manager" + defer func() { + _ = recover() + }() + g.createServerWaitGroup.Wait() + }() }() if limit > 0 { select { diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go index a67217e864..d7ab3f7afd 100644 --- a/modules/highlight/highlight.go +++ b/modules/highlight/highlight.go @@ -9,6 +9,7 @@ import ( "bytes" "fmt" gohtml "html" + "html/template" "io" "path/filepath" "strings" @@ -55,7 +56,7 @@ func NewContext() { } // Code returns a HTML version of code string with chroma syntax highlighting classes and the matched lexer name -func Code(fileName, language, code string) (string, string) { +func Code(fileName, language, code string) (output template.HTML, lexerName string) { NewContext() // diff view newline will be passed as empty, change to literal '\n' so it can be copied @@ -65,7 +66,7 @@ func Code(fileName, language, code string) (string, string) { } if len(code) > sizeLimit { - return code, "" + return template.HTML(template.HTMLEscapeString(code)), "" } var lexer chroma.Lexer @@ -102,13 +103,11 @@ func Code(fileName, language, code string) (string, string) { cache.Add(fileName, lexer) } - lexerName := formatLexerName(lexer.Config().Name) - - return CodeFromLexer(lexer, code), lexerName + return CodeFromLexer(lexer, code), formatLexerName(lexer.Config().Name) } // CodeFromLexer returns a HTML version of code string with chroma syntax highlighting classes -func CodeFromLexer(lexer chroma.Lexer, code string) string { +func CodeFromLexer(lexer chroma.Lexer, code string) template.HTML { formatter := html.New(html.WithClasses(true), html.WithLineNumbers(false), html.PreventSurroundingPre(true), @@ -120,23 +119,23 @@ func CodeFromLexer(lexer chroma.Lexer, code string) string { iterator, err := lexer.Tokenise(nil, code) if err != nil { log.Error("Can't tokenize code: %v", err) - return code + return template.HTML(template.HTMLEscapeString(code)) } // style not used for live site but need to pass something err = formatter.Format(htmlw, githubStyles, iterator) if err != nil { log.Error("Can't format code: %v", err) - return code + return template.HTML(template.HTMLEscapeString(code)) } _ = htmlw.Flush() // Chroma will add newlines for certain lexers in order to highlight them properly // Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output - return strings.TrimSuffix(htmlbuf.String(), "\n") + return template.HTML(strings.TrimSuffix(htmlbuf.String(), "\n")) } // File returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name -func File(fileName, language string, code []byte) ([]string, string, error) { +func File(fileName, language string, code []byte) ([]template.HTML, string, error) { NewContext() if len(code) > sizeLimit { @@ -183,14 +182,14 @@ func File(fileName, language string, code []byte) ([]string, string, error) { tokensLines := chroma.SplitTokensIntoLines(iterator.Tokens()) htmlBuf := &bytes.Buffer{} - lines := make([]string, 0, len(tokensLines)) + lines := make([]template.HTML, 0, len(tokensLines)) for _, tokens := range tokensLines { iterator = chroma.Literator(tokens...) err = formatter.Format(htmlBuf, githubStyles, iterator) if err != nil { return nil, "", fmt.Errorf("can't format code: %w", err) } - lines = append(lines, htmlBuf.String()) + lines = append(lines, template.HTML(htmlBuf.String())) htmlBuf.Reset() } @@ -198,9 +197,9 @@ func File(fileName, language string, code []byte) ([]string, string, error) { } // PlainText returns non-highlighted HTML for code -func PlainText(code []byte) []string { +func PlainText(code []byte) []template.HTML { r := bufio.NewReader(bytes.NewReader(code)) - m := make([]string, 0, bytes.Count(code, []byte{'\n'})+1) + m := make([]template.HTML, 0, bytes.Count(code, []byte{'\n'})+1) for { content, err := r.ReadString('\n') if err != nil && err != io.EOF { @@ -210,7 +209,7 @@ func PlainText(code []byte) []string { if content == "" && err == io.EOF { break } - s := gohtml.EscapeString(content) + s := template.HTML(gohtml.EscapeString(content)) m = append(m, s) } return m diff --git a/modules/highlight/highlight_test.go b/modules/highlight/highlight_test.go index 7a9887728f..659688bd0f 100644 --- a/modules/highlight/highlight_test.go +++ b/modules/highlight/highlight_test.go @@ -4,21 +4,36 @@ package highlight import ( + "html/template" "strings" "testing" "github.com/stretchr/testify/assert" ) -func lines(s string) []string { - return strings.Split(strings.ReplaceAll(strings.TrimSpace(s), `\n`, "\n"), "\n") +func lines(s string) (out []template.HTML) { + // "" => [], "a" => ["a"], "a\n" => ["a\n"], "a\nb" => ["a\n", "b"] (each line always includes EOL "\n" if it exists) + out = make([]template.HTML, 0) + s = strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(s), "\n", ""), `\n`, "\n") + for { + if p := strings.IndexByte(s, '\n'); p != -1 { + out = append(out, template.HTML(s[:p+1])) + s = s[p+1:] + } else { + break + } + } + if s != "" { + out = append(out, template.HTML(s)) + } + return out } func TestFile(t *testing.T) { tests := []struct { name string code string - want []string + want []template.HTML lexerName string }{ { @@ -99,10 +114,7 @@ c=2 t.Run(tt.name, func(t *testing.T) { out, lexerName, err := File(tt.name, "", []byte(tt.code)) assert.NoError(t, err) - expected := strings.Join(tt.want, "\n") - actual := strings.Join(out, "\n") - assert.Equal(t, strings.Count(actual, "")) - assert.EqualValues(t, expected, actual) + assert.EqualValues(t, tt.want, out) assert.Equal(t, tt.lexerName, lexerName) }) } @@ -112,7 +124,7 @@ func TestPlainText(t *testing.T) { tests := []struct { name string code string - want []string + want []template.HTML }{ { name: "empty.py", @@ -165,9 +177,7 @@ c=2`), for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { out := PlainText([]byte(tt.code)) - expected := strings.Join(tt.want, "\n") - actual := strings.Join(out, "\n") - assert.EqualValues(t, expected, actual) + assert.EqualValues(t, tt.want, out) }) } } diff --git a/modules/hostmatcher/http.go b/modules/hostmatcher/http.go index 65f5f78b14..c743f6efb3 100644 --- a/modules/hostmatcher/http.go +++ b/modules/hostmatcher/http.go @@ -7,12 +7,17 @@ import ( "context" "fmt" "net" + "net/url" "syscall" "time" ) // NewDialContext returns a DialContext for Transport, the DialContext will do allow/block list check func NewDialContext(usage string, allowList, blockList *HostMatchList) func(ctx context.Context, network, addr string) (net.Conn, error) { + return NewDialContextWithProxy(usage, allowList, blockList, nil) +} + +func NewDialContextWithProxy(usage string, allowList, blockList *HostMatchList, proxy *url.URL) func(ctx context.Context, network, addr string) (net.Conn, error) { // How Go HTTP Client works with redirection: // transport.RoundTrip URL=http://domain.com, Host=domain.com // transport.DialContext addrOrHost=domain.com:80 @@ -26,11 +31,18 @@ func NewDialContext(usage string, allowList, blockList *HostMatchList) func(ctx Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, - Control: func(network, ipAddr string, c syscall.RawConn) (err error) { - var host string - if host, _, err = net.SplitHostPort(addrOrHost); err != nil { + Control: func(network, ipAddr string, c syscall.RawConn) error { + host, port, err := net.SplitHostPort(addrOrHost) + if err != nil { return err } + if proxy != nil { + // Always allow the host of the proxy, but only on the specified port. + if host == proxy.Hostname() && port == proxy.Port() { + return nil + } + } + // in Control func, the addr was already resolved to IP:PORT format, there is no cost to do ResolveTCPAddr here tcpAddr, err := net.ResolveTCPAddr(network, ipAddr) if err != nil { diff --git a/modules/indexer/code/bleve/bleve.go b/modules/indexer/code/bleve/bleve.go index 0bfd85cb3f..8ba50ed77c 100644 --- a/modules/indexer/code/bleve/bleve.go +++ b/modules/indexer/code/bleve/bleve.go @@ -174,7 +174,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro return batch.Index(id, &RepoIndexerData{ RepoID: repo.ID, CommitID: commitSha, - Content: string(charset.ToUTF8DropErrors(fileContents)), + Content: string(charset.ToUTF8DropErrors(fileContents, charset.ConvertOpts{})), Language: analyze.GetCodeLanguage(update.Filename, fileContents), UpdatedAt: time.Now().UTC(), }) diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go index e7e3429a39..0f70f13485 100644 --- a/modules/indexer/code/elasticsearch/elasticsearch.go +++ b/modules/indexer/code/elasticsearch/elasticsearch.go @@ -135,7 +135,7 @@ func (b *Indexer) addUpdate(ctx context.Context, batchWriter git.WriteCloserErro Id(id). Doc(map[string]any{ "repo_id": repo.ID, - "content": string(charset.ToUTF8DropErrors(fileContents)), + "content": string(charset.ToUTF8DropErrors(fileContents, charset.ConvertOpts{})), "commit_id": sha, "language": analyze.GetCodeLanguage(update.Filename, fileContents), "updated_at": timeutil.TimeStampNow(), @@ -180,11 +180,17 @@ func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha st } if len(reqs) > 0 { - _, err := b.inner.Client.Bulk(). - Index(b.inner.VersionedIndexName()). - Add(reqs...). - Do(ctx) - return err + esBatchSize := 50 + + for i := 0; i < len(reqs); i += esBatchSize { + _, err := b.inner.Client.Bulk(). + Index(b.inner.VersionedIndexName()). + Add(reqs[i:min(i+esBatchSize, len(reqs))]...). + Do(ctx) + if err != nil { + return err + } + } } return nil } diff --git a/modules/indexer/code/search.go b/modules/indexer/code/search.go index fdb468df1a..2ddc2397fa 100644 --- a/modules/indexer/code/search.go +++ b/modules/indexer/code/search.go @@ -6,6 +6,7 @@ package code import ( "bytes" "context" + "html/template" "strings" "code.gitea.io/gitea/modules/highlight" @@ -15,14 +16,18 @@ import ( // Result a search result to display type Result struct { - RepoID int64 - Filename string - CommitID string - UpdatedUnix timeutil.TimeStamp - Language string - Color string - LineNumbers []int - FormattedLines string + RepoID int64 + Filename string + CommitID string + UpdatedUnix timeutil.TimeStamp + Language string + Color string + Lines []ResultLine +} + +type ResultLine struct { + Num int + FormattedContent template.HTML } type SearchResultLanguages = internal.SearchResultLanguages @@ -69,7 +74,7 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res var formattedLinesBuffer bytes.Buffer contentLines := strings.SplitAfter(result.Content[startIndex:endIndex], "\n") - lineNumbers := make([]int, len(contentLines)) + lines := make([]ResultLine, 0, len(contentLines)) index := startIndex for i, line := range contentLines { var err error @@ -92,21 +97,29 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res return nil, err } - lineNumbers[i] = startLineNum + i + lines = append(lines, ResultLine{Num: startLineNum + i}) index += len(line) } - highlighted, _ := highlight.Code(result.Filename, "", formattedLinesBuffer.String()) + // we should highlight the whole code block first, otherwise it doesn't work well with multiple line highlighting + hl, _ := highlight.Code(result.Filename, "", formattedLinesBuffer.String()) + highlightedLines := strings.Split(string(hl), "\n") + + // The lines outputted by highlight.Code might not match the original lines, because "highlight" removes the last `\n` + lines = lines[:min(len(highlightedLines), len(lines))] + highlightedLines = highlightedLines[:len(lines)] + for i := 0; i < len(lines); i++ { + lines[i].FormattedContent = template.HTML(highlightedLines[i]) + } return &Result{ - RepoID: result.RepoID, - Filename: result.Filename, - CommitID: result.CommitID, - UpdatedUnix: result.UpdatedUnix, - Language: result.Language, - Color: result.Color, - LineNumbers: lineNumbers, - FormattedLines: highlighted, + RepoID: result.RepoID, + Filename: result.Filename, + CommitID: result.CommitID, + UpdatedUnix: result.UpdatedUnix, + Language: result.Language, + Color: result.Color, + Lines: lines, }, nil } diff --git a/modules/indexer/issues/db/options.go b/modules/indexer/issues/db/options.go index 4d3fa44ca6..158b7397e2 100644 --- a/modules/indexer/issues/db/options.go +++ b/modules/indexer/issues/db/options.go @@ -38,7 +38,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m sortType = "leastupdate" case internal.SortByCommentsAsc: sortType = "leastcomment" - case internal.SortByDeadlineAsc: + case internal.SortByDeadlineDesc: sortType = "farduedate" case internal.SortByCreatedDesc: sortType = "newest" @@ -46,7 +46,7 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m sortType = "recentupdate" case internal.SortByCommentsDesc: sortType = "mostcomment" - case internal.SortByDeadlineDesc: + case internal.SortByDeadlineAsc: sortType = "nearduedate" default: sortType = "newest" diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index 020659c82b..ef06d8862a 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -204,12 +204,13 @@ func getIssueIndexerQueueHandler(ctx context.Context) func(items ...*IndexerMeta func populateIssueIndexer(ctx context.Context) { ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Service: PopulateIssueIndexer", process.SystemProcessType, true) defer finished() - if err := PopulateIssueIndexer(ctx, true); err != nil { + ctx = contextWithKeepRetry(ctx) // keep retrying since it's a background task + if err := PopulateIssueIndexer(ctx); err != nil { log.Error("Issue indexer population failed: %v", err) } } -func PopulateIssueIndexer(ctx context.Context, keepRetrying bool) error { +func PopulateIssueIndexer(ctx context.Context) error { for page := 1; ; page++ { select { case <-ctx.Done(): @@ -232,20 +233,8 @@ func PopulateIssueIndexer(ctx context.Context, keepRetrying bool) error { } for _, repo := range repos { - for { - select { - case <-ctx.Done(): - return fmt.Errorf("shutdown before completion: %w", ctx.Err()) - default: - } - if err := updateRepoIndexer(ctx, repo.ID); err != nil { - if keepRetrying && ctx.Err() == nil { - log.Warn("Retry to populate issue indexer for repo %d: %v", repo.ID, err) - continue - } - return fmt.Errorf("populate issue indexer for repo %d: %v", repo.ID, err) - } - break + if err := updateRepoIndexer(ctx, repo.ID); err != nil { + return fmt.Errorf("populate issue indexer for repo %d: %v", repo.ID, err) } } } @@ -259,8 +248,8 @@ func UpdateRepoIndexer(ctx context.Context, repoID int64) { } // UpdateIssueIndexer add/update an issue to the issue indexer -func UpdateIssueIndexer(issueID int64) { - if err := updateIssueIndexer(issueID); err != nil { +func UpdateIssueIndexer(ctx context.Context, issueID int64) { + if err := updateIssueIndexer(ctx, issueID); err != nil { log.Error("Unable to push issue %d to issue indexer: %v", issueID, err) } } diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go index 0e36d21313..7241f6313c 100644 --- a/modules/indexer/issues/indexer_test.go +++ b/modules/indexer/issues/indexer_test.go @@ -180,6 +180,21 @@ func searchIssueByID(t *testing.T) { }, []int64{11, 6, 5, 3, 2, 1}, }, + { + // issue 20 request user 15 and team 5 which user 15 belongs to + // the review request number of issue 20 should be 1 + SearchOptions{ + ReviewRequestedID: int64Pointer(15), + }, + []int64{12, 20}, + }, + { + // user 20 approved the issue 20, so return nothing + SearchOptions{ + ReviewRequestedID: int64Pointer(20), + }, + []int64{}, + }, } for _, test := range tests { @@ -206,7 +221,7 @@ func searchIssueIsPull(t *testing.T) { SearchOptions{ IsPull: util.OptionalBoolTrue, }, - []int64{12, 11, 19, 9, 8, 3, 2}, + []int64{12, 11, 20, 19, 9, 8, 3, 2}, }, } for _, test := range tests { @@ -227,7 +242,7 @@ func searchIssueIsClosed(t *testing.T) { SearchOptions{ IsClosed: util.OptionalBoolFalse, }, - []int64{17, 16, 15, 14, 13, 12, 11, 6, 19, 18, 10, 7, 9, 8, 3, 2, 1}, + []int64{17, 16, 15, 14, 13, 12, 11, 20, 6, 19, 18, 10, 7, 9, 8, 3, 2, 1}, }, { SearchOptions{ @@ -293,7 +308,7 @@ func searchIssueByLabelID(t *testing.T) { SearchOptions{ ExcludedLabelIDs: []int64{1}, }, - []int64{17, 16, 15, 14, 13, 12, 11, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3}, + []int64{17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3}, }, } for _, test := range tests { @@ -317,7 +332,7 @@ func searchIssueByTime(t *testing.T) { SearchOptions{ UpdatedAfterUnix: int64Pointer(0), }, - []int64{17, 16, 15, 14, 13, 12, 11, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1}, + []int64{17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1}, }, } for _, test := range tests { @@ -338,7 +353,7 @@ func searchIssueWithOrder(t *testing.T) { SearchOptions{ SortBy: internal.SortByCreatedAsc, }, - []int64{1, 2, 3, 8, 9, 4, 7, 10, 18, 19, 5, 6, 11, 12, 13, 14, 15, 16, 17}, + []int64{1, 2, 3, 8, 9, 4, 7, 10, 18, 19, 5, 6, 20, 11, 12, 13, 14, 15, 16, 17}, }, } for _, test := range tests { @@ -393,7 +408,7 @@ func searchIssueWithPaginator(t *testing.T) { }, }, []int64{17, 16, 15, 14, 13}, - 19, + 20, }, } for _, test := range tests { diff --git a/modules/indexer/issues/meilisearch/meilisearch.go b/modules/indexer/issues/meilisearch/meilisearch.go index 335395f2f6..060b5c5279 100644 --- a/modules/indexer/issues/meilisearch/meilisearch.go +++ b/modules/indexer/issues/meilisearch/meilisearch.go @@ -5,6 +5,7 @@ package meilisearch import ( "context" + "fmt" "strconv" "strings" @@ -210,11 +211,16 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( skip, limit := indexer_internal.ParsePaginator(options.Paginator, maxTotalHits) - searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).Search(options.Keyword, &meilisearch.SearchRequest{ - Filter: query.Statement(), - Limit: int64(limit), - Offset: int64(skip), - Sort: sortBy, + // to make it non fuzzy ("typo tolerance" in meilisearch terms), we have to quote the keyword(s) + // https://www.meilisearch.com/docs/reference/api/search#phrase-search + keyword := doubleQuoteKeyword(options.Keyword) + + searchRes, err := b.inner.Client.Index(b.inner.VersionedIndexName()).Search(keyword, &meilisearch.SearchRequest{ + Filter: query.Statement(), + Limit: int64(limit), + Offset: int64(skip), + Sort: sortBy, + MatchingStrategy: "all", }) if err != nil { return nil, err @@ -240,3 +246,16 @@ func parseSortBy(sortBy internal.SortBy) string { } return field + ":asc" } + +func doubleQuoteKeyword(k string) string { + kp := strings.Split(k, " ") + parts := 0 + for i := range kp { + part := strings.Trim(kp[i], "\"") + if part != "" { + kp[parts] = fmt.Sprintf(`"%s"`, part) + parts++ + } + } + return strings.Join(kp[:parts], " ") +} diff --git a/modules/indexer/issues/meilisearch/meilisearch_test.go b/modules/indexer/issues/meilisearch/meilisearch_test.go index 3d7237268e..2837c3a0bd 100644 --- a/modules/indexer/issues/meilisearch/meilisearch_test.go +++ b/modules/indexer/issues/meilisearch/meilisearch_test.go @@ -11,6 +11,8 @@ import ( "time" "code.gitea.io/gitea/modules/indexer/issues/internal/tests" + + "github.com/stretchr/testify/assert" ) func TestMeilisearchIndexer(t *testing.T) { @@ -48,3 +50,11 @@ func TestMeilisearchIndexer(t *testing.T) { tests.TestIndexer(t, indexer) } + +func TestDoubleQuoteKeyword(t *testing.T) { + assert.EqualValues(t, "", doubleQuoteKeyword("")) + assert.EqualValues(t, `"a" "b" "c"`, doubleQuoteKeyword("a b c")) + assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g")) + assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g")) + assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword(`a "" "d" """g`)) +} diff --git a/modules/indexer/issues/util.go b/modules/indexer/issues/util.go index 2dec3b71db..4e8e0a2899 100644 --- a/modules/indexer/issues/util.go +++ b/modules/indexer/issues/util.go @@ -127,15 +127,15 @@ func updateRepoIndexer(ctx context.Context, repoID int64) error { return fmt.Errorf("issue_model.GetIssueIDsByRepoID: %w", err) } for _, id := range ids { - if err := updateIssueIndexer(id); err != nil { + if err := updateIssueIndexer(ctx, id); err != nil { return err } } return nil } -func updateIssueIndexer(issueID int64) error { - return pushIssueIndexerQueue(&IndexerMetadata{ID: issueID}) +func updateIssueIndexer(ctx context.Context, issueID int64) error { + return pushIssueIndexerQueue(ctx, &IndexerMetadata{ID: issueID}) } func deleteRepoIssueIndexer(ctx context.Context, repoID int64) error { @@ -148,13 +148,21 @@ func deleteRepoIssueIndexer(ctx context.Context, repoID int64) error { if len(ids) == 0 { return nil } - return pushIssueIndexerQueue(&IndexerMetadata{ + return pushIssueIndexerQueue(ctx, &IndexerMetadata{ IDs: ids, IsDelete: true, }) } -func pushIssueIndexerQueue(data *IndexerMetadata) error { +type keepRetryKey struct{} + +// contextWithKeepRetry returns a context with a key indicating that the indexer should keep retrying. +// Please note that it's for background tasks only, and it should not be used for user requests, or it may cause blocking. +func contextWithKeepRetry(ctx context.Context) context.Context { + return context.WithValue(ctx, keepRetryKey{}, true) +} + +func pushIssueIndexerQueue(ctx context.Context, data *IndexerMetadata) error { if issueIndexerQueue == nil { // Some unit tests will trigger indexing, but the queue is not initialized. // It's OK to ignore it, but log a warning message in case it's not a unit test. @@ -162,12 +170,26 @@ func pushIssueIndexerQueue(data *IndexerMetadata) error { return nil } - err := issueIndexerQueue.Push(data) - if errors.Is(err, queue.ErrAlreadyInQueue) { - return nil + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + err := issueIndexerQueue.Push(data) + if errors.Is(err, queue.ErrAlreadyInQueue) { + return nil + } + if errors.Is(err, context.DeadlineExceeded) { // the queue is full + log.Warn("It seems that issue indexer is slow and the queue is full. Please check the issue indexer or increase the queue size.") + if ctx.Value(keepRetryKey{}) == nil { + return err + } + // It will be better to increase the queue size instead of retrying, but users may ignore the previous warning message. + // However, even it retries, it may still cause index loss when there's a deadline in the context. + log.Debug("Retry to push %+v to issue indexer queue", data) + continue + } + return err } - if errors.Is(err, context.DeadlineExceeded) { - log.Warn("It seems that issue indexer is slow and the queue is full. Please check the issue indexer or increase the queue size.") - } - return err } diff --git a/modules/markup/csv/csv.go b/modules/markup/csv/csv.go index 7af34a6cbc..12458e954a 100644 --- a/modules/markup/csv/csv.go +++ b/modules/markup/csv/csv.go @@ -93,8 +93,10 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri if _, err := tmpBlock.WriteString(html.EscapeString(string(rawBytes))); err != nil { return err } - _, err = tmpBlock.WriteString("") - return err + if _, err := tmpBlock.WriteString(""); err != nil { + return err + } + return tmpBlock.Flush() } rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, bytes.NewReader(rawBytes)) diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go index ffbb6da4da..122517ed11 100644 --- a/modules/markup/external/external.go +++ b/modules/markup/external/external.go @@ -79,9 +79,10 @@ func envMark(envName string) string { // Render renders the data of the document to HTML via the external tool. func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { var ( - urlRawPrefix = strings.Replace(ctx.URLPrefix, "/src/", "/raw/", 1) - command = strings.NewReplacer(envMark("GITEA_PREFIX_SRC"), ctx.URLPrefix, - envMark("GITEA_PREFIX_RAW"), urlRawPrefix).Replace(p.Command) + command = strings.NewReplacer( + envMark("GITEA_PREFIX_SRC"), ctx.Links.SrcLink(), + envMark("GITEA_PREFIX_RAW"), ctx.Links.RawLink(), + ).Replace(p.Command) commands = strings.Fields(command) args = commands[1:] ) @@ -121,14 +122,14 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. ctx.Ctx = graceful.GetManager().ShutdownContext() } - processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.URLPrefix)) + processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.Links.SrcLink())) defer finished() cmd := exec.CommandContext(processCtx, commands[0], args...) cmd.Env = append( os.Environ(), - "GITEA_PREFIX_SRC="+ctx.URLPrefix, - "GITEA_PREFIX_RAW="+urlRawPrefix, + "GITEA_PREFIX_SRC="+ctx.Links.SrcLink(), + "GITEA_PREFIX_RAW="+ctx.Links.RawLink(), ) if !p.IsInputFile { cmd.Stdin = input diff --git a/modules/markup/html.go b/modules/markup/html.go index e53ccc6a79..4d3fc32fbe 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -66,7 +66,7 @@ var ( // well as the HTML5 spec: // http://spec.commonmark.org/0.28/#email-address // https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail) - emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|\\.(\\s|$))") + emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|;|,|\\?|!|\\.(\\s|$))") // blackfriday extensions create IDs like fn:user-content-footnote blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`) @@ -80,15 +80,10 @@ const keywordClass = "issue-keyword" // IsLink reports whether link fits valid format. func IsLink(link []byte) bool { - return isLink(link) -} - -// isLink reports whether link fits valid format. -func isLink(link []byte) bool { return validLinksPattern.Match(link) } -func isLinkStr(link string) bool { +func IsLinkStr(link string) bool { return validLinksPattern.MatchString(link) } @@ -344,7 +339,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output node = node.FirstChild } - visitNode(ctx, procs, procs, node) + visitNode(ctx, procs, node) newNodes := make([]*html.Node, 0, 5) @@ -375,7 +370,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output return nil } -func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node) { +func visitNode(ctx *RenderContext, procs []processor, node *html.Node) { // Add user-content- to IDs and "#" links if they don't already have them for idx, attr := range node.Attr { val := strings.TrimPrefix(attr.Val, "#") @@ -390,35 +385,29 @@ func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node } if attr.Key == "class" && attr.Val == "emoji" { - textProcs = nil + procs = nil } } // We ignore code and pre. switch node.Type { case html.TextNode: - textNode(ctx, textProcs, node) + textNode(ctx, procs, node) case html.ElementNode: if node.Data == "img" { for i, attr := range node.Attr { if attr.Key != "src" { continue } - if len(attr.Val) > 0 && !isLinkStr(attr.Val) && !strings.HasPrefix(attr.Val, "data:image/") { - prefix := ctx.URLPrefix - if ctx.IsWiki { - prefix = util.URLJoin(prefix, "wiki", "raw") - } - prefix = strings.Replace(prefix, "/src/", "/media/", 1) - - attr.Val = util.URLJoin(prefix, attr.Val) + if len(attr.Val) > 0 && !IsLinkStr(attr.Val) && !strings.HasPrefix(attr.Val, "data:image/") { + attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), attr.Val) } attr.Val = camoHandleLink(attr.Val) node.Attr[i] = attr } } else if node.Data == "a" { // Restrict text in links to emojis - textProcs = emojiProcessors + procs = emojiProcessors } else if node.Data == "code" || node.Data == "pre" { return } else if node.Data == "i" { @@ -444,7 +433,7 @@ func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node } } for n := node.FirstChild; n != nil; n = n.NextSibling { - visitNode(ctx, procs, textProcs, n) + visitNode(ctx, procs, n) } } // ignore everything else @@ -641,10 +630,6 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) { } func shortLinkProcessor(ctx *RenderContext, node *html.Node) { - shortLinkProcessorFull(ctx, node, false) -} - -func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { next := node.NextSibling for node != nil && node != next { m := shortLinkPattern.FindStringSubmatchIndex(node.Data) @@ -665,7 +650,7 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { if equalPos := strings.IndexByte(v, '='); equalPos == -1 { // There is no equal in this argument; this is a mandatory arg if props["name"] == "" { - if isLinkStr(v) { + if IsLinkStr(v) { // If we clearly see it is a link, we save it so // But first we need to ensure, that if both mandatory args provided @@ -740,7 +725,7 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { DataAtom: atom.A, } childNode.Parent = linkNode - absoluteLink := isLinkStr(link) + absoluteLink := IsLinkStr(link) if !absoluteLink { if image { link = strings.ReplaceAll(link, " ", "+") @@ -751,16 +736,9 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { link = url.PathEscape(link) } } - urlPrefix := ctx.URLPrefix if image { if !absoluteLink { - if IsSameDomain(urlPrefix) { - urlPrefix = strings.Replace(urlPrefix, "/src/", "/raw/", 1) - } - if ctx.IsWiki { - link = util.URLJoin("wiki", "raw", link) - } - link = util.URLJoin(urlPrefix, link) + link = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), link) } title := props["title"] if title == "" { @@ -789,18 +767,15 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { } else { if !absoluteLink { if ctx.IsWiki { - link = util.URLJoin("wiki", link) + link = util.URLJoin(ctx.Links.WikiLink(), link) + } else { + link = util.URLJoin(ctx.Links.SrcLink(), link) } - link = util.URLJoin(urlPrefix, link) } childNode.Type = html.TextNode childNode.Data = name } - if noLink { - linkNode = childNode - } else { - linkNode.Attr = []html.Attribute{{Key: "href", Val: link}} - } + linkNode.Attr = []html.Attribute{{Key: "href", Val: link}} replaceContent(node, m[0], m[1], linkNode) node = node.NextSibling.NextSibling } @@ -852,7 +827,9 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) { } func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { - if ctx.Metas == nil || ctx.Metas["mode"] == "document" { + // FIXME: the use of "mode" is quite dirty and hacky, for example: what is a "document"? how should it be rendered? + // The "mode" approach should be refactored to some other more clear&reliable way. + if ctx.Metas == nil || (ctx.Metas["mode"] == "document" && !ctx.IsWiki) { return } var ( diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 7b7f6df701..5ba9561915 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -287,8 +287,8 @@ func TestRender_IssueIndexPattern_Document(t *testing.T) { } func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) { - if ctx.URLPrefix == "" { - ctx.URLPrefix = TestAppURL + if ctx.Links.Base == "" { + ctx.Links.Base = TestRepoURL } var buf strings.Builder @@ -303,19 +303,23 @@ func TestRender_AutoLink(t *testing.T) { test := func(input, expected string) { var buffer strings.Builder err := PostProcess(&RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: TestRepoURL, - Metas: localMetas, + Ctx: git.DefaultContext, + Links: Links{ + Base: TestRepoURL, + }, + Metas: localMetas, }, strings.NewReader(input), &buffer) assert.Equal(t, err, nil) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) buffer.Reset() err = PostProcess(&RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: TestRepoURL, - Metas: localMetas, - IsWiki: true, + Ctx: git.DefaultContext, + Links: Links{ + Base: TestRepoURL, + }, + Metas: localMetas, + IsWiki: true, }, strings.NewReader(input), &buffer) assert.Equal(t, err, nil) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) @@ -342,9 +346,11 @@ func TestRender_FullIssueURLs(t *testing.T) { test := func(input, expected string) { var result strings.Builder err := postProcess(&RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: TestRepoURL, - Metas: localMetas, + Ctx: git.DefaultContext, + Links: Links{ + Base: TestRepoURL, + }, + Metas: localMetas, }, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result) assert.NoError(t, err) assert.Equal(t, expected, result.String()) diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 9156bc6331..a5eecb93bb 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -42,8 +42,10 @@ func TestRender_Commits(t *testing.T) { buffer, err := RenderString(&RenderContext{ Ctx: git.DefaultContext, RelativePath: ".md", - URLPrefix: TestRepoURL, - Metas: localMetas, + Links: Links{ + Base: TestRepoURL, + }, + Metas: localMetas, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -93,8 +95,10 @@ func TestRender_CrossReferences(t *testing.T) { buffer, err := RenderString(&RenderContext{ Ctx: git.DefaultContext, RelativePath: "a.md", - URLPrefix: setting.AppSubURL, - Metas: localMetas, + Links: Links{ + Base: setting.AppSubURL, + }, + Metas: localMetas, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -138,7 +142,9 @@ func TestRender_links(t *testing.T) { buffer, err := RenderString(&RenderContext{ Ctx: git.DefaultContext, RelativePath: "a.md", - URLPrefix: TestRepoURL, + Links: Links{ + Base: TestRepoURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -238,7 +244,9 @@ func TestRender_email(t *testing.T) { res, err := RenderString(&RenderContext{ Ctx: git.DefaultContext, RelativePath: "a.md", - URLPrefix: TestRepoURL, + Links: Links{ + Base: TestRepoURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res)) @@ -264,6 +272,18 @@ func TestRender_email(t *testing.T) { "send email to info@gitea.co.uk.", `

send email to info@gitea.co.uk.

`) + test( + `j.doe@example.com, + j.doe@example.com. + j.doe@example.com; + j.doe@example.com? + j.doe@example.com!`, + `

j.doe@example.com,
+j.doe@example.com.
+j.doe@example.com;
+j.doe@example.com?
+j.doe@example.com!

`) + // Test that should *not* be turned into email links test( "\"info@gitea.com\"", @@ -297,7 +317,9 @@ func TestRender_emoji(t *testing.T) { buffer, err := RenderString(&RenderContext{ Ctx: git.DefaultContext, RelativePath: "a.md", - URLPrefix: TestRepoURL, + Links: Links{ + Base: TestRepoURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -359,29 +381,34 @@ func TestRender_ShortLinks(t *testing.T) { test := func(input, expected, expectedWiki string) { buffer, err := markdown.RenderString(&RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: tree, + Ctx: git.DefaultContext, + Links: Links{ + Base: TestRepoURL, + BranchPath: "master", + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) buffer, err = markdown.RenderString(&RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: TestRepoURL, - Metas: localMetas, - IsWiki: true, + Ctx: git.DefaultContext, + Links: Links{ + Base: TestRepoURL, + }, + Metas: localMetas, + IsWiki: true, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) } - rawtree := util.URLJoin(TestRepoURL, "raw", "master") + mediatree := util.URLJoin(TestRepoURL, "media", "master") url := util.URLJoin(tree, "Link") otherURL := util.URLJoin(tree, "Other-Link") encodedURL := util.URLJoin(tree, "Link%3F") - imgurl := util.URLJoin(rawtree, "Link.jpg") - otherImgurl := util.URLJoin(rawtree, "Link+Other.jpg") - encodedImgurl := util.URLJoin(rawtree, "Link+%23.jpg") - notencodedImgurl := util.URLJoin(rawtree, "some", "path", "Link+#.jpg") + imgurl := util.URLJoin(mediatree, "Link.jpg") + otherImgurl := util.URLJoin(mediatree, "Link+Other.jpg") + encodedImgurl := util.URLJoin(mediatree, "Link+%23.jpg") + notencodedImgurl := util.URLJoin(mediatree, "some", "path", "Link+#.jpg") urlWiki := util.URLJoin(TestRepoURL, "wiki", "Link") otherURLWiki := util.URLJoin(TestRepoURL, "wiki", "Other-Link") encodedURLWiki := util.URLJoin(TestRepoURL, "wiki", "Link%3F") @@ -463,21 +490,25 @@ func TestRender_ShortLinks(t *testing.T) { func TestRender_RelativeImages(t *testing.T) { setting.AppURL = TestAppURL - tree := util.URLJoin(TestRepoURL, "src", "master") test := func(input, expected, expectedWiki string) { buffer, err := markdown.RenderString(&RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: tree, - Metas: localMetas, + Ctx: git.DefaultContext, + Links: Links{ + Base: TestRepoURL, + BranchPath: "master", + }, + Metas: localMetas, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) buffer, err = markdown.RenderString(&RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: TestRepoURL, - Metas: localMetas, - IsWiki: true, + Ctx: git.DefaultContext, + Links: Links{ + Base: TestRepoURL, + }, + Metas: localMetas, + IsWiki: true, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) @@ -509,9 +540,11 @@ func Test_ParseClusterFuzz(t *testing.T) { var res strings.Builder err := PostProcess(&RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: "https://example.com", - Metas: localMetas, + Ctx: git.DefaultContext, + Links: Links{ + Base: "https://example.com", + }, + Metas: localMetas, }, strings.NewReader(data), &res) assert.NoError(t, err) assert.NotContains(t, res.String(), " 0 && !markup.IsLink(link) { - prefix := pc.Get(urlPrefixKey).(string) - if pc.Get(isWikiKey).(bool) { - prefix = giteautil.URLJoin(prefix, "wiki", "raw") - } - prefix = strings.Replace(prefix, "/src/", "/media/", 1) - - lnk := strings.TrimLeft(string(link), "/") - - lnk = giteautil.URLJoin(prefix, lnk) - link = []byte(lnk) + if len(v.Destination) > 0 && !markup.IsLink(v.Destination) { + v.Destination = []byte(giteautil.URLJoin( + ctx.Links.ResolveMediaLink(ctx.IsWiki), + strings.TrimLeft(string(v.Destination), "/"), + )) } - v.Destination = link parent := n.Parent() // Create a link around image only if parent is not already a link @@ -107,13 +99,13 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa // Create a link wrapper wrap := ast.NewLink() - wrap.Destination = link + wrap.Destination = v.Destination wrap.Title = v.Title wrap.SetAttributeString("target", []byte("_blank")) // Duplicate the current image node image := ast.NewImage(ast.NewLink()) - image.Destination = link + image.Destination = v.Destination image.Title = v.Title for _, attr := range v.Attributes() { image.SetAttribute(attr.Name, attr.Value) @@ -143,11 +135,17 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa link[0] != '#' && !bytes.HasPrefix(link, byteMailto) { // special case: this is not a link, a hash link or a mailto:, so it's a // relative URL - lnk := string(link) - if pc.Get(isWikiKey).(bool) { - lnk = giteautil.URLJoin("wiki", lnk) + + var base string + if ctx.IsWiki { + base = ctx.Links.WikiLink() + } else if ctx.Links.HasBranchInfo() { + base = ctx.Links.SrcLink() + } else { + base = ctx.Links.Base } - link = []byte(giteautil.URLJoin(pc.Get(urlPrefixKey).(string), lnk)) + + link = []byte(giteautil.URLJoin(base, string(link))) } if len(link) > 0 && link[0] == '#' { link = []byte("#user-content-" + string(link)[1:]) @@ -188,9 +186,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa applyElementDir(v) case *ast.Text: if v.SoftLineBreak() && !v.HardLineBreak() { - renderMetas := pc.Get(renderMetasKey).(map[string]string) - mode := renderMetas["mode"] - if mode != "document" { + if ctx.Metas["mode"] != "document" { v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInComments) } else { v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments) diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index 43885889d1..8bf3ac1da2 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -34,9 +34,6 @@ var ( ) var ( - urlPrefixKey = parser.NewContextKey() - isWikiKey = parser.NewContextKey() - renderMetasKey = parser.NewContextKey() renderContextKey = parser.NewContextKey() renderConfigKey = parser.NewContextKey() ) @@ -66,9 +63,6 @@ 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(urlPrefixKey, ctx.URLPrefix) - pc.Set(isWikiKey, ctx.IsWiki) - pc.Set(renderMetasKey, ctx.Metas) pc.Set(renderContextKey, ctx) return pc } @@ -109,7 +103,8 @@ func SpecializedMarkdown() goldmark.Markdown { } // include language-x class as part of commonmark spec - _, err = w.WriteString(``) + // the "display" class is used by "js/markup/math.js" to render the code element as a block + _, err = w.WriteString(``) if err != nil { return } diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index f2322b2554..7bdd1701e2 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -21,12 +21,11 @@ import ( ) const ( - AppURL = "http://localhost:3000/" - Repo = "gogits/gogs" - AppSubURL = AppURL + Repo + "/" + AppURL = "http://localhost:3000/" + FullURL = AppURL + "gogits/gogs/" ) -// these values should match the Repo const above +// these values should match the const above var localMetas = map[string]string{ "user": "gogits", "repo": "gogs", @@ -48,20 +47,23 @@ func TestMain(m *testing.M) { func TestRender_StandardLinks(t *testing.T) { setting.AppURL = AppURL - setting.AppSubURL = AppSubURL test := func(input, expected, expectedWiki string) { buffer, err := RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: FullURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) buffer, err = RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, - IsWiki: true, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: FullURL, + }, + IsWiki: true, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) @@ -70,8 +72,8 @@ func TestRender_StandardLinks(t *testing.T) { googleRendered := `

https://google.com/

` test("", googleRendered, googleRendered) - lnk := util.URLJoin(AppSubURL, "WikiPage") - lnkWiki := util.URLJoin(AppSubURL, "wiki", "WikiPage") + lnk := util.URLJoin(FullURL, "WikiPage") + lnkWiki := util.URLJoin(FullURL, "wiki", "WikiPage") test("[WikiPage](WikiPage)", `

WikiPage

`, `

WikiPage

`) @@ -79,12 +81,13 @@ func TestRender_StandardLinks(t *testing.T) { func TestRender_Images(t *testing.T) { setting.AppURL = AppURL - setting.AppSubURL = AppSubURL test := func(input, expected string) { buffer, err := RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: FullURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -93,7 +96,7 @@ func TestRender_Images(t *testing.T) { url := "../../.images/src/02/train.jpg" title := "Train" href := "https://gitea.io" - result := util.URLJoin(AppSubURL, url) + result := util.URLJoin(FullURL, url) // hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now test( @@ -107,7 +110,6 @@ func TestRender_Images(t *testing.T) { "[!["+title+"]("+url+")]("+href+")", `

`+title+`

`) - url = "/../../.images/src/02/train.jpg" test( "!["+title+"]("+url+")", `

`+title+`

`) @@ -284,16 +286,17 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno func TestTotal_RenderWiki(t *testing.T) { setting.AppURL = AppURL - setting.AppSubURL = AppSubURL - answers := testAnswers(util.URLJoin(AppSubURL, "wiki/"), util.URLJoin(AppSubURL, "wiki", "raw/")) + answers := testAnswers(util.URLJoin(FullURL, "wiki"), util.URLJoin(FullURL, "wiki", "raw")) for i := 0; i < len(sameCases); i++ { line, err := RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: AppSubURL, - Metas: localMetas, - IsWiki: true, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: FullURL, + }, + Metas: localMetas, + IsWiki: true, }, sameCases[i]) assert.NoError(t, err) assert.Equal(t, answers[i], line) @@ -303,20 +306,22 @@ func TestTotal_RenderWiki(t *testing.T) { // Guard wiki sidebar: special syntax `[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`, // rendered - `

Guardfile-DSL / Configuring-Guard

+ `

Guardfile-DSL / Configuring-Guard

`, // special syntax `[[Name|Link]]`, // rendered - `

Name

+ `

Name

`, } for i := 0; i < len(testCases); i += 2 { line, err := RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: AppSubURL, - IsWiki: true, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: FullURL, + }, + IsWiki: true, }, testCases[i]) assert.NoError(t, err) assert.Equal(t, testCases[i+1], line) @@ -325,15 +330,17 @@ func TestTotal_RenderWiki(t *testing.T) { func TestTotal_RenderString(t *testing.T) { setting.AppURL = AppURL - setting.AppSubURL = AppSubURL - answers := testAnswers(util.URLJoin(AppSubURL, "src", "master/"), util.URLJoin(AppSubURL, "raw", "master/")) + answers := testAnswers(util.URLJoin(FullURL, "src", "master"), util.URLJoin(FullURL, "media", "master")) for i := 0; i < len(sameCases); i++ { line, err := RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: util.URLJoin(AppSubURL, "src", "master/"), - Metas: localMetas, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: FullURL, + BranchPath: "master", + }, + Metas: localMetas, }, sameCases[i]) assert.NoError(t, err) assert.Equal(t, answers[i], line) @@ -343,8 +350,10 @@ func TestTotal_RenderString(t *testing.T) { for i := 0; i < len(testCases); i += 2 { line, err := RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: AppSubURL, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: FullURL, + }, }, testCases[i]) assert.NoError(t, err) assert.Equal(t, testCases[i+1], line) @@ -556,3 +565,393 @@ foo: bar assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase) } } + +func TestRenderLinks(t *testing.T) { + input := ` space @mention-user +/just/a/path.bin +https://example.com/file.bin +[local link](file.bin) +[remote link](https://example.com) +[[local link|file.bin]] +[[remote link|https://example.com]] +![local image](image.jpg) +![local image](path/file) +![local image](/path/file) +![remote image](https://example.com/image.jpg) +[[local image|image.jpg]] +[[remote link|https://example.com/image.jpg]] +https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare +https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit +:+1: +mail@domain.com +@mention-user test +#123 + space +` + cases := []struct { + Links markup.Links + IsWiki bool + Expected string + }{ + { // 0 + Links: markup.Links{}, + IsWiki: false, + Expected: `

space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+local image
+local image
+local image
+remote image
+local image
+remote link
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space

+`, + }, + { // 1 + Links: markup.Links{}, + IsWiki: true, + Expected: `

space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+local image
+local image
+local image
+remote image
+local image
+remote link
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space

+`, + }, + { // 2 + Links: markup.Links{ + Base: "https://gitea.io/", + }, + IsWiki: false, + Expected: `

space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+local image
+local image
+local image
+remote image
+local image
+remote link
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space

+`, + }, + { // 3 + Links: markup.Links{ + Base: "https://gitea.io/", + }, + IsWiki: true, + Expected: `

space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+local image
+local image
+local image
+remote image
+local image
+remote link
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space

+`, + }, + { // 4 + Links: markup.Links{ + Base: "/relative/path", + }, + IsWiki: false, + Expected: `

space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+local image
+local image
+local image
+remote image
+local image
+remote link
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space

+`, + }, + { // 5 + Links: markup.Links{ + Base: "/relative/path", + }, + IsWiki: true, + Expected: `

space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+local image
+local image
+local image
+remote image
+local image
+remote link
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space

+`, + }, + { // 6 + Links: markup.Links{ + Base: "/user/repo", + BranchPath: "branch/main", + }, + IsWiki: false, + Expected: `

space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+local image
+local image
+local image
+remote image
+local image
+remote link
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space

+`, + }, + { // 7 + Links: markup.Links{ + Base: "/relative/path", + BranchPath: "branch/main", + }, + IsWiki: true, + Expected: `

space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+local image
+local image
+local image
+remote image
+local image
+remote link
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space

+`, + }, + { // 8 + Links: markup.Links{ + Base: "/user/repo", + TreePath: "sub/folder", + }, + IsWiki: false, + Expected: `

space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+local image
+local image
+local image
+remote image
+local image
+remote link
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space

+`, + }, + { // 9 + Links: markup.Links{ + Base: "/relative/path", + TreePath: "sub/folder", + }, + IsWiki: true, + Expected: `

space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+local image
+local image
+local image
+remote image
+local image
+remote link
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space

+`, + }, + { // 10 + Links: markup.Links{ + Base: "/user/repo", + BranchPath: "branch/main", + TreePath: "sub/folder", + }, + IsWiki: false, + Expected: `

space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+local image
+local image
+local image
+remote image
+local image
+remote link
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space

+`, + }, + { // 11 + Links: markup.Links{ + Base: "/relative/path", + BranchPath: "branch/main", + TreePath: "sub/folder", + }, + IsWiki: true, + Expected: `

space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+local image
+local image
+local image
+remote image
+local image
+remote link
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space

+`, + }, + } + + for i, c := range cases { + result, err := RenderString(&markup.RenderContext{Ctx: context.Background(), Links: c.Links, IsWiki: c.IsWiki}, input) + assert.NoError(t, err, "Unexpected error in testcase: %v", i) + assert.Equal(t, c.Expected, result, "Unexpected result in testcase %v", i) + } +} diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index 9b6175649f..53bacb0a4e 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -4,7 +4,6 @@ package markup import ( - "bytes" "fmt" "html" "io" @@ -87,7 +86,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error } lexer = chroma.Coalesce(lexer) - if _, err := w.WriteString(highlight.CodeFromLexer(lexer, source)); err != nil { + if _, err := w.WriteString(string(highlight.CodeFromLexer(lexer, source))); err != nil { return "" } } @@ -101,8 +100,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error w := &Writer{ HTMLWriter: htmlWriter, - URLPrefix: ctx.URLPrefix, - IsWiki: ctx.IsWiki, + Ctx: ctx, } htmlWriter.ExtendingWriter = w @@ -132,63 +130,61 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri // Writer implements org.Writer type Writer struct { *org.HTMLWriter - URLPrefix string - IsWiki bool + Ctx *markup.RenderContext } -var byteMailto = []byte("mailto:") +func (r *Writer) resolveLink(kind, link string) string { + link = strings.TrimPrefix(link, "file:") + if !strings.HasPrefix(link, "#") && // not a URL fragment + !markup.IsLinkStr(link) && // not an absolute URL + !strings.HasPrefix(link, "mailto:") { + if kind == "regular" { + // orgmode reports the link kind as "regular" for "[[ImageLink.svg][The Image Desc]]" + // so we need to try to guess the link kind again here + kind = org.RegularLink{URL: link}.Kind() + } + + base := r.Ctx.Links.Base + if r.Ctx.IsWiki { + base = r.Ctx.Links.WikiLink() + } else if r.Ctx.Links.HasBranchInfo() { + base = r.Ctx.Links.SrcLink() + } + + if kind == "image" || kind == "video" { + base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsWiki) + } + + link = util.URLJoin(base, link) + } + return link +} // WriteRegularLink renders images, links or videos func (r *Writer) WriteRegularLink(l org.RegularLink) { - link := []byte(html.EscapeString(l.URL)) - if l.Protocol == "file" { - link = link[len("file:"):] - } - if len(link) > 0 && !markup.IsLink(link) && - link[0] != '#' && !bytes.HasPrefix(link, byteMailto) { - lnk := string(link) - if r.IsWiki { - lnk = util.URLJoin("wiki", lnk) - } - link = []byte(util.URLJoin(r.URLPrefix, lnk)) - } + link := r.resolveLink(l.Kind(), l.URL) // Inspired by https://github.com/niklasfasching/go-org/blob/6eb20dbda93cb88c3503f7508dc78cbbc639378f/org/html_writer.go#L406-L427 switch l.Kind() { case "image": if l.Description == nil { - imageSrc := getMediaURL(link) - fmt.Fprintf(r, `%s`, imageSrc, link, link) + _, _ = fmt.Fprintf(r, `%s`, link, link) } else { - description := strings.TrimPrefix(org.String(l.Description...), "file:") - imageSrc := getMediaURL([]byte(description)) - fmt.Fprintf(r, `%s`, link, imageSrc, imageSrc) + imageSrc := r.resolveLink(l.Kind(), org.String(l.Description...)) + _, _ = fmt.Fprintf(r, `%s`, link, imageSrc, imageSrc) } case "video": if l.Description == nil { - imageSrc := getMediaURL(link) - fmt.Fprintf(r, ``, imageSrc, link, link) + _, _ = fmt.Fprintf(r, ``, link, link) } else { - description := strings.TrimPrefix(org.String(l.Description...), "file:") - videoSrc := getMediaURL([]byte(description)) - fmt.Fprintf(r, ``, link, videoSrc, videoSrc) + videoSrc := r.resolveLink(l.Kind(), org.String(l.Description...)) + _, _ = fmt.Fprintf(r, ``, link, videoSrc, videoSrc) } default: - description := string(link) + description := link if l.Description != nil { description = r.WriteNodesAsString(l.Description...) } - fmt.Fprintf(r, `%s`, link, description, description) + _, _ = fmt.Fprintf(r, `%s`, link, description) } } - -func getMediaURL(l []byte) string { - srcURL := string(l) - - // Check if link is valid - if len(srcURL) > 0 && !markup.IsLink(l) { - srcURL = strings.Replace(srcURL, "/src/", "/media/", 1) - } - - return srcURL -} diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go index 8f454e9955..cf64093fe4 100644 --- a/modules/markup/orgmode/orgmode_test.go +++ b/modules/markup/orgmode/orgmode_test.go @@ -10,78 +10,109 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) -const ( - AppURL = "http://localhost:3000/" - Repo = "gogits/gogs" - AppSubURL = AppURL + Repo + "/" -) +const AppURL = "http://localhost:3000/" func TestRender_StandardLinks(t *testing.T) { setting.AppURL = AppURL - setting.AppSubURL = AppSubURL - test := func(input, expected string) { + test := func(input, expected string, isWiki bool) { buffer, err := RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: "/relative-path", + BranchPath: "branch/main", + }, + IsWiki: isWiki, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } - googleRendered := "

https://google.com/

" - test("[[https://google.com/]]", googleRendered) + test("[[https://google.com/]]", + `

https://google.com/

`, false) + test("[[WikiPage][The WikiPage Desc]]", + `

The WikiPage Desc

`, true) + test("[[ImageLink.svg][The Image Desc]]", + `

The Image Desc

`, false) +} - lnk := util.URLJoin(AppSubURL, "WikiPage") - test("[[WikiPage][WikiPage]]", - "

WikiPage

") +func TestRender_InternalLinks(t *testing.T) { + setting.AppURL = AppURL + + test := func(input, expected string) { + buffer, err := RenderString(&markup.RenderContext{ + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: "/relative-path", + BranchPath: "branch/main", + }, + }, input) + assert.NoError(t, err) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) + } + + test("[[file:test.org][Test]]", + `

Test

`) + test("[[./test.org][Test]]", + `

Test

`) + test("[[test.org][Test]]", + `

Test

`) + test("[[path/to/test.org][Test]]", + `

Test

`) } func TestRender_Media(t *testing.T) { setting.AppURL = AppURL - setting.AppSubURL = AppSubURL test := func(input, expected string) { buffer, err := RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: "./relative-path", + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } - url := "../../.images/src/02/train.jpg" - result := util.URLJoin(AppSubURL, url) - - test("[[file:"+url+"]]", - "

\""+result+"\"

") + test("[[file:../../.images/src/02/train.jpg]]", + `

.images/src/02/train.jpg

`) + test("[[file:train.jpg]]", + `

relative-path/train.jpg

`) // With description. test("[[https://example.com][https://example.com/example.svg]]", `

https://example.com/example.svg

`) test("[[https://example.com][https://example.com/example.mp4]]", - `

`) + `

`) // Without description. test("[[https://example.com/example.svg]]", - `

https://example.com/example.svg

`) + `

https://example.com/example.svg

`) test("[[https://example.com/example.mp4]]", - `

`) + `

`) + + // test [[LINK][DESCRIPTION]] syntax with "file:" prefix + test(`[[https://example.com/][file:https://example.com/foo%20bar.svg]]`, + `

https://example.com/foo%20bar.svg

`) + test(`[[file:https://example.com/foo%20bar.svg][Goto Image]]`, + `

Goto Image

`) + test(`[[file:https://example.com/link][https://example.com/image.jpg]]`, + `

https://example.com/image.jpg

`) + test(`[[file:https://example.com/link][file:https://example.com/image.jpg]]`, + `

https://example.com/image.jpg

`) } func TestRender_Source(t *testing.T) { setting.AppURL = AppURL - setting.AppSubURL = AppSubURL test := func(input, expected string) { buffer, err := RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, + Ctx: git.DefaultContext, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index 0331c3742a..5a7adcc553 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "github.com/yuin/goldmark/ast" ) @@ -69,7 +70,7 @@ type RenderContext struct { RelativePath string // relative path from tree root of the branch Type string IsWiki bool - URLPrefix string + Links Links Metas map[string]string DefaultLink string GitRepo *git.Repository @@ -80,6 +81,45 @@ type RenderContext struct { InStandalonePage bool // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page } +type Links struct { + Base string + BranchPath string + TreePath string +} + +func (l *Links) HasBranchInfo() bool { + return l.BranchPath != "" +} + +func (l *Links) SrcLink() string { + return util.URLJoin(l.Base, "src", l.BranchPath, l.TreePath) +} + +func (l *Links) MediaLink() string { + return util.URLJoin(l.Base, "media", l.BranchPath, l.TreePath) +} + +func (l *Links) RawLink() string { + return util.URLJoin(l.Base, "raw", l.BranchPath, l.TreePath) +} + +func (l *Links) WikiLink() string { + return util.URLJoin(l.Base, "wiki") +} + +func (l *Links) WikiRawLink() string { + return util.URLJoin(l.Base, "wiki/raw") +} + +func (l *Links) ResolveMediaLink(isWiki bool) string { + if isWiki { + return l.WikiRawLink() + } else if l.HasBranchInfo() { + return l.MediaLink() + } + return l.Base +} + // Cancel runs any cleanup functions that have been registered for this Ctx func (ctx *RenderContext) Cancel() { if ctx == nil { diff --git a/modules/packages/hashed_buffer.go b/modules/packages/hashed_buffer.go index 017ddf1c8f..4ab45edcec 100644 --- a/modules/packages/hashed_buffer.go +++ b/modules/packages/hashed_buffer.go @@ -32,7 +32,7 @@ func NewHashedBuffer() (*HashedBuffer, error) { return NewHashedBufferWithSize(DefaultMemorySize) } -// NewHashedBuffer creates a hashed buffer with a specific memory size +// NewHashedBufferWithSize creates a hashed buffer with a specific memory size func NewHashedBufferWithSize(maxMemorySize int) (*HashedBuffer, error) { b, err := filebuffer.New(maxMemorySize) if err != nil { diff --git a/modules/private/actions.go b/modules/private/actions.go index c924dac2cd..a22833632e 100644 --- a/modules/private/actions.go +++ b/modules/private/actions.go @@ -22,5 +22,8 @@ func GenerateActionsRunnerToken(ctx context.Context, scope string) (string, Resp }) resp, extra := requestJSONResp(req, &responseText{}) + if extra.HasError() { + return "", extra + } return resp.Text, extra } diff --git a/modules/private/key.go b/modules/private/key.go index 6f7cd87796..08762bd401 100644 --- a/modules/private/key.go +++ b/modules/private/key.go @@ -27,5 +27,8 @@ func AuthorizedPublicKeyByContent(ctx context.Context, content string) (string, req := newInternalRequest(ctx, reqURL, "POST") req.Param("content", content) resp, extra := requestJSONResp(req, &responseText{}) + if extra.HasError() { + return "", extra + } return resp.Text, extra } diff --git a/modules/private/mail.go b/modules/private/mail.go index 82216b346b..ac55d6fe4d 100644 --- a/modules/private/mail.go +++ b/modules/private/mail.go @@ -30,5 +30,8 @@ func SendEmail(ctx context.Context, subject, message string, to []string) (strin }) resp, extra := requestJSONResp(req, &responseText{}) + if extra.HasError() { + return "", extra + } return resp.Text, extra } diff --git a/modules/private/request.go b/modules/private/request.go index d3f99381a6..2bc43b972d 100644 --- a/modules/private/request.go +++ b/modules/private/request.go @@ -47,6 +47,7 @@ func (re responseError) Error() string { // requestJSONResp sends a request to the gitea server and then parses the response. // If the status code is not 2xx, or any error occurs, the ResponseExtra.Error field is guaranteed to be non-nil, // and the ResponseExtra.UserMsg field will be set to a message for the end user. +// Caller should check the ResponseExtra.HasError() first to see whether the request fails. // // * If the "res" is a struct pointer, the response will be parsed as JSON // * If the "res" is responseText pointer, the response will be stored as text in it diff --git a/modules/public/public.go b/modules/public/public.go index 5fbfe30a81..abc6b46158 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -33,7 +33,7 @@ func FileHandlerFunc() http.HandlerFunc { assetFS := AssetFS() return func(resp http.ResponseWriter, req *http.Request) { if req.Method != "GET" && req.Method != "HEAD" { - resp.WriteHeader(http.StatusNotFound) + resp.WriteHeader(http.StatusMethodNotAllowed) return } handleRequest(resp, req, assetFS, req.URL.Path) diff --git a/modules/queue/manager_test.go b/modules/queue/manager_test.go index 9a5b616b1a..15dd1b4f2f 100644 --- a/modules/queue/manager_test.go +++ b/modules/queue/manager_test.go @@ -46,7 +46,7 @@ CONN_STR = redis:// assert.Equal(t, "default", q.GetName()) assert.Equal(t, "level", q.GetType()) assert.Equal(t, filepath.Join(setting.AppDataPath, "queues/common"), q.baseConfig.DataFullDir) - assert.Equal(t, 100, q.baseConfig.Length) + assert.Equal(t, 100000, q.baseConfig.Length) assert.Equal(t, 20, q.batchLength) assert.Equal(t, "", q.baseConfig.ConnStr) assert.Equal(t, "default_queue", q.baseConfig.QueueFullName) diff --git a/modules/queue/workergroup.go b/modules/queue/workergroup.go index 147a4f335e..e3801ef2b2 100644 --- a/modules/queue/workergroup.go +++ b/modules/queue/workergroup.go @@ -60,6 +60,9 @@ func (q *WorkerPoolQueue[T]) doDispatchBatchToWorker(wg *workerGroup[T], flushCh full = true } + // TODO: the logic could be improved in the future, to avoid a data-race between "doStartNewWorker" and "workerNum" + // The root problem is that if we skip "doStartNewWorker" here, the "workerNum" might be decreased by other workers later + // So ideally, it should check whether there are enough workers by some approaches, and start new workers if necessary. q.workerNumMu.Lock() noWorker := q.workerNum == 0 if full || noWorker { @@ -143,7 +146,11 @@ func (q *WorkerPoolQueue[T]) doStartNewWorker(wp *workerGroup[T]) { log.Debug("Queue %q starts new worker", q.GetName()) defer log.Debug("Queue %q stops idle worker", q.GetName()) + atomic.AddInt32(&q.workerStartedCounter, 1) // Only increase counter, used for debugging + t := time.NewTicker(workerIdleDuration) + defer t.Stop() + keepWorking := true stopWorking := func() { q.workerNumMu.Lock() @@ -158,13 +165,18 @@ func (q *WorkerPoolQueue[T]) doStartNewWorker(wp *workerGroup[T]) { case batch, ok := <-q.batchChan: if !ok { stopWorking() - } else { - q.doWorkerHandle(batch) - t.Reset(workerIdleDuration) + continue + } + q.doWorkerHandle(batch) + // reset the idle ticker, and drain the tick after reset in case a tick is already triggered + t.Reset(workerIdleDuration) + select { + case <-t.C: + default: } case <-t.C: q.workerNumMu.Lock() - keepWorking = q.workerNum <= 1 + keepWorking = q.workerNum <= 1 // keep the last worker running if !keepWorking { q.workerNum-- } diff --git a/modules/queue/workerqueue.go b/modules/queue/workerqueue.go index b28fd88027..4160622d81 100644 --- a/modules/queue/workerqueue.go +++ b/modules/queue/workerqueue.go @@ -40,6 +40,8 @@ type WorkerPoolQueue[T any] struct { workerMaxNum int workerActiveNum int workerNumMu sync.Mutex + + workerStartedCounter int32 } type flushType chan struct{} diff --git a/modules/queue/workerqueue_test.go b/modules/queue/workerqueue_test.go index e60120162a..e09669c542 100644 --- a/modules/queue/workerqueue_test.go +++ b/modules/queue/workerqueue_test.go @@ -11,6 +11,7 @@ import ( "time" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" ) @@ -175,11 +176,7 @@ func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSett } func TestWorkerPoolQueueActiveWorkers(t *testing.T) { - oldWorkerIdleDuration := workerIdleDuration - workerIdleDuration = 300 * time.Millisecond - defer func() { - workerIdleDuration = oldWorkerIdleDuration - }() + defer test.MockVariableValue(&workerIdleDuration, 300*time.Millisecond)() handler := func(items ...int) (unhandled []int) { time.Sleep(100 * time.Millisecond) @@ -250,3 +247,25 @@ func TestWorkerPoolQueueShutdown(t *testing.T) { q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", qs, handler, false) assert.EqualValues(t, 20, q.GetQueueItemNumber()) } + +func TestWorkerPoolQueueWorkerIdleReset(t *testing.T) { + defer test.MockVariableValue(&workerIdleDuration, 10*time.Millisecond)() + + handler := func(items ...int) (unhandled []int) { + time.Sleep(50 * time.Millisecond) + return nil + } + + q, _ := newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 2, Length: 100}, handler, false) + stop := runWorkerPoolQueue(q) + for i := 0; i < 20; i++ { + assert.NoError(t, q.Push(i)) + } + + time.Sleep(500 * time.Millisecond) + assert.EqualValues(t, 2, q.GetWorkerNumber()) + assert.EqualValues(t, 2, q.GetWorkerActiveNumber()) + // when the queue never becomes empty, the existing workers should keep working + assert.EqualValues(t, 2, q.workerStartedCounter) + stop() +} diff --git a/modules/references/references.go b/modules/references/references.go index 68662425cc..6f6f90f6df 100644 --- a/modules/references/references.go +++ b/modules/references/references.go @@ -31,9 +31,9 @@ var ( // mentionPattern matches all mentions in the form of "@user" or "@org/team" mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_]+\/?[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_][0-9a-zA-Z-_.]+\/?[0-9a-zA-Z-_.]+[0-9a-zA-Z-_])(?:\s|[:,;.?!]\s|[:,;.?!]?$|\)|\])`) // issueNumericPattern matches string that references to a numeric issue, e.g. #1287 - issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\')([#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) + issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\")([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$)`) // issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234 - issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$))`) + issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\"|\')([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$)|\"|\')`) // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository // e.g. org/repo#12345 crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) diff --git a/modules/references/references_test.go b/modules/references/references_test.go index b60d6b0459..e332613619 100644 --- a/modules/references/references_test.go +++ b/modules/references/references_test.go @@ -429,6 +429,8 @@ func TestRegExp_issueNumericPattern(t *testing.T) { " #12", "#12:", "ref: #12: msg", + "\"#1234\"", + "'#1234'", } falseTestCases := []string{ "# 1234", @@ -459,6 +461,8 @@ func TestRegExp_issueAlphanumericPattern(t *testing.T) { "(ABC-123)", "[ABC-123]", "ABC-123:", + "\"ABC-123\"", + "'ABC-123'", } falseTestCases := []string{ "RC-08", diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go index 0931092597..827b2a9849 100644 --- a/modules/repository/commits_test.go +++ b/modules/repository/commits_test.go @@ -12,7 +12,6 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" - system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" @@ -103,12 +102,6 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { assert.EqualValues(t, []string{"readme.md"}, headCommit.Modified) } -func initGravatarSource(t *testing.T) { - setting.GravatarSource = "https://secure.gravatar.com/avatar" - err := system_model.Init(db.DefaultContext) - assert.NoError(t, err) -} - func TestPushCommits_AvatarLink(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) @@ -132,7 +125,7 @@ func TestPushCommits_AvatarLink(t *testing.T) { }, } - initGravatarSource(t) + setting.GravatarSource = "https://secure.gravatar.com/avatar" assert.Equal(t, "https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon&s="+strconv.Itoa(28*setting.Avatar.RenderedSizeFactor), diff --git a/modules/repository/create.go b/modules/repository/create.go index 2dac35224e..7c954a1412 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -160,24 +160,25 @@ const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | o // getDirectorySize returns the disk consumption for a given path func getDirectorySize(path string) (int64, error) { var size int64 - err := filepath.WalkDir(path, func(_ string, info os.DirEntry, err error) error { - if err != nil { - if os.IsNotExist(err) { // ignore the error because the file maybe deleted during traversing. - return nil - } + err := filepath.WalkDir(path, func(_ string, entry os.DirEntry, err error) error { + if os.IsNotExist(err) { // ignore the error because some files (like temp/lock file) may be deleted during traversing. + return nil + } else if err != nil { return err } - if info.IsDir() { + if entry.IsDir() { return nil } - f, err := info.Info() - if err != nil { + info, err := entry.Info() + if os.IsNotExist(err) { // ignore the error as above + return nil + } else if err != nil { return err } - if (f.Mode() & notRegularFileMode) == 0 { - size += f.Size() + if (info.Mode() & notRegularFileMode) == 0 { + size += info.Size() } - return err + return nil }) return size, err } diff --git a/modules/repository/delete.go b/modules/repository/delete.go index 72c0dc9135..43c52c933d 100644 --- a/modules/repository/delete.go +++ b/modules/repository/delete.go @@ -4,19 +4,20 @@ package repository import ( - "code.gitea.io/gitea/models/db" + "context" + "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" ) // CanUserDelete returns true if user could delete the repository -func CanUserDelete(repo *repo_model.Repository, user *user_model.User) (bool, error) { +func CanUserDelete(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) { if user.IsAdmin || user.ID == repo.OwnerID { return true, nil } - if err := repo.LoadOwner(db.DefaultContext); err != nil { + if err := repo.LoadOwner(ctx); err != nil { return false, err } diff --git a/modules/repository/hooks.go b/modules/repository/hooks.go index daab7c3091..95849789ab 100644 --- a/modules/repository/hooks.go +++ b/modules/repository/hooks.go @@ -9,7 +9,6 @@ import ( "path/filepath" "runtime" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" ) @@ -94,15 +93,14 @@ done `, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf)), } - if git.SupportProcReceive { - hookNames = append(hookNames, "proc-receive") - hookTpls = append(hookTpls, - fmt.Sprintf(`#!/usr/bin/env %s + // although only new git (>=2.29) supports proc-receive, it's still good to create its hook, in case the user upgrades git + hookNames = append(hookNames, "proc-receive") + hookTpls = append(hookTpls, + fmt.Sprintf(`#!/usr/bin/env %s # AUTO GENERATED BY GITEA, DO NOT MODIFY %s hook --config=%s proc-receive `, setting.ScriptType, util.ShellEscape(setting.AppPath), util.ShellEscape(setting.CustomConf))) - giteaHookTpls = append(giteaHookTpls, "") - } + giteaHookTpls = append(giteaHookTpls, "") return hookNames, hookTpls, giteaHookTpls } diff --git a/modules/repository/repo.go b/modules/repository/repo.go index b50c8f75ca..1387cee66d 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -159,7 +159,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, // note: this will greatly improve release (tag) sync // for pull-mirrors with many tags repo.IsMirror = opts.Mirror - if err = SyncReleasesWithTags(repo, gitRepo); err != nil { + if err = SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { log.Error("Failed to synchronize tags to releases for repository: %v", err) } } @@ -285,13 +285,13 @@ func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo } // SyncReleasesWithTags synchronizes release table with repository tags -func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository) error { +func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) error { log.Debug("SyncReleasesWithTags: in Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name) // optimized procedure for pull-mirrors which saves a lot of time (in // particular for repos with many tags). if repo.IsMirror { - return pullMirrorReleaseSync(repo, gitRepo) + return pullMirrorReleaseSync(ctx, repo, gitRepo) } existingRelTags := make(container.Set[string]) @@ -318,7 +318,7 @@ func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository) return fmt.Errorf("unable to GetTagCommitID for %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err) } if git.IsErrNotExist(err) || commitID != rel.Sha1 { - if err := repo_model.PushUpdateDeleteTag(repo, rel.TagName); err != nil { + if err := repo_model.PushUpdateDeleteTag(ctx, repo, rel.TagName); err != nil { return fmt.Errorf("unable to PushUpdateDeleteTag: %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err) } } else { @@ -333,8 +333,10 @@ func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository) return nil } - if err := PushUpdateAddTag(db.DefaultContext, repo, gitRepo, tagName, sha1, refname); err != nil { - return fmt.Errorf("unable to PushUpdateAddTag: %q to Repo[%d:%s/%s]: %w", tagName, repo.ID, repo.OwnerName, repo.Name, err) + if err := PushUpdateAddTag(ctx, repo, gitRepo, tagName, sha1, refname); err != nil { + // sometimes, some tags will be sync failed. i.e. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tag/?h=v2.6.11 + // this is a tree object, not a tag object which created before git + log.Error("unable to PushUpdateAddTag: %q to Repo[%d:%s/%s]: %v", tagName, repo.ID, repo.OwnerName, repo.Name, err) } return nil @@ -390,7 +392,7 @@ func PushUpdateAddTag(ctx context.Context, repo *repo_model.Repository, gitRepo rel.PublisherID = author.ID } - return repo_model.SaveOrUpdateTag(repo, &rel) + return repo_model.SaveOrUpdateTag(ctx, repo, &rel) } // StoreMissingLfsObjectsInRepository downloads missing LFS objects @@ -491,29 +493,47 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re return nil } +// shortRelease to reduce load memory, this struct can replace repo_model.Release +type shortRelease struct { + ID int64 + TagName string + Sha1 string + IsTag bool +} + +func (shortRelease) TableName() string { + return "release" +} + // pullMirrorReleaseSync is a pull-mirror specific tag<->release table // synchronization which overwrites all Releases from the repository tags. This // can be relied on since a pull-mirror is always identical to its // upstream. Hence, after each sync we want the pull-mirror release set to be // identical to the upstream tag set. This is much more efficient for // repositories like https://github.com/vim/vim (with over 13000 tags). -func pullMirrorReleaseSync(repo *repo_model.Repository, gitRepo *git.Repository) error { +func pullMirrorReleaseSync(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) error { log.Trace("pullMirrorReleaseSync: rebuilding releases for pull-mirror Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name) tags, numTags, err := gitRepo.GetTagInfos(0, 0) if err != nil { return fmt.Errorf("unable to GetTagInfos in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err) } - err = db.WithTx(db.DefaultContext, func(ctx context.Context) error { - // - // clear out existing releases - // - if _, err := db.DeleteByBean(ctx, &repo_model.Release{RepoID: repo.ID}); err != nil { - return fmt.Errorf("unable to clear releases for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err) + err = db.WithTx(ctx, func(ctx context.Context) error { + dbReleases := make([]*shortRelease, 0, len(tags)) + err := db.Find(ctx, &repo_model.FindReleasesOptions{ + ListOptions: db.ListOptions{ListAll: true}, + RepoID: repo.ID, + IncludeDrafts: true, + IncludeTags: true, + }, &dbReleases) + if err != nil { + return fmt.Errorf("unable to FindReleases in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err) } + + inserts, deletes, updates := calcSync(tags, dbReleases) // // make release set identical to upstream tags // - for _, tag := range tags { + for _, tag := range inserts { release := repo_model.Release{ RepoID: repo.ID, TagName: tag.Name, @@ -530,6 +550,25 @@ func pullMirrorReleaseSync(repo *repo_model.Repository, gitRepo *git.Repository) return fmt.Errorf("unable insert tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err) } } + + // only delete tags releases + if len(deletes) > 0 { + if _, err := db.GetEngine(ctx).Where("repo_id=?", repo.ID). + In("id", deletes). + Delete(&repo_model.Release{}); err != nil { + return fmt.Errorf("unable to delete tags for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err) + } + } + + for _, tag := range updates { + if _, err := db.GetEngine(ctx).Where("repo_id = ? AND lower_tag_name = ?", repo.ID, strings.ToLower(tag.Name)). + Cols("sha1"). + Update(&repo_model.Release{ + Sha1: tag.Object.String(), + }); err != nil { + return fmt.Errorf("unable to update tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err) + } + } return nil }) if err != nil { @@ -539,3 +578,32 @@ func pullMirrorReleaseSync(repo *repo_model.Repository, gitRepo *git.Repository) log.Trace("pullMirrorReleaseSync: done rebuilding %d releases", numTags) return nil } + +func calcSync(destTags []*git.Tag, dbTags []*shortRelease) ([]*git.Tag, []int64, []*git.Tag) { + destTagMap := make(map[string]*git.Tag) + for _, tag := range destTags { + destTagMap[tag.Name] = tag + } + dbTagMap := make(map[string]*shortRelease) + for _, rel := range dbTags { + dbTagMap[rel.TagName] = rel + } + + inserted := make([]*git.Tag, 0, 10) + updated := make([]*git.Tag, 0, 10) + for _, tag := range destTags { + rel := dbTagMap[tag.Name] + if rel == nil { + inserted = append(inserted, tag) + } else if rel.Sha1 != tag.Object.String() { + updated = append(updated, tag) + } + } + deleted := make([]int64, 0, 10) + for _, tag := range dbTags { + if destTagMap[tag.TagName] == nil && tag.IsTag { + deleted = append(deleted, tag.ID) + } + } + return inserted, deleted, updated +} diff --git a/modules/repository/repo_test.go b/modules/repository/repo_test.go new file mode 100644 index 0000000000..68980f92f9 --- /dev/null +++ b/modules/repository/repo_test.go @@ -0,0 +1,76 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "testing" + + "code.gitea.io/gitea/modules/git" + + "github.com/stretchr/testify/assert" +) + +func Test_calcSync(t *testing.T) { + gitTags := []*git.Tag{ + /*{ + Name: "v0.1.0-beta", //deleted tag + Object: git.MustIDFromString(""), + }, + { + Name: "v0.1.1-beta", //deleted tag but release should not be deleted because it's a release + Object: git.MustIDFromString(""), + }, + */ + { + Name: "v1.0.0", // keep as before + Object: git.MustIDFromString("1006e6e13c73ad3d9e2d5682ad266b5016523485"), + }, + { + Name: "v1.1.0", // retagged with new commit id + Object: git.MustIDFromString("bbdb7df30248e7d4a26a909c8d2598a152e13868"), + }, + { + Name: "v1.2.0", // new tag + Object: git.MustIDFromString("a5147145e2f24d89fd6d2a87826384cc1d253267"), + }, + } + + dbReleases := []*shortRelease{ + { + ID: 1, + TagName: "v0.1.0-beta", + Sha1: "244758d7da8dd1d9e0727e8cb7704ed4ba9a17c3", + IsTag: true, + }, + { + ID: 2, + TagName: "v0.1.1-beta", + Sha1: "244758d7da8dd1d9e0727e8cb7704ed4ba9a17c3", + IsTag: false, + }, + { + ID: 3, + TagName: "v1.0.0", + Sha1: "1006e6e13c73ad3d9e2d5682ad266b5016523485", + }, + { + ID: 4, + TagName: "v1.1.0", + Sha1: "53ab18dcecf4152b58328d1f47429510eb414d50", + }, + } + + inserts, deletes, updates := calcSync(gitTags, dbReleases) + if assert.EqualValues(t, 1, len(inserts), "inserts") { + assert.EqualValues(t, *gitTags[2], *inserts[0], "inserts equal") + } + + if assert.EqualValues(t, 1, len(deletes), "deletes") { + assert.EqualValues(t, 1, deletes[0], "deletes equal") + } + + if assert.EqualValues(t, 1, len(updates), "updates") { + assert.EqualValues(t, *gitTags[1], *updates[0], "updates equal") + } +} diff --git a/modules/setting/actions.go b/modules/setting/actions.go index 28c84f0149..026bab4bfc 100644 --- a/modules/setting/actions.go +++ b/modules/setting/actions.go @@ -6,6 +6,7 @@ package setting import ( "fmt" "strings" + "time" "code.gitea.io/gitea/modules/log" ) @@ -18,6 +19,9 @@ var ( ArtifactRetentionDays int64 `ini:"ARTIFACT_RETENTION_DAYS"` Enabled bool DefaultActionsURL defaultActionsURL `ini:"DEFAULT_ACTIONS_URL"` + ZombieTaskTimeout time.Duration `ini:"ZOMBIE_TASK_TIMEOUT"` + EndlessTaskTimeout time.Duration `ini:"ENDLESS_TASK_TIMEOUT"` + AbandonedJobTimeout time.Duration `ini:"ABANDONED_JOB_TIMEOUT"` }{ Enabled: true, DefaultActionsURL: defaultActionsURLGitHub, @@ -82,5 +86,9 @@ func loadActionsFrom(rootCfg ConfigProvider) error { Actions.ArtifactRetentionDays = 90 } + Actions.ZombieTaskTimeout = sec.Key("ZOMBIE_TASK_TIMEOUT").MustDuration(10 * time.Minute) + Actions.EndlessTaskTimeout = sec.Key("ENDLESS_TASK_TIMEOUT").MustDuration(3 * time.Hour) + Actions.AbandonedJobTimeout = sec.Key("ABANDONED_JOB_TIMEOUT").MustDuration(24 * time.Hour) + return err } diff --git a/modules/setting/attachment.go b/modules/setting/attachment.go index 491564c9dc..934d4d7f46 100644 --- a/modules/setting/attachment.go +++ b/modules/setting/attachment.go @@ -13,7 +13,7 @@ var Attachment = struct { }{ Storage: &Storage{}, AllowedTypes: ".csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip", - MaxSize: 4, + MaxSize: 2048, MaxFiles: 5, Enabled: true, } @@ -26,7 +26,7 @@ func loadAttachmentFrom(rootCfg ConfigProvider) (err error) { } Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip") - Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(4) + Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(2048) Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5) Attachment.Enabled = sec.Key("ENABLED").MustBool(true) diff --git a/modules/setting/config.go b/modules/setting/config.go new file mode 100644 index 0000000000..db189f44ac --- /dev/null +++ b/modules/setting/config.go @@ -0,0 +1,55 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "sync" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting/config" +) + +type PictureStruct struct { + DisableGravatar *config.Value[bool] + EnableFederatedAvatar *config.Value[bool] +} + +type ConfigStruct struct { + Picture *PictureStruct +} + +var ( + defaultConfig *ConfigStruct + defaultConfigOnce sync.Once +) + +func initDefaultConfig() { + config.SetCfgSecKeyGetter(&cfgSecKeyGetter{}) + defaultConfig = &ConfigStruct{ + Picture: &PictureStruct{ + DisableGravatar: config.Bool(false, config.CfgSecKey{Sec: "picture", Key: "DISABLE_GRAVATAR"}, "picture.disable_gravatar"), + EnableFederatedAvatar: config.Bool(false, config.CfgSecKey{Sec: "picture", Key: "ENABLE_FEDERATED_AVATAR"}, "picture.enable_federated_avatar"), + }, + } +} + +func Config() *ConfigStruct { + defaultConfigOnce.Do(initDefaultConfig) + return defaultConfig +} + +type cfgSecKeyGetter struct{} + +func (c cfgSecKeyGetter) GetValue(sec, key string) (v string, has bool) { + cfgSec, err := CfgProvider.GetSection(sec) + if err != nil { + log.Error("Unable to get config section: %q", sec) + return "", false + } + cfgKey := ConfigSectionKey(cfgSec, key) + if cfgKey == nil { + return "", false + } + return cfgKey.Value(), true +} diff --git a/modules/setting/config/getter.go b/modules/setting/config/getter.go new file mode 100644 index 0000000000..99f9a4775a --- /dev/null +++ b/modules/setting/config/getter.go @@ -0,0 +1,49 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package config + +import ( + "context" + "sync" +) + +var getterMu sync.RWMutex + +type CfgSecKeyGetter interface { + GetValue(sec, key string) (v string, has bool) +} + +var cfgSecKeyGetterInternal CfgSecKeyGetter + +func SetCfgSecKeyGetter(p CfgSecKeyGetter) { + getterMu.Lock() + cfgSecKeyGetterInternal = p + getterMu.Unlock() +} + +func GetCfgSecKeyGetter() CfgSecKeyGetter { + getterMu.RLock() + defer getterMu.RUnlock() + return cfgSecKeyGetterInternal +} + +type DynKeyGetter interface { + GetValue(ctx context.Context, key string) (v string, has bool) + GetRevision(ctx context.Context) int + InvalidateCache() +} + +var dynKeyGetterInternal DynKeyGetter + +func SetDynGetter(p DynKeyGetter) { + getterMu.Lock() + dynKeyGetterInternal = p + getterMu.Unlock() +} + +func GetDynGetter() DynKeyGetter { + getterMu.RLock() + defer getterMu.RUnlock() + return dynKeyGetterInternal +} diff --git a/modules/setting/config/value.go b/modules/setting/config/value.go new file mode 100644 index 0000000000..817fcdb786 --- /dev/null +++ b/modules/setting/config/value.go @@ -0,0 +1,81 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package config + +import ( + "context" + "strconv" + "sync" +) + +type CfgSecKey struct { + Sec, Key string +} + +type Value[T any] struct { + mu sync.RWMutex + + cfgSecKey CfgSecKey + dynKey string + + def, value T + revision int +} + +func (value *Value[T]) parse(s string) (v T) { + switch any(v).(type) { + case bool: + b, _ := strconv.ParseBool(s) + return any(b).(T) + default: + panic("unsupported config type, please complete the code") + } +} + +func (value *Value[T]) Value(ctx context.Context) (v T) { + dg := GetDynGetter() + if dg == nil { + // this is an edge case: the database is not initialized but the system setting is going to be used + // it should panic to avoid inconsistent config values (from config / system setting) and fix the code + panic("no config dyn value getter") + } + + rev := dg.GetRevision(ctx) + + // if the revision in database doesn't change, use the last value + value.mu.RLock() + if rev == value.revision { + v = value.value + value.mu.RUnlock() + return v + } + value.mu.RUnlock() + + // try to parse the config and cache it + var valStr *string + if dynVal, has := dg.GetValue(ctx, value.dynKey); has { + valStr = &dynVal + } else if cfgVal, has := GetCfgSecKeyGetter().GetValue(value.cfgSecKey.Sec, value.cfgSecKey.Key); has { + valStr = &cfgVal + } + if valStr == nil { + v = value.def + } else { + v = value.parse(*valStr) + } + + value.mu.Lock() + value.value = v + value.revision = rev + value.mu.Unlock() + return v +} + +func (value *Value[T]) DynKey() string { + return value.dynKey +} + +func Bool(def bool, cfgSecKey CfgSecKey, dynKey string) *Value[bool] { + return &Value[bool]{def: def, cfgSecKey: cfgSecKey, dynKey: dynKey} +} diff --git a/modules/setting/config_env.go b/modules/setting/config_env.go index b30e44de30..242f40914a 100644 --- a/modules/setting/config_env.go +++ b/modules/setting/config_env.go @@ -149,8 +149,9 @@ func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) { continue } } - key := section.Key(keyName) + key := ConfigSectionKey(section, keyName) if key == nil { + changed = true key, err = section.NewKey(keyName, keyValue) if err != nil { log.Error("Error creating key: %s in section: %s with value: %s : %v", keyName, sectionName, keyValue, err) diff --git a/modules/setting/config_env_test.go b/modules/setting/config_env_test.go index e14d5ecb41..7d07c479a1 100644 --- a/modules/setting/config_env_test.go +++ b/modules/setting/config_env_test.go @@ -115,3 +115,29 @@ key = old EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile}) assert.Equal(t, "value-from-file\n", cfg.Section("sec").Key("key").String()) } + +func TestEnvironmentToConfigSubSecKey(t *testing.T) { + // the INI package has a quirk: by default, the keys are inherited. + // when maintaining the keys, the newly added sub key should not be affected by the parent key. + cfg, err := NewConfigProviderFromData(` +[sec] +key = some +`) + assert.NoError(t, err) + + changed := EnvironmentToConfig(cfg, []string{"GITEA__sec_0X2E_sub__key=some"}) + assert.True(t, changed) + + tmpFile := t.TempDir() + "/test-sub-sec-key.ini" + defer os.Remove(tmpFile) + err = cfg.SaveTo(tmpFile) + assert.NoError(t, err) + bs, err := os.ReadFile(tmpFile) + assert.NoError(t, err) + assert.Equal(t, `[sec] +key = some + +[sec.sub] +key = some +`, string(bs)) +} diff --git a/modules/setting/cors.go b/modules/setting/cors.go index bafbbab64f..63daaad60b 100644 --- a/modules/setting/cors.go +++ b/modules/setting/cors.go @@ -12,9 +12,7 @@ import ( // CORSConfig defines CORS settings var CORSConfig = struct { Enabled bool - Scheme string - AllowDomain []string - AllowSubdomain bool + AllowDomain []string // FIXME: this option is from legacy code, it actually works as "AllowedOrigins". When refactoring in the future, the config option should also be renamed together. Methods []string MaxAge time.Duration AllowCredentials bool diff --git a/modules/setting/database.go b/modules/setting/database.go index 709655368c..23747c9d57 100644 --- a/modules/setting/database.go +++ b/modules/setting/database.go @@ -6,9 +6,9 @@ package setting import ( "errors" "fmt" + "net" "net/url" "os" - "path" "path/filepath" "strings" "time" @@ -108,7 +108,7 @@ func DBConnStr() (string, error) { connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=%s&parseTime=true&tls=%s", Database.User, Database.Passwd, connType, Database.Host, Database.Name, paramSep, Database.MysqlCharset, tls) case "postgres": - connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, paramSep, Database.SSLMode) + connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, Database.SSLMode) case "mssql": host, port := ParseMSSQLHostPort(Database.Host) connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, Database.Name, Database.User, Database.Passwd) @@ -116,7 +116,7 @@ func DBConnStr() (string, error) { if !EnableSQLite3 { return "", errors.New("this Gitea binary was not built with SQLite3 support") } - if err := os.MkdirAll(path.Dir(Database.Path), os.ModePerm); err != nil { + if err := os.MkdirAll(filepath.Dir(Database.Path), os.ModePerm); err != nil { return "", fmt.Errorf("Failed to create directories: %w", err) } journalMode := "" @@ -135,15 +135,18 @@ func DBConnStr() (string, error) { // parsePostgreSQLHostPort parses given input in various forms defined in // https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING // and returns proper host and port number. -func parsePostgreSQLHostPort(info string) (string, string) { - host, port := "127.0.0.1", "5432" - if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") { - idx := strings.LastIndex(info, ":") - host = info[:idx] - port = info[idx+1:] - } else if len(info) > 0 { +func parsePostgreSQLHostPort(info string) (host, port string) { + if h, p, err := net.SplitHostPort(info); err == nil { + host, port = h, p + } else { + // treat the "info" as "host", if it's an IPv6 address, remove the wrapper host = info + if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { + host = host[1 : len(host)-1] + } } + + // set fallback values if host == "" { host = "127.0.0.1" } @@ -153,16 +156,25 @@ func parsePostgreSQLHostPort(info string) (string, string) { return host, port } -func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbParam, dbsslMode string) (connStr string) { +func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbsslMode string) (connStr string) { + dbName, dbParam, _ := strings.Cut(dbName, "?") host, port := parsePostgreSQLHostPort(dbHost) - if host[0] == '/' { // looks like a unix socket - connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s", - url.PathEscape(dbUser), url.PathEscape(dbPasswd), port, dbName, dbParam, dbsslMode, host) - } else { - connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s", - url.PathEscape(dbUser), url.PathEscape(dbPasswd), host, port, dbName, dbParam, dbsslMode) + connURL := url.URL{ + Scheme: "postgres", + User: url.UserPassword(dbUser, dbPasswd), + Host: net.JoinHostPort(host, port), + Path: dbName, + OmitHost: false, + RawQuery: dbParam, } - return connStr + query := connURL.Query() + if strings.HasPrefix(host, "/") { // looks like a unix socket + query.Add("host", host) + connURL.Host = ":" + port + } + query.Set("sslmode", dbsslMode) + connURL.RawQuery = query.Encode() + return connURL.String() } // ParseMSSQLHostPort splits the host into host and port diff --git a/modules/setting/database_test.go b/modules/setting/database_test.go index 481ca969b1..a742d54f8c 100644 --- a/modules/setting/database_test.go +++ b/modules/setting/database_test.go @@ -10,84 +10,100 @@ import ( ) func Test_parsePostgreSQLHostPort(t *testing.T) { - tests := []struct { + tests := map[string]struct { HostPort string Host string Port string }{ - { + "host-port": { HostPort: "127.0.0.1:1234", Host: "127.0.0.1", Port: "1234", }, - { + "no-port": { HostPort: "127.0.0.1", Host: "127.0.0.1", Port: "5432", }, - { + "ipv6-port": { HostPort: "[::1]:1234", - Host: "[::1]", + Host: "::1", Port: "1234", }, - { + "ipv6-no-port": { HostPort: "[::1]", - Host: "[::1]", + Host: "::1", Port: "5432", }, - { + "unix-socket": { HostPort: "/tmp/pg.sock:1234", Host: "/tmp/pg.sock", Port: "1234", }, - { + "unix-socket-no-port": { HostPort: "/tmp/pg.sock", Host: "/tmp/pg.sock", Port: "5432", }, } - for _, test := range tests { - host, port := parsePostgreSQLHostPort(test.HostPort) - assert.Equal(t, test.Host, host) - assert.Equal(t, test.Port, port) + for k, test := range tests { + t.Run(k, func(t *testing.T) { + t.Log(test.HostPort) + host, port := parsePostgreSQLHostPort(test.HostPort) + assert.Equal(t, test.Host, host) + assert.Equal(t, test.Port, port) + }) } } func Test_getPostgreSQLConnectionString(t *testing.T) { tests := []struct { Host string - Port string User string Passwd string Name string - Param string SSLMode string Output string }{ + { + Host: "", // empty means default + Output: "postgres://:@127.0.0.1:5432?sslmode=", + }, { Host: "/tmp/pg.sock", - Port: "4321", User: "testuser", Passwd: "space space !#$%^^%^```-=?=", Name: "gitea", - Param: "", SSLMode: "false", - Output: "postgres://testuser:space%20space%20%21%23$%25%5E%5E%25%5E%60%60%60-=%3F=@:5432/giteasslmode=false&host=/tmp/pg.sock", + Output: "postgres://testuser:space%20space%20%21%23$%25%5E%5E%25%5E%60%60%60-=%3F=@:5432/gitea?host=%2Ftmp%2Fpg.sock&sslmode=false", + }, + { + Host: "/tmp/pg.sock:6432", + User: "testuser", + Passwd: "pass", + Name: "gitea", + SSLMode: "false", + Output: "postgres://testuser:pass@:6432/gitea?host=%2Ftmp%2Fpg.sock&sslmode=false", }, { Host: "localhost", - Port: "1234", User: "pgsqlusername", Passwd: "I love Gitea!", Name: "gitea", - Param: "", SSLMode: "true", - Output: "postgres://pgsqlusername:I%20love%20Gitea%21@localhost:5432/giteasslmode=true", + Output: "postgres://pgsqlusername:I%20love%20Gitea%21@localhost:5432/gitea?sslmode=true", + }, + { + Host: "localhost:1234", + User: "user", + Passwd: "pass", + Name: "gitea?param=1", + Output: "postgres://user:pass@localhost:1234/gitea?param=1&sslmode=", }, } for _, test := range tests { - connStr := getPostgreSQLConnectionString(test.Host, test.User, test.Passwd, test.Name, test.Param, test.SSLMode) + connStr := getPostgreSQLConnectionString(test.Host, test.User, test.Passwd, test.Name, test.SSLMode) assert.Equal(t, test.Output, connStr) } } diff --git a/modules/setting/lfs.go b/modules/setting/lfs.go index a5ea537cef..2034ef782c 100644 --- a/modules/setting/lfs.go +++ b/modules/setting/lfs.go @@ -4,22 +4,19 @@ package setting import ( - "encoding/base64" "fmt" "time" "code.gitea.io/gitea/modules/generate" - "code.gitea.io/gitea/modules/util" ) // LFS represents the configuration for Git LFS var LFS = struct { - StartServer bool `ini:"LFS_START_SERVER"` - JWTSecretBase64 string `ini:"LFS_JWT_SECRET"` - JWTSecretBytes []byte `ini:"-"` - HTTPAuthExpiry time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"` - MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"` - LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"` + StartServer bool `ini:"LFS_START_SERVER"` + JWTSecretBytes []byte `ini:"-"` + HTTPAuthExpiry time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"` + MaxFileSize int64 `ini:"LFS_MAX_FILE_SIZE"` + LocksPagingNum int `ini:"LFS_LOCKS_PAGING_NUM"` Storage *Storage }{} @@ -61,10 +58,10 @@ func loadLFSFrom(rootCfg ConfigProvider) error { return nil } - LFS.JWTSecretBase64 = loadSecret(rootCfg.Section("server"), "LFS_JWT_SECRET_URI", "LFS_JWT_SECRET") - LFS.JWTSecretBytes, err = util.Base64FixedDecode(base64.RawURLEncoding, []byte(LFS.JWTSecretBase64), 32) + jwtSecretBase64 := loadSecret(rootCfg.Section("server"), "LFS_JWT_SECRET_URI", "LFS_JWT_SECRET") + LFS.JWTSecretBytes, err = generate.DecodeJwtSecretBase64(jwtSecretBase64) if err != nil { - LFS.JWTSecretBytes, LFS.JWTSecretBase64, err = generate.NewJwtSecretBase64() + LFS.JWTSecretBytes, jwtSecretBase64, err = generate.NewJwtSecretWithBase64() if err != nil { return fmt.Errorf("error generating JWT Secret for custom config: %v", err) } @@ -74,8 +71,8 @@ func loadLFSFrom(rootCfg ConfigProvider) error { if err != nil { return fmt.Errorf("error saving JWT Secret for custom config: %v", err) } - rootCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64) - saveCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64) + rootCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(jwtSecretBase64) + saveCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(jwtSecretBase64) if err := saveCfg.Save(); err != nil { return fmt.Errorf("error saving JWT Secret for custom config: %v", err) } diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index ab82393106..847e0f1ee8 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -4,13 +4,12 @@ package setting import ( - "encoding/base64" "math" "path/filepath" + "sync/atomic" "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" ) // OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data @@ -98,7 +97,6 @@ var OAuth2 = struct { RefreshTokenExpirationTime int64 InvalidateRefreshTokens bool JWTSigningAlgorithm string `ini:"JWT_SIGNING_ALGORITHM"` - JWTSecretBase64 string `ini:"JWT_SECRET"` JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"` MaxTokenLength int DefaultApplications []string @@ -123,29 +121,50 @@ func loadOAuth2From(rootCfg ConfigProvider) { return } - OAuth2.JWTSecretBase64 = loadSecret(rootCfg.Section("oauth2"), "JWT_SECRET_URI", "JWT_SECRET") + jwtSecretBase64 := loadSecret(rootCfg.Section("oauth2"), "JWT_SECRET_URI", "JWT_SECRET") if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) { OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile) } if InstallLock { - if _, err := util.Base64FixedDecode(base64.RawURLEncoding, []byte(OAuth2.JWTSecretBase64), 32); err != nil { - key, err := generate.NewJwtSecret() + jwtSecretBytes, err := generate.DecodeJwtSecretBase64(jwtSecretBase64) + if err != nil { + jwtSecretBytes, jwtSecretBase64, err = generate.NewJwtSecretWithBase64() if err != nil { log.Fatal("error generating JWT secret: %v", err) } - - OAuth2.JWTSecretBase64 = base64.RawURLEncoding.EncodeToString(key) saveCfg, err := rootCfg.PrepareSaving() if err != nil { log.Fatal("save oauth2.JWT_SECRET failed: %v", err) } - rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(OAuth2.JWTSecretBase64) - saveCfg.Section("oauth2").Key("JWT_SECRET").SetValue(OAuth2.JWTSecretBase64) + rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(jwtSecretBase64) + saveCfg.Section("oauth2").Key("JWT_SECRET").SetValue(jwtSecretBase64) if err := saveCfg.Save(); err != nil { log.Fatal("save oauth2.JWT_SECRET failed: %v", err) } } + generalSigningSecret.Store(&jwtSecretBytes) } } + +// generalSigningSecret is used as container for a []byte value +// instead of an additional mutex, we use CompareAndSwap func to change the value thread save +var generalSigningSecret atomic.Pointer[[]byte] + +func GetGeneralTokenSigningSecret() []byte { + old := generalSigningSecret.Load() + if old == nil || len(*old) == 0 { + jwtSecret, _, err := generate.NewJwtSecretWithBase64() + if err != nil { + log.Fatal("Unable to generate general JWT secret: %s", err.Error()) + } + if generalSigningSecret.CompareAndSwap(old, &jwtSecret) { + // FIXME: in main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...) + log.Warn("OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes") + return jwtSecret + } + return *generalSigningSecret.Load() + } + return *old +} diff --git a/modules/setting/oauth2_test.go b/modules/setting/oauth2_test.go new file mode 100644 index 0000000000..d822198619 --- /dev/null +++ b/modules/setting/oauth2_test.go @@ -0,0 +1,34 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "testing" + + "code.gitea.io/gitea/modules/generate" + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" +) + +func TestGetGeneralSigningSecret(t *testing.T) { + // when there is no general signing secret, it should be generated, and keep the same value + assert.Nil(t, generalSigningSecret.Load()) + s1 := GetGeneralTokenSigningSecret() + assert.NotNil(t, s1) + s2 := GetGeneralTokenSigningSecret() + assert.Equal(t, s1, s2) + + // the config value should always override any pre-generated value + cfg, _ := NewConfigProviderFromData(` +[oauth2] +JWT_SECRET = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +`) + defer test.MockVariableValue(&InstallLock, true)() + loadOAuth2From(cfg) + actual := GetGeneralTokenSigningSecret() + expected, _ := generate.DecodeJwtSecretBase64("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB") + assert.Len(t, actual, 32) + assert.EqualValues(t, expected, actual) +} diff --git a/modules/setting/queue.go b/modules/setting/queue.go index fc15bd07ed..251a6c1e30 100644 --- a/modules/setting/queue.go +++ b/modules/setting/queue.go @@ -30,7 +30,7 @@ func GetQueueSettings(rootCfg ConfigProvider, name string) (QueueSettings, error queueSettingsDefault := QueueSettings{ Type: "level", // dummy, channel, level, redis Datadir: "queues/common", // relative to AppDataPath - Length: 100, // queue length before a channel queue will block + Length: 100000, // queue length before a channel queue will block QueueName: "_queue", SetName: "_unique", diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 42ffb99138..9697a851d3 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -184,7 +184,7 @@ var ( Enabled: true, TempPath: "data/tmp/uploads", AllowedTypes: "", - FileMaxSize: 3, + FileMaxSize: 50, MaxFiles: 5, }, diff --git a/modules/setting/security.go b/modules/setting/security.go index 90f614d4cd..01e02ea5a0 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -35,6 +35,7 @@ var ( PasswordHashAlgo string PasswordCheckPwn bool SuccessfulTokensCacheSize int + DisableQueryAuthToken bool CSRFCookieName = "_csrf" CSRFCookieHTTPOnly = true ) @@ -159,4 +160,14 @@ 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 default to true in gitea 1.23 and will be removed in gitea 1.24.") + } } diff --git a/modules/setting/server.go b/modules/setting/server.go index 08eb82fb3d..6c65fc1b9e 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -343,8 +343,7 @@ func loadServerFrom(rootCfg ConfigProvider) { LandingPageURL = LandingPageOrganizations case "login": LandingPageURL = LandingPageLogin - case "": - case "home": + case "", "home": LandingPageURL = LandingPageHome default: LandingPageURL = LandingPage(landingPage) diff --git a/modules/setting/session.go b/modules/setting/session.go index 664c66f869..e9637fdfc5 100644 --- a/modules/setting/session.go +++ b/modules/setting/session.go @@ -21,7 +21,7 @@ var SessionConfig = struct { ProviderConfig string // Cookie name to save session ID. Default is "MacaronSession". CookieName string - // Cookie path to store. Default is "/". HINT: there was a bug, the old value doesn't have trailing slash, and could be empty "". + // Cookie path to store. Default is "/". CookiePath string // GC interval time in seconds. Default is 3600. Gclifetime int64 @@ -49,7 +49,10 @@ func loadSessionFrom(rootCfg ConfigProvider) { SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig) } SessionConfig.CookieName = sec.Key("COOKIE_NAME").MustString("i_like_gitea") - SessionConfig.CookiePath = AppSubURL + "/" // there was a bug, old code only set CookePath=AppSubURL, no trailing slash + SessionConfig.CookiePath = AppSubURL + if SessionConfig.CookiePath == "" { + SessionConfig.CookiePath = "/" + } SessionConfig.Secure = sec.Key("COOKIE_SECURE").MustBool(strings.HasPrefix(strings.ToLower(AppURL), "https://")) SessionConfig.Gclifetime = sec.Key("GC_INTERVAL_TIME").MustInt64(86400) SessionConfig.Maxlifetime = sec.Key("SESSION_LIFE_TIME").MustInt64(86400) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index d444d9a017..2043d6cd7a 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/user" + "code.gitea.io/gitea/modules/util" ) // settings @@ -158,9 +159,11 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error { func loadRunModeFrom(rootCfg ConfigProvider) { rootSec := rootCfg.Section("") RunUser = rootSec.Key("RUN_USER").MustString(user.CurrentUsername()) + // The following is a purposefully undocumented option. Please do not run Gitea as root. It will only cause future headaches. // Please don't use root as a bandaid to "fix" something that is broken, instead the broken thing should instead be fixed properly. unsafeAllowRunAsRoot := ConfigSectionKeyBool(rootSec, "I_AM_BEING_UNSAFE_RUNNING_AS_ROOT") + unsafeAllowRunAsRoot = unsafeAllowRunAsRoot || util.OptionalBoolParse(os.Getenv("GITEA_I_AM_BEING_UNSAFE_RUNNING_AS_ROOT")).IsTrue() RunMode = os.Getenv("GITEA_RUN_MODE") if RunMode == "" { RunMode = rootSec.Key("RUN_MODE").MustString("prod") diff --git a/modules/setting/ui.go b/modules/setting/ui.go index 41438db40d..0a8bde9751 100644 --- a/modules/setting/ui.go +++ b/modules/setting/ui.go @@ -34,6 +34,8 @@ var UI = struct { SearchRepoDescription bool OnlyShowRelevantRepos bool + AmbiguousUnicodeDetection bool + Notification struct { MinTimeout time.Duration TimeoutStep time.Duration @@ -81,6 +83,9 @@ var UI = struct { Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`}, CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"}, + + AmbiguousUnicodeDetection: true, + Notification: struct { MinTimeout time.Duration TimeoutStep time.Duration diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go index 37624ab679..f8e4f569b8 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -17,7 +17,6 @@ import ( "os" "os/exec" "path/filepath" - "reflect" "strconv" "strings" "sync" @@ -165,10 +164,6 @@ func sessionHandler(session ssh.Session) { } func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { - // FIXME: the "ssh.Context" is not thread-safe, so db operations should use the immutable parent "Context" - // TODO: Remove after https://github.com/gliderlabs/ssh/pull/211 - parentCtx := reflect.ValueOf(ctx).Elem().FieldByName("Context").Interface().(context.Context) - 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()) } @@ -200,7 +195,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { // look for the exact principal principalLoop: for _, principal := range cert.ValidPrincipals { - pkey, err := asymkey_model.SearchPublicKeyByContentExact(parentCtx, principal) + 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) @@ -257,7 +252,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { log.Debug("Handle Public Key: %s Fingerprint: %s is not a certificate", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) } - pkey, err := asymkey_model.SearchPublicKeyByContent(parentCtx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(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()) diff --git a/modules/structs/admin_user.go b/modules/structs/admin_user.go index 4d679c81d0..f7c6d10ba0 100644 --- a/modules/structs/admin_user.go +++ b/modules/structs/admin_user.go @@ -15,9 +15,8 @@ type CreateUserOption struct { FullName string `json:"full_name" binding:"MaxSize(100)"` // required: true // swagger:strfmt email - Email string `json:"email" binding:"Required;Email;MaxSize(254)"` - // required: true - Password string `json:"password" binding:"Required;MaxSize(255)"` + Email string `json:"email" binding:"Required;Email;MaxSize(254)"` + Password string `json:"password" binding:"MaxSize(255)"` MustChangePassword *bool `json:"must_change_password"` SendNotify bool `json:"send_notify"` Restricted *bool `json:"restricted"` diff --git a/modules/structs/commit_status.go b/modules/structs/commit_status.go index fda795dca6..dc880ef5eb 100644 --- a/modules/structs/commit_status.go +++ b/modules/structs/commit_status.go @@ -16,13 +16,16 @@ const ( CommitStatusError CommitStatusState = "error" // CommitStatusFailure is for when the CommitStatus is Failure CommitStatusFailure CommitStatusState = "failure" + // CommitStatusWarning is for when the CommitStatus is Warning + CommitStatusWarning CommitStatusState = "warning" ) var commitStatusPriorities = map[CommitStatusState]int{ CommitStatusError: 0, CommitStatusFailure: 1, - CommitStatusPending: 2, - CommitStatusSuccess: 3, + CommitStatusWarning: 2, + CommitStatusPending: 3, + CommitStatusSuccess: 4, } func (css CommitStatusState) String() string { @@ -32,7 +35,7 @@ func (css CommitStatusState) String() string { // NoBetterThan returns true if this State is no better than the given State // This function only handles the states defined in CommitStatusPriorities func (css CommitStatusState) NoBetterThan(css2 CommitStatusState) bool { - // NoBetterThan only handles the 4 states above + // NoBetterThan only handles the 5 states above if _, exist := commitStatusPriorities[css]; !exist { return false } @@ -63,3 +66,8 @@ func (css CommitStatusState) IsError() bool { func (css CommitStatusState) IsFailure() bool { return css == CommitStatusFailure } + +// IsWarning represents if commit status state is warning +func (css CommitStatusState) IsWarning() bool { + return css == CommitStatusWarning +} diff --git a/modules/structs/package.go b/modules/structs/package.go index 0059535bea..a9a9429de2 100644 --- a/modules/structs/package.go +++ b/modules/structs/package.go @@ -16,6 +16,7 @@ type Package struct { Type string `json:"type"` Name string `json:"name"` Version string `json:"version"` + HTMLURL string `json:"html_url"` // swagger:strfmt date-time CreatedAt time.Time `json:"created_at"` } diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 6a2ba4836b..3974c4db3a 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -63,6 +63,7 @@ type Repository struct { Language string `json:"language"` LanguagesURL string `json:"languages_url"` HTMLURL string `json:"html_url"` + URL string `json:"url"` Link string `json:"link"` SSHURL string `json:"ssh_url"` CloneURL string `json:"clone_url"` diff --git a/modules/templates/helper.go b/modules/templates/helper.go index bb1411cbfd..7605320ed2 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -157,7 +157,6 @@ func NewFuncMap() template.FuncMap { "RenderEmoji": RenderEmoji, "RenderEmojiPlain": emoji.ReplaceAliases, "ReactionToEmoji": ReactionToEmoji, - "RenderNote": RenderNote, "RenderMarkdownToHtml": RenderMarkdownToHtml, "RenderLabel": RenderLabel, diff --git a/modules/templates/util_misc.go b/modules/templates/util_misc.go index 7700a13932..6c1b4ab240 100644 --- a/modules/templates/util_misc.go +++ b/modules/templates/util_misc.go @@ -58,11 +58,11 @@ func IsMultilineCommitMessage(msg string) bool { // Actioner describes an action type Actioner interface { GetOpType() activities_model.ActionType - GetActUserName() string - GetRepoUserName() string - GetRepoName() string - GetRepoPath() string - GetRepoLink() string + GetActUserName(ctx context.Context) string + GetRepoUserName(ctx context.Context) string + GetRepoName(ctx context.Context) string + GetRepoPath(ctx context.Context) string + GetRepoLink(ctx context.Context) string GetBranch() string GetContent() string GetCreate() time.Time @@ -74,27 +74,31 @@ func ActionIcon(opType activities_model.ActionType) string { switch opType { case activities_model.ActionCreateRepo, activities_model.ActionTransferRepo, activities_model.ActionRenameRepo: return "repo" - case activities_model.ActionCommitRepo, activities_model.ActionPushTag, activities_model.ActionDeleteTag, activities_model.ActionDeleteBranch: + case activities_model.ActionCommitRepo: return "git-commit" - case activities_model.ActionCreateIssue: - return "issue-opened" - case activities_model.ActionCreatePullRequest: - return "git-pull-request" - case activities_model.ActionCommentIssue, activities_model.ActionCommentPull: - return "comment-discussion" + case activities_model.ActionDeleteBranch: + return "git-branch" case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest: return "git-merge" - case activities_model.ActionCloseIssue, activities_model.ActionClosePullRequest: + case activities_model.ActionCreatePullRequest: + return "git-pull-request" + case activities_model.ActionClosePullRequest: + return "git-pull-request-closed" + case activities_model.ActionCreateIssue: + return "issue-opened" + case activities_model.ActionCloseIssue: return "issue-closed" case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest: return "issue-reopened" + case activities_model.ActionCommentIssue, activities_model.ActionCommentPull: + return "comment-discussion" case activities_model.ActionMirrorSyncPush, activities_model.ActionMirrorSyncCreate, activities_model.ActionMirrorSyncDelete: return "mirror" case activities_model.ActionApprovePullRequest: return "check" case activities_model.ActionRejectPullRequest: - return "diff" - case activities_model.ActionPublishRelease: + return "file-diff" + case activities_model.ActionPublishRelease, activities_model.ActionPushTag, activities_model.ActionDeleteTag: return "tag" case activities_model.ActionPullReviewDismissed: return "x" diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index ed6eae326a..1d9635410b 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -24,21 +24,13 @@ import ( ) // RenderCommitMessage renders commit message with XSS-safe and special links. -func RenderCommitMessage(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML { - return RenderCommitMessageLink(ctx, msg, urlPrefix, "", metas) -} - -// RenderCommitMessageLink renders commit message as a XXS-safe link to the provided -// default url, handling for special links. -func RenderCommitMessageLink(ctx context.Context, msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML { +func RenderCommitMessage(ctx context.Context, msg string, metas map[string]string) template.HTML { cleanMsg := template.HTMLEscapeString(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{ - Ctx: ctx, - URLPrefix: urlPrefix, - DefaultLink: urlDefault, - Metas: metas, + Ctx: ctx, + Metas: metas, }, cleanMsg) if err != nil { log.Error("RenderCommitMessage: %v", err) @@ -51,9 +43,9 @@ func RenderCommitMessageLink(ctx context.Context, msg, urlPrefix, urlDefault str return template.HTML(msgLines[0]) } -// RenderCommitMessageLinkSubject renders commit message as a XXS-safe link to +// RenderCommitMessageLinkSubject renders commit message as a XSS-safe link to // the provided default url, handling for special links without email to links. -func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML { +func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlDefault string, metas map[string]string) template.HTML { msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace) lineEnd := strings.IndexByte(msgLine, '\n') if lineEnd > 0 { @@ -68,7 +60,6 @@ func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlPrefix, urlDefa // shouldn't be any special HTML. renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{ Ctx: ctx, - URLPrefix: urlPrefix, DefaultLink: urlDefault, Metas: metas, }, template.HTMLEscapeString(msgLine)) @@ -80,7 +71,7 @@ func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlPrefix, urlDefa } // RenderCommitBody extracts the body of a commit message without its title. -func RenderCommitBody(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML { +func RenderCommitBody(ctx context.Context, msg string, metas map[string]string) template.HTML { msgLine := strings.TrimSpace(msg) lineEnd := strings.IndexByte(msgLine, '\n') if lineEnd > 0 { @@ -94,9 +85,8 @@ func RenderCommitBody(ctx context.Context, msg, urlPrefix string, metas map[stri } renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: urlPrefix, - Metas: metas, + Ctx: ctx, + Metas: metas, }, template.HTMLEscapeString(msgLine)) if err != nil { log.Error("RenderCommitMessage: %v", err) @@ -115,11 +105,10 @@ func RenderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML { } // RenderIssueTitle renders issue/pull title with defined post processors -func RenderIssueTitle(ctx context.Context, text, urlPrefix string, metas map[string]string) template.HTML { +func RenderIssueTitle(ctx context.Context, text string, metas map[string]string) template.HTML { renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: urlPrefix, - Metas: metas, + Ctx: ctx, + Metas: metas, }, template.HTMLEscapeString(text)) if err != nil { log.Error("RenderIssueTitle: %v", err) @@ -143,7 +132,7 @@ func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML { if labelScope == "" { // Regular label - s := fmt.Sprintf("
%s
", + s := fmt.Sprintf("
%s
", textColor, label.Color, description, RenderEmoji(ctx, label.Name)) return template.HTML(s) } @@ -177,9 +166,9 @@ func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML { itemColor := "#" + hex.EncodeToString(itemBytes) scopeColor := "#" + hex.EncodeToString(scopeBytes) - s := fmt.Sprintf(""+ + s := fmt.Sprintf(""+ "
%s
"+ - "
%s
"+ + "
%s
"+ "
", description, textColor, scopeColor, scopeText, @@ -211,25 +200,10 @@ func ReactionToEmoji(reaction string) template.HTML { return template.HTML(fmt.Sprintf(`:%s:`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction))) } -// RenderNote renders the contents of a git-notes file as a commit message. -func RenderNote(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML { - cleanMsg := template.HTMLEscapeString(msg) - fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: urlPrefix, - Metas: metas, - }, cleanMsg) - if err != nil { - log.Error("RenderNote: %v", err) - return "" - } - return template.HTML(fullMessage) -} - func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //nolint:revive output, err := markdown.RenderString(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: setting.AppSubURL, + Ctx: ctx, + Metas: map[string]string{"mode": "document"}, }, input) if err != nil { log.Error("RenderString: %v", err) diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index 29d3ed3a56..8648967d38 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -6,17 +6,64 @@ package templates import ( "context" "html/template" + "os" "testing" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/markup" + "github.com/stretchr/testify/assert" ) +const testInput = ` space @mention-user +/just/a/path.bin +https://example.com/file.bin +[local link](file.bin) +[remote link](https://example.com) +[[local link|file.bin]] +[[remote link|https://example.com]] +![local image](image.jpg) +![remote image](https://example.com/image.jpg) +[[local image|image.jpg]] +[[remote link|https://example.com/image.jpg]] +https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare +https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit +:+1: +mail@domain.com +@mention-user test +#123 + space +` + +var testMetas = map[string]string{ + "user": "user13", + "repo": "repo11", + "repoPath": "../../tests/gitea-repositories-meta/user13/repo11.git/", + "mode": "comment", +} + +func TestMain(m *testing.M) { + unittest.InitSettings() + if err := git.InitSimple(context.Background()); err != nil { + log.Fatal("git init failed, err: %v", err) + } + markup.Init(&markup.ProcessorHelper{ + IsUsernameMentionable: func(ctx context.Context, username string) bool { + return username == "mention-user" + }, + }) + os.Exit(m.Run()) +} + func TestRenderCommitBody(t *testing.T) { type args struct { - ctx context.Context - msg string - urlPrefix string - metas map[string]string + ctx context.Context + msg string + metas map[string]string } tests := []struct { name string @@ -50,7 +97,91 @@ func TestRenderCommitBody(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, RenderCommitBody(tt.args.ctx, tt.args.msg, tt.args.urlPrefix, tt.args.metas), "RenderCommitBody(%v, %v, %v, %v)", tt.args.ctx, tt.args.msg, tt.args.urlPrefix, tt.args.metas) + assert.Equalf(t, tt.want, RenderCommitBody(tt.args.ctx, tt.args.msg, tt.args.metas), "RenderCommitBody(%v, %v, %v)", tt.args.ctx, tt.args.msg, tt.args.metas) }) } + + expected := `/just/a/path.bin +https://example.com/file.bin +[local link](file.bin) +[remote link](https://example.com) +[[local link|file.bin]] +[[remote link|https://example.com]] +![local image](image.jpg) +![remote image](https://example.com/image.jpg) +[[local image|image.jpg]] +[[remote link|https://example.com/image.jpg]] +88fc37a3c0...12fc37a3c0 (hash) +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare +88fc37a3c0 +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit +👍 +mail@domain.com +@mention-user test +#123 + space` + + assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas)) +} + +func TestRenderCommitMessage(t *testing.T) { + expected := `space @mention-user ` + + assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput, testMetas)) +} + +func TestRenderCommitMessageLinkSubject(t *testing.T) { + expected := `space @mention-user` + + assert.EqualValues(t, expected, RenderCommitMessageLinkSubject(context.Background(), testInput, "https://example.com/link", testMetas)) +} + +func TestRenderIssueTitle(t *testing.T) { + expected := ` space @mention-user +/just/a/path.bin +https://example.com/file.bin +[local link](file.bin) +[remote link](https://example.com) +[[local link|file.bin]] +[[remote link|https://example.com]] +![local image](image.jpg) +![remote image](https://example.com/image.jpg) +[[local image|image.jpg]] +[[remote link|https://example.com/image.jpg]] +https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare +https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit +👍 +mail@domain.com +@mention-user test +#123 + space +` + assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput, testMetas)) +} + +func TestRenderMarkdownToHtml(t *testing.T) { + expected := `

space @mention-user
+/just/a/path.bin +https://example.com/file.bin +local link +remote link +local link +remote link +local image +remote image +local image +remote link +88fc37a3c0...12fc37a3c0 (hash) +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare +88fc37a3c0 +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit +👍 +mail@domain.com +@mention-user test +#123 +space

+` + assert.EqualValues(t, expected, RenderMarkdownToHtml(context.Background(), testInput)) } diff --git a/modules/timeutil/datetime.go b/modules/timeutil/datetime.go index 83170b374b..50c8d44f13 100644 --- a/modules/timeutil/datetime.go +++ b/modules/timeutil/datetime.go @@ -7,11 +7,12 @@ import ( "fmt" "html" "html/template" + "strings" "time" ) // DateTime renders an absolute time HTML element by datetime. -func DateTime(format string, datetime any) template.HTML { +func DateTime(format string, datetime any, extraAttrs ...string) template.HTML { if p, ok := datetime.(*time.Time); ok { datetime = *p } @@ -48,13 +49,18 @@ func DateTime(format string, datetime any) template.HTML { panic(fmt.Sprintf("Unsupported time type %T", datetime)) } + attrs := make([]string, 0, 10+len(extraAttrs)) + attrs = append(attrs, extraAttrs...) + attrs = append(attrs, `weekday=""`, `year="numeric"`) + switch format { - case "short": - return template.HTML(fmt.Sprintf(`%s`, datetimeEscaped, textEscaped)) - case "long": - return template.HTML(fmt.Sprintf(`%s`, datetimeEscaped, textEscaped)) - case "full": - return template.HTML(fmt.Sprintf(`%s`, datetimeEscaped, textEscaped)) + case "short", "long": // date only + attrs = append(attrs, `month="`+format+`"`, `day="numeric"`) + return template.HTML(fmt.Sprintf(`%s`, strings.Join(attrs, " "), datetimeEscaped, textEscaped)) + case "full": // full date including time + attrs = append(attrs, `format="datetime"`, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`, `data-tooltip-content`, `data-tooltip-interactive="true"`) + return template.HTML(fmt.Sprintf(`%s`, strings.Join(attrs, " "), datetimeEscaped, textEscaped)) + default: + panic(fmt.Sprintf("Unsupported format %s", format)) } - panic(fmt.Sprintf("Unsupported format %s", format)) } diff --git a/modules/timeutil/datetime_test.go b/modules/timeutil/datetime_test.go index f44b7aaae3..39aecbc43b 100644 --- a/modules/timeutil/datetime_test.go +++ b/modules/timeutil/datetime_test.go @@ -8,18 +8,17 @@ import ( "time" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" ) func TestDateTime(t *testing.T) { - oldTz := setting.DefaultUILocation - setting.DefaultUILocation, _ = time.LoadLocation("America/New_York") - defer func() { - setting.DefaultUILocation = oldTz - }() + testTz, _ := time.LoadLocation("America/New_York") + defer test.MockVariableValue(&setting.DefaultUILocation, testTz)() refTimeStr := "2018-01-01T00:00:00Z" + refDateStr := "2018-01-01" refTime, _ := time.Parse(time.RFC3339, refTimeStr) refTimeStamp := TimeStamp(refTime.Unix()) @@ -29,17 +28,20 @@ func TestDateTime(t *testing.T) { assert.EqualValues(t, "-", DateTime("short", TimeStamp(0))) actual := DateTime("short", "invalid") - assert.EqualValues(t, `invalid`, actual) + assert.EqualValues(t, `invalid`, actual) actual = DateTime("short", refTimeStr) - assert.EqualValues(t, `2018-01-01T00:00:00Z`, actual) + assert.EqualValues(t, `2018-01-01T00:00:00Z`, actual) actual = DateTime("short", refTime) - assert.EqualValues(t, `2018-01-01`, actual) + assert.EqualValues(t, `2018-01-01`, actual) + + actual = DateTime("short", refDateStr) + assert.EqualValues(t, `2018-01-01`, actual) actual = DateTime("short", refTimeStamp) - assert.EqualValues(t, `2017-12-31`, actual) + assert.EqualValues(t, `2017-12-31`, actual) actual = DateTime("full", refTimeStamp) - assert.EqualValues(t, `2017-12-31 19:00:00 -05:00`, actual) + assert.EqualValues(t, `2017-12-31 19:00:00 -05:00`, actual) } diff --git a/modules/translation/mock.go b/modules/translation/mock.go index 2d0cb17324..d4c60e2517 100644 --- a/modules/translation/mock.go +++ b/modules/translation/mock.go @@ -6,7 +6,9 @@ package translation import "fmt" // MockLocale provides a mocked locale without any translations -type MockLocale struct{} +type MockLocale struct { + Lang, LangName string // these fields are used directly in templates: ctx.Locale.Lang +} var _ Locale = (*MockLocale)(nil) diff --git a/modules/translation/translation.go b/modules/translation/translation.go index dba4de6607..0acfe48c97 100644 --- a/modules/translation/translation.go +++ b/modules/translation/translation.go @@ -140,7 +140,7 @@ func Match(tags ...language.Tag) language.Tag { // locale represents the information of localization. type locale struct { i18n.Locale - Lang, LangName string // these fields are used directly in templates: .i18n.Lang + Lang, LangName string // these fields are used directly in templates: ctx.Locale.Lang msgPrinter *message.Printer } diff --git a/modules/util/slice.go b/modules/util/slice.go index 6d63ab4a77..15ffd9deeb 100644 --- a/modules/util/slice.go +++ b/modules/util/slice.go @@ -45,3 +45,12 @@ func SliceSortedEqual[T comparable](s1, s2 []T) bool { func SliceRemoveAll[T comparable](slice []T, target T) []T { return slices.DeleteFunc(slice, func(t T) bool { return t == target }) } + +// TODO: Replace with "maps.Keys" once available, current it only in golang.org/x/exp/maps but not in standard library +func KeysOfMap[K comparable, V any](m map[K]V) []K { + keys := make([]K, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + return keys +} diff --git a/modules/util/string.go b/modules/util/string.go index f2def7b0ec..2cf44d29b1 100644 --- a/modules/util/string.go +++ b/modules/util/string.go @@ -3,7 +3,7 @@ package util -import "github.com/yuin/goldmark/util" +import "unsafe" func isSnakeCaseUpper(c byte) bool { return 'A' <= c && c <= 'Z' @@ -83,5 +83,15 @@ func ToSnakeCase(input string) string { } } } - return util.BytesToReadOnlyString(res) + return UnsafeBytesToString(res) +} + +// UnsafeBytesToString uses Go's unsafe package to convert a byte slice to a string. +// TODO: replace all "goldmark/util.BytesToReadOnlyString" with this official approach +func UnsafeBytesToString(b []byte) string { + return unsafe.String(unsafe.SliceData(b), len(b)) +} + +func UnsafeStringToBytes(s string) []byte { + return unsafe.Slice(unsafe.StringData(s), len(s)) } diff --git a/modules/util/util.go b/modules/util/util.go index c47931f6c9..0e5c6a4e64 100644 --- a/modules/util/util.go +++ b/modules/util/util.go @@ -6,7 +6,6 @@ package util import ( "bytes" "crypto/rand" - "encoding/base64" "fmt" "math/big" "strconv" @@ -246,13 +245,3 @@ func ToFloat64(number any) (float64, error) { func ToPointer[T any](val T) *T { return &val } - -func Base64FixedDecode(encoding *base64.Encoding, src []byte, length int) ([]byte, error) { - decoded := make([]byte, encoding.DecodedLen(len(src))+3) - if n, err := encoding.Decode(decoded, src); err != nil { - return nil, err - } else if n != length { - return nil, fmt.Errorf("invalid base64 decoded length: %d, expects: %d", n, length) - } - return decoded[:length], nil -} diff --git a/modules/util/util_test.go b/modules/util/util_test.go index 8509d8aced..c5830ce01c 100644 --- a/modules/util/util_test.go +++ b/modules/util/util_test.go @@ -4,7 +4,6 @@ package util import ( - "encoding/base64" "regexp" "strings" "testing" @@ -234,16 +233,3 @@ func TestToPointer(t *testing.T) { val123 := 123 assert.False(t, &val123 == ToPointer(val123)) } - -func TestBase64FixedDecode(t *testing.T) { - _, err := Base64FixedDecode(base64.RawURLEncoding, []byte("abcd"), 32) - assert.ErrorContains(t, err, "invalid base64 decoded length") - _, err = Base64FixedDecode(base64.RawURLEncoding, []byte(strings.Repeat("a", 64)), 32) - assert.ErrorContains(t, err, "invalid base64 decoded length") - - str32 := strings.Repeat("x", 32) - encoded32 := base64.RawURLEncoding.EncodeToString([]byte(str32)) - decoded32, err := Base64FixedDecode(base64.RawURLEncoding, []byte(encoded32), 32) - assert.NoError(t, err) - assert.Equal(t, str32, string(decoded32)) -} diff --git a/modules/web/route.go b/modules/web/route.go index c24c8f4d67..805fcb4411 100644 --- a/modules/web/route.go +++ b/modules/web/route.go @@ -101,16 +101,18 @@ func (r *Route) wrapMiddlewareAndHandler(h []any) ([]func(http.Handler) http.Han return middlewares, handlerFunc } -func (r *Route) Methods(method, pattern string, h ...any) { +// Methods adds the same handlers for multiple http "methods" (separated by ","). +// If any method is invalid, the lower level router will panic. +func (r *Route) Methods(methods, pattern string, h ...any) { middlewares, handlerFunc := r.wrapMiddlewareAndHandler(h) fullPattern := r.getPattern(pattern) - if strings.Contains(method, ",") { - methods := strings.Split(method, ",") + if strings.Contains(methods, ",") { + methods := strings.Split(methods, ",") for _, method := range methods { r.R.With(middlewares...).Method(strings.TrimSpace(method), fullPattern, handlerFunc) } } else { - r.R.With(middlewares...).Method(method, fullPattern, handlerFunc) + r.R.With(middlewares...).Method(methods, fullPattern, handlerFunc) } } @@ -136,16 +138,6 @@ func (r *Route) Get(pattern string, h ...any) { r.Methods("GET", pattern, h...) } -// GetOptions delegate get and options method -func (r *Route) GetOptions(pattern string, h ...any) { - r.Methods("GET,OPTIONS", pattern, h...) -} - -// PostOptions delegate post and options method -func (r *Route) PostOptions(pattern string, h ...any) { - r.Methods("POST,OPTIONS", pattern, h...) -} - // Head delegate head method func (r *Route) Head(pattern string, h ...any) { r.Methods("HEAD", pattern, h...) diff --git a/modules/webhook/type.go b/modules/webhook/type.go index 7042d391b7..0013691c02 100644 --- a/modules/webhook/type.go +++ b/modules/webhook/type.go @@ -31,6 +31,7 @@ const ( HookEventRepository HookEventType = "repository" HookEventRelease HookEventType = "release" HookEventPackage HookEventType = "package" + HookEventSchedule HookEventType = "schedule" ) // Event returns the HookEventType as an event string diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 57c1635c1c..dcfa57c828 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -23,7 +23,6 @@ enable_javascript=Tato stránka vyžaduje JavaScript. toc=Obsah licenses=Licence return_to_gitea=Vrátit se do Gitea - username=Uživatelské jméno email=E-mailová adresa password=Heslo @@ -33,7 +32,6 @@ captcha=CAPTCHA twofa=Dvoufaktorové ověřování twofa_scratch=Dvoufaktorový pomocný kód passcode=Přístupový kód - webauthn_insert_key=Vložte svůj bezpečnostní klíč webauthn_sign_in=Stiskněte tlačítko na svém bezpečnostním klíči. Pokud bezpečnostní klíč nemá žádné tlačítko, vložte jej znovu. webauthn_press_button=Stiskněte prosím tlačítko na zabezpečovacím klíči… @@ -41,13 +39,12 @@ webauthn_use_twofa=Použít dvoufaktorový kód z vašeho telefonu webauthn_error=Nepodařilo se přečíst váš zabezpečovací klíč. webauthn_unsupported_browser=Váš prohlížeč momentálně nepodporuje WebAuthn. webauthn_error_unknown=Došlo k neznámé chybě. Opakujte akci. -webauthn_error_insecure=`WebAuthn podporuje pouze zabezpečená připojení. Pro testování přes HTTP můžete použít výchozí "localhost" nebo "127.0.0.1"` +webauthn_error_insecure=WebAuthn podporuje pouze zabezpečená připojení. Pro testování přes HTTP můžete použít výchozí "localhost" nebo "127.0.0.1" webauthn_error_unable_to_process=Server nemohl zpracovat váš požadavek. webauthn_error_duplicated=Zabezpečovací klíč není pro tento požadavek povolen. Prosím ujistěte se, zda klíč není již registrován. webauthn_error_empty=Musíte nastavit název tohoto klíče. webauthn_error_timeout=Požadavek vypršel dříve, než se podařilo přečíst váš klíč. Znovu načtěte tuto stránku a akci opakujte. webauthn_reload=Znovu načíst - repository=Repozitář organization=Organizace mirror=Zrcadlo @@ -65,18 +62,15 @@ settings=Nastavení your_profile=Profil your_starred=Oblíbené your_settings=Nastavení - all=Vše sources=Zdrojové kódy mirrors=Zrcadla collaborative=Spolupráce forks=Rozštěpení - activities=Aktivity pull_requests=Požadavky na natažení issues=Úkoly milestones=Milníky - ok=OK cancel=Zrušit rerun=Znovu spustit @@ -86,12 +80,10 @@ add=Přidat add_all=Přidat vše remove=Odstranit remove_all=Odstranit vše -remove_label_str=`Odstranit položku "%s"` +remove_label_str=Odstranit položku "%s" edit=Upravit - enabled=Povolený disabled=Zakázané - copy=Kopírovat copy_url=Kopírovat URL copy_content=Kopírovat obsah @@ -99,39 +91,29 @@ copy_branch=Kopírovat jméno větve copy_success=Zkopírováno! copy_error=Kopírování se nezdařilo copy_type_unsupported=Tento typ souboru nelze zkopírovat - write=Zapsat preview=Náhled loading=Načítá se… - step1=Krok 1: step2=Krok 2: - error=Chyba error404=Stránka, kterou se snažíte zobrazit, buď neexistuje, nebo nemáte oprávnění ji zobrazit. - never=Nikdy unknown=Neznámý - rss_feed=RSS kanál - pin=Připnout unpin=Odepnout - artifacts=Artefakty - concept_system_global=Globální concept_user_individual=Individuální concept_code_repository=Repozitář concept_user_organization=Organizace - show_timestamps=Zobrazit časové značky show_log_seconds=Zobrazit sekundy show_full_screen=Zobrazit celou obrazovku - - name=Název value=Hodnota +archived=Archivováno [aria] navbar=Navigační lišta @@ -210,7 +192,6 @@ err_empty_admin_email=Email administrátora nemůže být prázdný. err_admin_name_is_reserved=Uživatelské jméno administrátora není platné, uživatelské jméno je rezervované err_admin_name_pattern_not_allowed=Uživatelské jméno administrátora je neplatné, uživatelské jméno odpovídá vyhrazenému vzoru err_admin_name_is_invalid=Uživatelské jméno administrátora není platné - general_title=Obecná nastavení app_name=Název stránky app_name_helper=Zde můžete zadat název vaší společnosti. @@ -229,7 +210,6 @@ app_url=Základní URL Gitea app_url_helper=Základní adresa pro HTTP(S) URL adresy pro klonování a e-mailová oznámení. log_root_path=Adresář logů log_root_path_helper=Soubory protokolu budou zapsány do tohoto adresáře. - optional_title=Dodatečná nastavení email_title=Nastavení e-mailu smtp_addr=Server SMTP @@ -271,7 +251,7 @@ invalid_db_setting=Nastavení databáze je neplatné: %v invalid_db_table=Databázová tabulka "%s" je neplatná: %v invalid_repo_path=Kořenový adresář repozitářů není správný: %v invalid_app_data_path=Cesta k datům aplikace je neplatná: %v -run_user_not_match=`"Run as" uživatelské jméno není aktuální uživatelské jméno: %s -> %s` +run_user_not_match="Run as" uživatelské jméno není aktuální uživatelské jméno: %s -> %s internal_token_failed=Nepodařilo se vytvořit interní token: %v secret_key_failed=Nepodařilo se vytvořit tajný klíč: %v save_config_failed=Uložení konfigurace se nezdařilo: %v @@ -305,18 +285,15 @@ search_repos=Nalézt repozitář… filter=Ostatní filtry filter_by_team_repositories=Filtrovat podle repozitářů týmu feed_of=Kanál z „%s“ - show_archived=Archivováno archived=Archivováno show_both_archived_unarchived=Zobrazeny jak archivované tak nearchivované show_only_archived=Zobrazeny pouze archivované show_only_unarchived=Zobrazeny pouze nearchivované - show_private=Soukromý show_both_private_public=Zobrazeny jak veřejné tak soukromé show_only_private=Zobrazeny pouze soukromé show_only_public=Zobrazeny pouze veřejné - issues.in_your_repos=Ve vašich repozitářích [explore] @@ -336,12 +313,11 @@ repo_no_results=Nebyly nalezeny žádné odpovídající repozitáře. user_no_results=Nebyly nalezeni žádní odpovídající uživatelé. org_no_results=Nebyly nalezeny žádné odpovídající organizace. code_no_results=Nebyl nalezen žádný zdrojový kód odpovídající hledanému výrazu. -code_search_results=`Výsledky hledání pro "%s"` +code_search_results=Výsledky hledání pro "%s" code_last_indexed_at=Naposledy indexováno %s relevant_repositories_tooltip=Repozitáře, které jsou rozštěpení nebo nemají žádné téma, ikonu a žádný popis jsou skryty. relevant_repositories=Zobrazují se pouze relevantní repositáře, zobrazit nefiltrované výsledky. - [auth] create_new_account=Registrovat účet register_helper_msg=Již máte účet? Přihlaste se! @@ -410,30 +386,23 @@ view_it_on=Zobrazit na %s reply=nebo přímo odpovědět na tento e-mail link_not_working_do_paste=Nefunguje? Zkuste jej zkopírovat a vložit do svého prohlížeče. hi_user_x=Ahoj %s, - activate_account=Prosíme, aktivujte si váš účet activate_account.title=%s, prosím aktivujte si váš účet activate_account.text_1=Ahoj %[1]s, děkujeme za registraci na %[2]s! activate_account.text_2=Pro aktivaci vašeho účtu do %s klikněte na následující odkaz: - activate_email=Ověřte vaši e-mailovou adresu activate_email.text=Pro aktivaci vašeho účtu do %s klikněte na následující odkaz: - register_notify=Vítejte v Gitea register_notify.title=%[1]s vítejte v %[2]s register_notify.text_1=toto je váš potvrzovací e-mail pro %s! register_notify.text_2=Nyní se můžete přihlásit přes uživatelské jméno: %s. register_notify.text_3=Pokud pro vás byl vytvořen tento účet, nejprve nastavte své heslo. - reset_password=Obnovit váš účet reset_password.title=%s, požádal jste o obnovení vašeho účtu reset_password.text=Klikněte prosím na následující odkaz pro obnovení vašeho účtu v rámci %s: - register_success=Registrace byla úspěšná - issue_assigned.pull=@%[1]s vás přiřadil/a k požadavku na natažení %[2]s repozitáři %[3]s. issue_assigned.issue=@%[1]s vás přiřadil/a k úkolu %[2]s repozitáři %[3]s. - issue.x_mentioned_you=@%s vás zmínil/a: issue.action.force_push=%[1]s vynutil/a nahrání %[2]s z %[3]s do %[4]s. issue.action.push_1=@%[1]s nahrál/a %[3]d commit do %[2]s @@ -448,7 +417,6 @@ issue.action.review_dismissed=@%[1]s odmítl/a poslední kontrolu z %[2]s issue.action.ready_for_review=@%[1]s označil/a tento požadavek na natažení jako připravený ke kontrole. issue.action.new=@%[1]s vytvořil/a #%[2]d. issue.in_tree_path=V %s: - release.new.subject=%s v %s vydáno release.new.text=@%[1]s vydal/a %[2]s v %[3]s release.title=Název: %s @@ -456,15 +424,12 @@ release.note=Poznámka: release.downloads=Soubory ke stažení: release.download.zip=Zdrojový kód (ZIP) release.download.targz=Zdrojový kód (TAR.GZ) - repo.transfer.subject_to=%s by chtěl převést „%s“ pro %s repo.transfer.subject_to_you=%s by Vám chtěl převést „%s“ repo.transfer.to_you=vám repo.transfer.body=Chcete-li ji přijmout nebo odmítnout, navštivte %s nebo ji prostě ignorujte. - repo.collaborator.added.subject=%s vás přidal do %s repo.collaborator.added.text=Byl jste přidán jako spolupracovník repozitáře: - team_invite.subject=%[1]s vás pozval/a, abyste se připojili k organizaci %[2]s team_invite.text_1=%[1]s vás pozval/a do týmu %[2]s v organizaci %[3]s. team_invite.text_2=Pro připojení k týmu klikněte na následující odkaz: @@ -489,35 +454,31 @@ PayloadUrl=URL nákladu TeamName=Název týmu AuthName=Název ověření AdminEmail=E-mailová adresa správce - NewBranchName=Název nové větve CommitSummary=Shrnutí commity CommitMessage=Zpráva commitu CommitChoice=Výběr commitu TreeName=Cesta k souboru Content=Obsah - SSPISeparatorReplacement=Oddělovač SSPIDefaultLanguage=Výchozí jazyk - -require_error=` nemůže být prázdný.` -alpha_dash_error=` by měl obsahovat pouze alfanumerické znaky, pomlčku („-“) a podtržítka („_“). ` -alpha_dash_dot_error=` by měl obsahovat pouze alfanumerické znaky, pomlčku („-“), podtržítka („_“) nebo tečku („.“). ` -git_ref_name_error=` musí být správný název odkazu Git.` -size_error=` musí být minimálně velikosti %s.` -min_size_error=` musí obsahovat nejméně %s znaků.` -max_size_error=` musí obsahovat maximálně %s znaků.` -email_error=` není správná e-mailová adresa.` -url_error=`„%s“ není platná adresa URL.` -include_error=` musí obsahovat substring „%s“.` -glob_pattern_error=`zástupný vzor je neplatný: %s.` -regex_pattern_error=` regex vzor je neplatný: %s.` -invalid_group_team_map_error=` mapování je neplatné: %s` +require_error=" nemůže být prázdný." +alpha_dash_error=" by měl obsahovat pouze alfanumerické znaky, pomlčku („-“) a podtržítka („_“). " +alpha_dash_dot_error=" by měl obsahovat pouze alfanumerické znaky, pomlčku („-“), podtržítka („_“) nebo tečku („.“). " +git_ref_name_error=" musí být správný název odkazu Git." +size_error=" musí být minimálně velikosti %s." +min_size_error=" musí obsahovat nejméně %s znaků." +max_size_error=" musí obsahovat maximálně %s znaků." +email_error=" není správná e-mailová adresa." +url_error=„%s“ není platná adresa URL. +include_error=" musí obsahovat substring „%s“." +glob_pattern_error=zástupný vzor je neplatný: %s. +regex_pattern_error=" regex vzor je neplatný: %s." +invalid_group_team_map_error=" mapování je neplatné: %s" unknown_error=Neznámá chyba: captcha_incorrect=CAPTCHA kód není správný. password_not_match=Zadaná hesla nesouhlasí. lang_select_error=Vyberte jazyk ze seznamu. - username_been_taken=Uživatelské jméno je již obsazeno. username_change_not_local_user=Uživatelé, kteří jsou ověřováni jinak než lokálně, si nemohou změnit uživatelské jméno. username_has_not_been_changed=Uživatelské jméno nebylo změněno @@ -551,14 +512,11 @@ last_org_owner=Nemůžete odstranit posledního uživatele z týmu „vlastníci cannot_add_org_to_team=Organizace nemůže být přidána jako člen týmu. duplicate_invite_to_team=Uživatel byl již pozván jako člen týmu. organization_leave_success=Úspěšně jste opustili organizaci %s. - invalid_ssh_key=Nelze ověřit váš SSH klíč: %s invalid_gpg_key=Nelze ověřit váš GPG klíč: %s invalid_ssh_principal=Neplatný SSH Principal certifikát: %s must_use_public_key=Zadaný klíč je soukromý klíč. Nenahrávejte svůj soukromý klíč nikde. Místo toho použijte váš veřejný klíč. auth_failed=Ověření selhalo: %v - - target_branch_not_exist=Cílová větev neexistuje. [user] @@ -578,7 +536,6 @@ user_bio=Životopis disabled_public_activity=Tento uživatel zakázal veřejnou viditelnost aktivity. email_visibility.limited=Vaše e-mailová adresa je viditelná pro všechny ověřené uživatele email_visibility.private=Vaše e-mailová adresa je viditelná pouze pro vás a administrátory - form.name_reserved=Uživatelské jméno "%s" je rezervováno. form.name_pattern_not_allowed=Vzor "%s" není povolen v uživatelském jméně. form.name_chars_not_allowed=Uživatelské jméno "%s" obsahuje neplatné znaky. @@ -600,7 +557,6 @@ twofa=Dvoufaktorové ověřování account_link=Propojené účty organization=Organizace webauthn=Bezpečnostní klíče - public_profile=Veřejný profil password_username_disabled=Externí uživatelé nemohou měnit svoje uživatelské jméno. Kontaktujte prosím svého administrátora pro více detailů. full_name=Celé jméno @@ -635,7 +591,6 @@ comment_type_group_issue_ref=Referenční číslo úkolu saved_successfully=Vaše nastavení bylo úspěšně uloženo. privacy=Soukromí keep_activity_private_popup=Učinit aktivitu viditelnou pouze pro vás a administrátory - lookup_avatar_by_mail=Vyhledat avatar pomocí e-mailové adresy federated_avatar_lookup=Vyhledání avatarů ve veřejných zdrojích enable_custom_avatar=Použít vlastní avatar @@ -645,14 +600,12 @@ delete_current_avatar=Smazat aktuální avatar uploaded_avatar_not_a_image=Nahraný soubor není obrázek. update_avatar_success=Vaše avatar byl aktualizován. update_user_avatar_success=Uživatelův avatar byl aktualizován. - change_password=Aktualizovat heslo old_password=Stávající heslo new_password=Nové heslo password_incorrect=Zadané heslo není správné. change_password_success=Vaše heslo bylo aktualizováno. Od teď se přihlašujte novým heslem. password_change_disabled=Externě ověřovaní uživatelé nemohou aktualizovat své heslo prostřednictvím webového rozhraní Gitea. - emails=E-mailová adresa manage_emails=Správa e-mailových adres manage_themes=Vyberte výchozí motiv vzhledu @@ -682,7 +635,6 @@ email_preference_set_success=Nastavení e-mailu bylo úspěšně nastaveno. add_openid_success=Nová OpenID adresa byla přidána. keep_email_private=Schovat e-mailovou adresu openid_desc=OpenID vám umožní delegovat ověřování na externího poskytovatele. - manage_ssh_keys=Správa klíčů SSH manage_ssh_principals=Spravovat SSH Principal certifikáty manage_gpg_keys=Správa GPG klíčů @@ -759,7 +711,6 @@ ssh_signonly=SSH je v současné době zakázáno, proto jsou tyto klíče použ ssh_externally_managed=Tento SSH klíč je spravován externě pro tohoto uživatele manage_social=Správa propojených účtů sociálních sítí unbind=Odpojit - manage_access_token=Spravovat přístupové tokeny generate_new_token=Vygenerovat nový token tokens_desc=Tyto tokeny umožňují přístup k vašemu účtu pomocí Gitea API. @@ -781,7 +732,6 @@ permission_no_access=Bez přístupu permission_read=Přečtené at_least_one_permission=Musíte vybrat alespoň jedno oprávnění pro vytvoření tokenu permissions_list=Oprávnění: - manage_oauth2_applications=Spravovat OAuth2 aplikace edit_oauth2_application=Upravit OAuth2 aplikaci oauth2_applications_desc=OAuth2 aplikace umožní aplikacím třetích stran bezpečně ověřit uživatele v této instanci Gitea. @@ -800,12 +750,10 @@ oauth2_regenerate_secret=Obnovit tajný klíč oauth2_regenerate_secret_hint=Ztratili jste svůj tajný klíč? oauth2_application_edit=Upravit oauth2_application_create_description=OAuth2 aplikace poskytuje přístup aplikacím třetích stran k uživatelským účtům na této instanci. - authorized_oauth2_applications=Autorizovat OAuth2 aplikaci revoke_key=Zrušit revoke_oauth2_grant=Zrušit přístup revoke_oauth2_grant_description=Zrušením přístupu této aplikaci třetí strany ji zabráníte v přístupu k vašim datům. Jste si jisti? - twofa_desc=Dvoufaktorový způsob ověřování zvýší zabezpečení vašeho účtu. twofa_is_enrolled=Váš účet aktuálně používá dvoufaktorové ověřování. twofa_not_enrolled=Váš účet aktuálně nepoužívá dvoufaktorové ověřování. @@ -822,13 +770,11 @@ then_enter_passcode=A zadejte přístupový kód zobrazený ve vaší aplikaci: passcode_invalid=Přístupový kód není platný. Zkuste to znovu. twofa_enrolled=Ve vašem účtu bylo povoleno dvoufaktorové ověřování. Uložte si pomocný token (%s) na bezpečném místě, protože bude zobrazen pouze jednou! twofa_failed_get_secret=Nepodařilo se získat tajemství. - webauthn_desc=Bezpečnostní klíče jsou hardwarová zařízení obsahující kryptografické klíče. Mohou být použity pro dvoufaktorové ověřování. Bezpečnostní klíče musí podporovat WebAuthn Authenticator standard. webauthn_register_key=Přidat bezpečnostní klíč webauthn_nickname=Přezdívka webauthn_delete_key=Odstranit bezpečnostní klíč webauthn_delete_key_desc=Pokud odstraníte bezpečnostní klíč, již se s ním nebudete moci přihlásit. Pokračovat? - manage_account_links=Správa propojených účtů manage_account_links_desc=Tyto externí účty jsou propojeny s vaším Gitea účtem. account_links_not_available=K vašemu Gitea účtu nejsou aktuálně připojené žádné externí účty. @@ -836,23 +782,18 @@ link_account=Propojit účet remove_account_link=Odstranit propojený účet remove_account_link_desc=Odstraněním propojeného účtu zrušíte jeho přístup k vašemu Gitea účtu. Pokračovat? remove_account_link_success=Propojený účet byl odstraněn. - - orgs_none=Nejste členem žádné organizace. - delete_account=Smazat váš účet delete_prompt=Tato operace natrvalo odstraní váš uživatelský účet. NELZE ji vrátit zpět. delete_with_all_comments=Váš účet je mladší než %s. Aby se zabránilo fantomovým komentářům, všechny komentáře k úkolům/požadavkům na natažení budou smazány. confirm_delete_account=Potvrdit smazání delete_account_title=Smazat uživatelský účet delete_account_desc=Jste si jisti, že chcete trvale smazat tento účet? - email_notifications.enable=Povolit e-mailová oznámení email_notifications.onmention=E-mail pouze při zmínce email_notifications.disable=Zakázat e-mailová oznámení email_notifications.submit=Nastavit předvolby e-mailu email_notifications.andyourown=A Vaše vlastní upozornění - visibility=Viditelnost uživatele visibility.public=Veřejný visibility.public_tooltip=Viditelné pro všechny @@ -941,12 +882,10 @@ delete_preexisting_content=Odstranit soubory v %s delete_preexisting_success=Smazány nepřijaté soubory v %s blame_prior=Zobrazit blame před touto změnou author_search_tooltip=Zobrazí maximálně 30 uživatelů - transfer.accept=Přijmout převod transfer.accept_desc=Převést do „%s“ transfer.reject=Odmítnout převod transfer.reject_desc=Zrušit převod do „%s“ - desc.private=Soukromý desc.public=Veřejný desc.private_template=Soukromá šablona @@ -954,7 +893,6 @@ desc.public_template=Šablona desc.internal=Interní desc.internal_template=Interní šablona desc.archived=Archivováno - template.items=Položky šablony template.git_content=Obsah gitu (výchozí větev) template.git_hooks=Háčky Gitu @@ -965,14 +903,11 @@ template.avatar=Avatar template.issue_labels=Štítky úkolů template.one_item=Musíte vybrat alespoň jednu položku šablony template.invalid=Musíte vybrat repositář šablony - archive.issue.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat úkoly. archive.pull.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat požadavky na natažení. - form.reach_limit_of_creation_1=Již jste dosáhli svůj limit %d repozitář. form.reach_limit_of_creation_n=Již jste dosáhli svůj limit %d repozitářů. form.name_reserved=Název repozitáře „%s“ je rezervován. - need_auth=Ověření migrate_options=Možnosti migrace migrate_service=Migrační služba @@ -1023,7 +958,6 @@ migrate.migrating_issues=Migrování úkolů migrate.migrating_pulls=Migrování požadavků na natažení migrate.cancel_migrating_title=Zrušit migraci migrate.cancel_migrating_confirm=Chcete zrušit tuto migraci? - mirror_from=zrcadlo forked_from=rozštěpen z generated_from=generováno z @@ -1038,7 +972,6 @@ star=Oblíbit fork=Rozštěpit download_archive=Stáhnout repozitář more_operations=Další operace - no_desc=Bez popisu quick_guide=Krátká příručka clone_this_repo=Naklonovat tento repozitář @@ -1047,7 +980,6 @@ create_new_repo_command=Vytvořit nový repozitář na příkazové řádce push_exist_repo=Nahrání existujícího repozitáře z příkazové řádky empty_message=Tento repozitář nemá žádný obsah. broken_message=Data gitu, která jsou základem tohoto repozitáře, nelze číst. Kontaktujte správce této instance nebo smažte tento repositář. - code=Zdrojový kód code.desc=Přístup ke zdrojovým kódům, souborům, commitům a větvím. branch=Větev @@ -1065,7 +997,6 @@ actions=Akce labels=Štítky org_labels_desc=Štítky na úrovni organizace, které mohou být použity se všemi repozitáři v rámci této organizace org_labels_desc_manage=spravovat - milestones=Milníky commits=Commity commit=Commit @@ -1081,10 +1012,9 @@ file_view_rendered=Zobrazit vykreslené file_view_raw=Zobrazit v surovém stavu file_permalink=Trvalý odkaz file_too_large=Soubor je příliš velký pro zobrazení. -invisible_runes_line=`Tento řádek má neviditelné znaky Unicode` -ambiguous_runes_line=`Tento řádek má nejednoznačné znaky Unicode` -ambiguous_character=`%[1]c [U+%04[1]X] je zaměnitelný s %[2]c [U+%04[2]X]` - +invisible_runes_line=Tento řádek má neviditelné znaky Unicode +ambiguous_runes_line=Tento řádek má nejednoznačné znaky Unicode +ambiguous_character=%[1]c [U+%04[1]X] je zaměnitelný s %[2]c [U+%04[2]X] escape_control_characters=Escape sekvence unescape_control_characters=Bez escape sekvencí file_copy_permalink=Kopírovat trvalý odkaz @@ -1104,7 +1034,6 @@ normal_view=Normální zobrazení line=řádek lines=řádky from_comment=(komentář) - editor.add_file=Přidat soubor editor.new_file=Nový soubor editor.upload_file=Nahrát soubor @@ -1164,7 +1093,6 @@ editor.user_no_push_to_branch=Uživatel nemůže nahrávat do větve editor.require_signed_commit=Větev vyžaduje podepsaný commit editor.cherry_pick=Cherry-pick %s na: editor.revert=Vrátit %s na: - commits.desc=Procházet historii změn zdrojového kódu. commits.commits=Commity commits.nothing_to_compare=Tyto větve jsou stejné. @@ -1181,7 +1109,6 @@ commits.signed_by_untrusted_user=Podepsáno nedůvěryhodným uživatelem commits.signed_by_untrusted_user_unmatched=Podepsáno nedůvěryhodným uživatelem, který nesouhlasí s přispěvatelem commits.gpg_key_id=ID GPG klíče commits.ssh_key_fingerprint=Otisk klíče SSH - commit.operations=Operace commit.revert=Vrátit commit.revert-header=Vrátit: %s @@ -1189,15 +1116,12 @@ commit.revert-content=Vyberte větev pro návrat na: commit.cherry-pick=Cherry-pick commit.cherry-pick-header=Cherry-pick: %s commit.cherry-pick-content=Vyberte větev pro Cherry-pick na: - commitstatus.error=Chyba commitstatus.failure=Chyba commitstatus.pending=Čekající commitstatus.success=Úspěch - ext_issues=Přístup k externím úkolům ext_issues.desc=Odkaz na externí systém úkolů. - projects=Projekty projects.desc=Spravovat úkoly a požadavky na natažení na projektových nástěnkách. projects.description=Popis (volitelné) @@ -1236,7 +1160,6 @@ projects.column.assigned_to=Přiřazeno k projects.card_type.desc=Náhledy karet projects.card_type.images_and_text=Obrázky a text projects.card_type.text_only=Pouze text - issues.desc=Organizování hlášení chyb, úkolů a milníků. issues.filter_assignees=Filtrovat zpracovatele issues.filter_milestones=Filtrovat milník @@ -1285,25 +1208,25 @@ issues.add_labels=přidal/a %s štítky %s issues.remove_label=odstranil/a %s štítek %s issues.remove_labels=odstranil/a %s štítky %s issues.add_remove_labels=přidáno %s a odebráno %s štítků %s -issues.add_milestone_at=`přidal/a toto do milníku %s %s` -issues.add_project_at=`přidal/a toto do projektu %s %s` -issues.change_milestone_at=`upravil/a milník z %s na %s %s` -issues.change_project_at=`upravil/a projekt z %s na %s %s` -issues.remove_milestone_at=`odstranil/a toto z milníku %s %s` -issues.remove_project_at=`odstranil/a toto z projektu %s %s` -issues.deleted_milestone=`(odstraněno)` -issues.deleted_project=`(odstraněno)` -issues.self_assign_at=`přiřadil/a sobě toto %s` -issues.add_assignee_at=`byl přiřazen %s %s` -issues.remove_assignee_at=`byl odstraněn z přiřazení %s %s` -issues.remove_self_assignment=`odstranil/a jejich přiřazení %s` -issues.change_title_at=`změnil/a název z %s na %s %s` -issues.change_ref_at=`změnil/a referenci z %s na %s %s` -issues.remove_ref_at=`odstranil/a referenci %s %s` -issues.add_ref_at=`přidal/a referenci %s %s` -issues.delete_branch_at=`odstranil/a větev %s %s` +issues.add_milestone_at=přidal/a toto do milníku %s %s +issues.add_project_at=přidal/a toto do projektu %s %s +issues.change_milestone_at=upravil/a milník z %s na %s %s +issues.change_project_at=upravil/a projekt z %s na %s %s +issues.remove_milestone_at=odstranil/a toto z milníku %s %s +issues.remove_project_at=odstranil/a toto z projektu %s %s +issues.deleted_milestone=(odstraněno) +issues.deleted_project=(odstraněno) +issues.self_assign_at=přiřadil/a sobě toto %s +issues.add_assignee_at=byl přiřazen %s %s +issues.remove_assignee_at=byl odstraněn z přiřazení %s %s +issues.remove_self_assignment=odstranil/a jejich přiřazení %s +issues.change_title_at=změnil/a název z %s na %s %s +issues.change_ref_at=změnil/a referenci z %s na %s %s +issues.remove_ref_at=odstranil/a referenci %s %s +issues.add_ref_at=přidal/a referenci %s %s +issues.delete_branch_at=odstranil/a větev %s %s issues.filter_label=Štítek -issues.filter_label_exclude=`Chcete-li vyloučit štítky, použijte alt + click/enter` +issues.filter_label_exclude=Chcete-li vyloučit štítky, použijte alt + click/enter issues.filter_label_no_select=Všechny štítky issues.filter_milestone=Milník issues.filter_milestone_all=Všechny milníky @@ -1359,7 +1282,7 @@ issues.open_title=otevřený issues.closed_title=zavřený issues.draft_title=Koncept issues.num_comments=%d komentářů -issues.commented_at=`okomentoval %s` +issues.commented_at=okomentoval %s issues.delete_comment_confirm=Jste si jist, že chcete smazat tento komentář? issues.context.copy_link=Kopírovat odkaz issues.context.quote_reply=Citovat odpověď @@ -1372,16 +1295,16 @@ issues.close_comment_issue=Okomentovat a zavřít issues.reopen_issue=Znovuotevřít issues.reopen_comment_issue=Okomentovat a znovuotevřít issues.create_comment=Okomentovat -issues.closed_at=`uzavřel/a tento úkol %[2]s` -issues.reopened_at=`znovuotevřel/a tento úkol %[2]s` -issues.commit_ref_at=`odkázal na tento úkol z commitu %[2]s` -issues.ref_issue_from=`odkazoval/a na tento úkol %[4]s %[2]s` -issues.ref_pull_from=`odkazoval/a na tento požadavek na natažení %[4]s %[2]s` -issues.ref_closing_from=`odkazoval/a na požadavek na natažení %[4]s, který uzavře tento úkol %[2]s` -issues.ref_reopening_from=`odkazoval/a na požadavek na natažení %[4]s, který znovu otevře tento úkol %[2]s` -issues.ref_closed_from=`uzavřel/a tento úkol %[4]s %[2]s` -issues.ref_reopened_from=`znovu otevřel/a tento úkol %[4]s %[2]s` -issues.ref_from=`z %[1]s` +issues.closed_at=uzavřel/a tento úkol %[2]s +issues.reopened_at=znovuotevřel/a tento úkol %[2]s +issues.commit_ref_at=odkázal na tento úkol z commitu %[2]s +issues.ref_issue_from=odkazoval/a na tento úkol %[4]s %[2]s +issues.ref_pull_from=odkazoval/a na tento požadavek na natažení %[4]s %[2]s +issues.ref_closing_from=odkazoval/a na požadavek na natažení %[4]s, který uzavře tento úkol %[2]s +issues.ref_reopening_from=odkazoval/a na požadavek na natažení %[4]s, který znovu otevře tento úkol %[2]s +issues.ref_closed_from=uzavřel/a tento úkol %[4]s %[2]s +issues.ref_reopened_from=znovu otevřel/a tento úkol %[4]s %[2]s +issues.ref_from=z %[1]s issues.author=Autor issues.role.owner=Vlastník issues.role.member=Člen @@ -1412,8 +1335,8 @@ issues.label.filter_sort.reverse_alphabetically=Od konce abecedy issues.label.filter_sort.by_size=Nejmenší velikost issues.label.filter_sort.reverse_by_size=Největší velikost issues.num_participants=%d účastníků -issues.attachment.open_tab=`Klikněte pro zobrazení „%s“ v nové záložce` -issues.attachment.download=`Klikněte pro stažení „%s“` +issues.attachment.open_tab=Klikněte pro zobrazení „%s“ v nové záložce +issues.attachment.download=Klikněte pro stažení „%s“ issues.subscribe=Odebírat issues.unsubscribe=Zrušit odběr issues.unpin_issue=Odepnout problém @@ -1445,30 +1368,30 @@ issues.delete.text=Opravdu chcete tento úkol smazat? (Tím se trvale odstraní issues.tracker=Sledování času issues.start_tracking_short=Spustit časovač issues.start_tracking=Spustit sledování času -issues.start_tracking_history=`započal/a práci %s` +issues.start_tracking_history=započal/a práci %s issues.tracker_auto_close=Časovač se automaticky zastaví po zavření tohoto úkolu -issues.tracking_already_started=`Již jste spustili sledování času na jiném úkolu!` +issues.tracking_already_started=Již jste spustili sledování času na jiném úkolu! issues.stop_tracking=Zastavit časovač -issues.stop_tracking_history=`ukončil/a práci %s` +issues.stop_tracking_history=ukončil/a práci %s issues.cancel_tracking=Zahodit issues.add_time=Přidat čas ručně issues.del_time=Odstranit tento časový záznam issues.add_time_short=Přidat čas issues.add_time_cancel=Zrušit -issues.add_time_history=`přidal/a strávený čas %s` -issues.del_time_history=`odstranil/a strávený čas %s` +issues.add_time_history=přidal/a strávený čas %s +issues.del_time_history=odstranil/a strávený čas %s issues.add_time_hours=Hodiny issues.add_time_minutes=Minuty issues.add_time_sum_to_small=Čas nebyl zadán. issues.time_spent_total=Celkový strávený čas -issues.time_spent_from_all_authors=`Celkový strávený čas: %s` +issues.time_spent_from_all_authors=Celkový strávený čas: %s issues.due_date=Termín dokončení issues.invalid_due_date_format=Termín dokončení musí být ve formátu 'rrrr-mm-dd'. issues.error_modifying_due_date=Změna termínu dokončení selhala. issues.error_removing_due_date=Odstranění termínu dokončení selhalo. issues.push_commit_1=přidal/a %d commit %s issues.push_commits_n=přidal/a %d commity %s -issues.force_push_codes=`vynucené nahrání %[1]s od %[2]s do %[4]s %[6]s` +issues.force_push_codes=vynucené nahrání %[1]s od %[2]s do %[4]s %[6]s issues.force_push_compare=Porovnat issues.due_date_form=rrrr-mm-dd issues.due_date_form_add=Přidat termín dokončení @@ -1490,8 +1413,8 @@ issues.dependency.add=Přidat závislost… issues.dependency.cancel=Zrušit issues.dependency.remove=Odstranit issues.dependency.remove_info=Odstranit tuto závislost -issues.dependency.added_dependency=`přidal/a novou závislost %s` -issues.dependency.removed_dependency=`odstranil/a závislost %s` +issues.dependency.added_dependency=přidal/a novou závislost %s +issues.dependency.removed_dependency=odstranil/a závislost %s issues.dependency.pr_closing_blockedby=Uzavření tohoto požadavku na natažení je blokováno následujícími úkoly issues.dependency.issue_closing_blockedby=Uzavření tohoto úkolu je blokováno následujícími úkoly issues.dependency.issue_close_blocks=Tento úkol blokuje uzavření následujících úkolů @@ -1544,10 +1467,8 @@ issues.content_history.delete_from_history=Smazat z historie issues.content_history.delete_from_history_confirm=Smazat z historie? issues.content_history.options=Možnosti issues.reference_link=Reference: %s - compare.compare_base=základní compare.compare_head=porovnat - pulls.desc=Povolit požadavky na natažení a posuzování kódu. pulls.new=Nový požadavek na natažení pulls.view=Zobrazit požadavek na natažení @@ -1569,11 +1490,11 @@ pulls.filter_branch=Filtrovat větev pulls.no_results=Nebyly nalezeny žádné výsledky. pulls.nothing_to_compare=Tyto větve jsou stejné. Není potřeba vytvářet požadavek na natažení. pulls.nothing_to_compare_and_allow_empty_pr=Tyto větve jsou stejné. Tento požadavek na natažení bude prázdný. -pulls.has_pull_request=`Požadavek na natažení mezi těmito větvemi již existuje: %[2]s#%[3]d` +pulls.has_pull_request=Požadavek na natažení mezi těmito větvemi již existuje: %[2]s#%[3]d pulls.create=Vytvořit požadavek na natažení pulls.title_desc=chce sloučit %[1]d commity z větve %[2]s do %[3]s pulls.merged_title_desc=sloučil %[1]d commity z větve %[2]s do větve %[3]s před %[4]s -pulls.change_target_branch_at=`změnil/a cílovou větev z %s na %s %s` +pulls.change_target_branch_at=změnil/a cílovou větev z %s na %s %s pulls.tab_conversation=Konverzace pulls.tab_commits=Commity pulls.tab_files=Změněné soubory @@ -1583,7 +1504,7 @@ pulls.merged=Sloučený pulls.manually_merged=Sloučeno ručně pulls.merged_info_text=Větev %s může být nyní odstraněna. pulls.is_closed=Požadavek na natažení byl uzavřen. -pulls.title_wip_desc=`Začněte název s %s a zamezíte tak nechtěnému sloučení požadavku na natažení.` +pulls.title_wip_desc=Začněte název s %s a zamezíte tak nechtěnému sloučení požadavku na natažení. pulls.cannot_merge_work_in_progress=Tento požadavek na natažení je označen jako probíhající práce. pulls.still_in_progress=Stále probíhá? pulls.add_prefix=Přidat prefix %s @@ -1608,7 +1529,6 @@ pulls.reject_count_n=%d žádosti o změnu pulls.waiting_count_1=%d čekající posouzení pulls.waiting_count_n=%d čekající posouzení pulls.wrong_commit_id=ID commitu musí být ID commitu v cílové větvi - pulls.no_merge_desc=Tento požadavek na natažení nemůže být sloučen, protože všechny možnosti repozitáře na sloučení jsou zakázány. pulls.no_merge_helper=Povolte možnosti sloučení v nastavení repozitáře nebo proveďte sloučení požadavku na natažení ručně. pulls.no_merge_wip=Požadavek na natažení nemůže být sloučen protože je označen jako nedokončený. @@ -1621,7 +1541,6 @@ pulls.squash_merge_pull_request=Vytvořit squash commit pulls.merge_manually=Sloučeno ručně pulls.merge_commit_id=ID slučovacího commitu pulls.require_signed_wont_sign=Větev vyžaduje podepsané commity, ale toto sloučení nebude podepsáno - pulls.invalid_merge_option=Nemůžete použít tuto možnost sloučení pro tento požadavek na natažení. pulls.merge_conflict=Sloučení selhalo: Došlo ke konfliktu při sloučení. Tip: Zkuste jinou strategii pulls.merge_conflict_summary=Chybové hlášení @@ -1633,7 +1552,7 @@ pulls.head_out_of_date=Sloučení selhalo: Hlavní revize byla aktualizován př pulls.push_rejected=Sloučení selhalo: Nahrání bylo zamítnuto. Zkontrolujte háčky Gitu pro tento repozitář. pulls.push_rejected_summary=Úplná zpráva o odmítnutí pulls.push_rejected_no_message=Sloučení se nezdařilo: Nahrání bylo odmítnuto, ale nebyla nalezena žádná vzdálená zpráva.
Zkontrolujte háčky gitu pro tento repozitář -pulls.open_unmerged_pull_exists=`Nemůžete provést operaci znovuotevření protože je tu čekající požadavek na natažení (#%d) s identickými vlastnostmi.` +pulls.open_unmerged_pull_exists=Nemůžete provést operaci znovuotevření protože je tu čekající požadavek na natažení (#%d) s identickými vlastnostmi. pulls.status_checking=Některé kontroly jsou nedořešeny pulls.status_checks_success=Všechny kontroly byly úspěšné pulls.status_checks_warning=Některé kontroly nahlásily varování @@ -1646,29 +1565,22 @@ pulls.update_branch_rebase=Aktualizovat větev pomocí rebase pulls.update_branch_success=Aktualizace větve byla úspěšná pulls.update_not_allowed=Nemáte oprávnění aktualizovat větev pulls.outdated_with_base_branch=Tato větev je zastaralá oproti základní větvi -pulls.closed_at=`uzavřel/a tento požadavek na natažení %[2]s` -pulls.reopened_at=`znovuotevřel/a tento požadavek na natažení %[2]s` -pulls.merge_instruction_hint=`Můžete také zobrazit instrukce příkazové řádky.` +pulls.closed_at=uzavřel/a tento požadavek na natažení %[2]s +pulls.reopened_at=znovuotevřel/a tento požadavek na natažení %[2]s +pulls.merge_instruction_hint=Můžete také zobrazit instrukce příkazové řádky. pulls.merge_instruction_step1_desc=Z vašeho repositáře projektu se podívejte na novou větev a vyzkoušejte změny. pulls.merge_instruction_step2_desc=Slučte změny a aktualizujte je na Gitea. - pulls.auto_merge_button_when_succeed=(Když kontroly uspějí) pulls.auto_merge_when_succeed=Automaticky sloučit, když všechny kontroly uspějí pulls.auto_merge_newly_scheduled=Požadavek na natažení byl naplánován na sloučení, jakmile všechny kontroly uspějí. pulls.auto_merge_has_pending_schedule=%[1]s naplánoval/a tento požadavek na natažení pro automatické sloučení, když všechny kontroly uspějí v %[2]s. - pulls.auto_merge_cancel_schedule=Zrušit automatické sloučení pulls.auto_merge_not_scheduled=Tento požadavek na natažení není naplánován na automatické sloučení. pulls.auto_merge_canceled_schedule=Automatické sloučení bylo zrušeno pro tento požadavek na natažení. - -pulls.auto_merge_newly_scheduled_comment=`požadavek na automatické sloučení tohoto požadavku na natažení je naplánován, když všechny kontroly uspějí %[1]s` -pulls.auto_merge_canceled_schedule_comment=`zrušil/a automatické sloučení tohoto požadavku na natažení, když všechny kontroly uspějí %[1]s` - +pulls.auto_merge_newly_scheduled_comment=požadavek na automatické sloučení tohoto požadavku na natažení je naplánován, když všechny kontroly uspějí %[1]s +pulls.auto_merge_canceled_schedule_comment=zrušil/a automatické sloučení tohoto požadavku na natažení, když všechny kontroly uspějí %[1]s pulls.delete.title=Odstranit tento požadavek na natažení? pulls.delete.text=Opravdu chcete tento požadavek na natažení smazat? (Tím se trvale odstraní veškerý obsah. Pokud jej hodláte archivovat, zvažte raději jeho uzavření.) - - - milestones.new=Nový milník milestones.closed=Zavřen dne %s milestones.no_due_date=Bez lhůty dokončení @@ -1692,11 +1604,8 @@ milestones.filter_sort.least_complete=Nejméně dokončené milestones.filter_sort.most_complete=Nejvíce dokončené milestones.filter_sort.most_issues=Nejvíce úkolů milestones.filter_sort.least_issues=Nejméně úkolů - - ext_wiki=Přístup k externí Wiki ext_wiki.desc=Odkaz do externí Wiki. - wiki=Wiki wiki.welcome=Vítejte ve Wiki. wiki.welcome_desc=Wiki vám umožní psát a sdílet dokumentaci se spolupracovníky. @@ -1720,7 +1629,6 @@ wiki.page_already_exists=Stránka Wiki se stejným názvem již existuje. wiki.pages=Stránky wiki.last_updated=Naposledy aktualizováno: %s wiki.page_name_desc=Zadejte název této Wiki stránky. Některé speciální názvy jsou: „Home“, „_Sidebar“ a „_Footer“. - activity=Aktivita activity.period.filter_label=Období: activity.period.daily=1 den @@ -1786,7 +1694,6 @@ activity.git_stats_addition_n=%d přidání activity.git_stats_and_deletions=a activity.git_stats_deletion_1=%d odebrání activity.git_stats_deletion_n=%d odebrání - search=Vyhledat search.search_repo=Hledat repozitář search.type.tooltip=Druh vyhledávání @@ -1797,7 +1704,6 @@ search.match.tooltip=Zahrnout pouze výsledky, které odpovídají přesnému hl search.results=Výsledky hledání „%s“ v %s search.code_no_results=Nebyl nalezen žádný zdrojový kód odpovídající hledanému výrazu. search.code_search_unavailable=V současné době není vyhledávání kódu dostupné. Obraťte se na správce webu. - settings=Nastavení settings.desc=Nastavení je místo, kde můžete měnit nastavení repozitáře settings.options=Repozitář @@ -1821,7 +1727,6 @@ settings.mirror_settings.last_update=Poslední aktualizace settings.mirror_settings.push_mirror.none=Nenastavena žádná zrcadla pro nahrání settings.mirror_settings.push_mirror.remote_url=URL vzdáleného Git repozitáře settings.mirror_settings.push_mirror.add=Přidat zrcadlo pro nahrání - settings.sync_mirror=Synchronizovat nyní settings.mirror_sync_in_progress=Právě probíhá synchronizace zrcadla. Zkuste to za chvíli. settings.site=Webová stránka @@ -2172,7 +2077,6 @@ settings.rename_branch_success=Větev %s byla úspěšně přejmenována na %s. settings.rename_branch_from=starý název větve settings.rename_branch_to=nový název větve settings.rename_branch=Přejmenovat větev - diff.browse_source=Procházet zdrojové kódy diff.parent=rodič diff.commit=revize @@ -2226,7 +2130,6 @@ diff.image.overlay=Překrytí diff.has_escaped=Tento řádek má skryté znaky Unicode diff.show_file_tree=Zobrazit souborový strom diff.hide_file_tree=Skrýt souborový strom - releases.desc=Sledování verzí projektu a souborů ke stažení. release.releases=Vydání release.detail=Podrobnosti vydání @@ -2267,7 +2170,6 @@ release.download_count=Stažení: %s release.add_tag_msg=Použít název a obsah vydání jako zprávu značky. release.add_tag=Vytvořit pouze značku release.tags_for=Značky pro %s - branch.name=Jméno větve branch.search=Hledat větve branch.delete_head=Smazat @@ -2283,20 +2185,15 @@ branch.confirm_rename_branch=Přejmenovat větev branch.create_branch_operation=Vytvořit větev branch.new_branch=Vytvořit novou větev branch.renamed=Větev %s byla přejmenována na %s. - tag.create_tag=Vytvořit značku %s tag.create_tag_operation=Vytvořit značku tag.confirm_create_tag=Vytvořit značku - tag.create_success=Značka „%s“ byla vytvořena. - topic.manage_topics=Spravovat témata topic.done=Hotovo topic.count_prompt=Nelze vybrat více než 25 témat - find_file.go_to_file=Přejít na soubor find_file.no_matching=Nebyl nalezen žádný odpovídající soubor - error.csv.too_large=Tento soubor nelze vykreslit, protože je příliš velký. error.csv.unexpected=Tento soubor nelze vykreslit, protože obsahuje neočekávaný znak na řádku %d ve sloupci %d. error.csv.invalid_field_count=Soubor nelze vykreslit, protože má nesprávný počet polí na řádku %d. @@ -2323,9 +2220,7 @@ team_access_desc=Přístup k repozitáři team_permission_desc=Oprávnění team_unit_desc=Povolit přístup do částí repozitáře team_unit_disabled=(zakázaná) - form.create_org_not_allowed=Nemáte oprávnění vytvářet nové organizace. - settings=Nastavení settings.options=Organizace settings.full_name=Celé jméno @@ -2338,7 +2233,6 @@ settings.visibility.public=Veřejná settings.visibility.limited_shortname=Omezený settings.visibility.private=Soukromá (viditelné jen členům organizace) settings.visibility.private_shortname=Soukromý - settings.update_settings=Upravit nastavení settings.update_setting_success=Nastavení organizace bylo upraveno. settings.change_orgname_redirect_prompt=Staré jméno bude přesměrovávat, dokud nebude znovu obsazeno. @@ -2350,9 +2244,7 @@ settings.confirm_delete_account=Potvrdit smazání settings.delete_org_title=Smazat organizaci settings.delete_org_desc=Tato organizace bude trvale smazána. Pokračovat? settings.hooks_desc=Přidat webové háčky, které budou spouštěny pro všechny repozitáře v této organizaci. - settings.labels_desc=Přidejte štítky, které mohou být použity pro úkoly všech repositářů v rámci této organizace. - members.membership_visibility=Viditelnost členství: members.public=Viditelný members.public_helper=skrýt @@ -2367,7 +2259,6 @@ members.leave=Opustit members.leave.detail=Opustit %s? members.invite_desc=Přidat nového člena do %s: members.invite_now=Pozvat teď - teams.join=Připojit teams.leave=Opustit teams.leave.detail=Opustit %s? @@ -2432,7 +2323,6 @@ monitor=Sledování first_page=První last_page=Poslední total=Celkem: %d - dashboard.statistic=Souhrn dashboard.operations=Operace údržby dashboard.system_status=Status systému @@ -2509,7 +2399,6 @@ dashboard.delete_old_system_notices=Odstranit všechna stará systémová upozor dashboard.stop_zombie_tasks=Zastavit zombie úkoly dashboard.stop_endless_tasks=Zastavit nekonečné úkoly dashboard.cancel_abandoned_jobs=Zrušit opuštěné úlohy - users.user_manage_panel=Správa uživatelských účtů users.new_account=Vytvořit uživatelský účet users.name=Uživatelské jméno @@ -2561,7 +2450,6 @@ users.list_status_filter.is_prohibit_login=Zakázat přihlášení users.list_status_filter.not_prohibit_login=Povolit přihlášení users.list_status_filter.is_2fa_enabled=2FA povoleno users.list_status_filter.not_2fa_enabled=2FA zakázáno - emails.email_manage_panel=Správa e-mailů uživatele emails.primary=Hlavní emails.activated=Aktivován @@ -2574,13 +2462,11 @@ emails.not_updated=Aktualizace požadované e-mailové adresy se nezdařila: %v emails.duplicate_active=Tato e-mailová adresa je již aktivní pro jiného uživatele. emails.change_email_header=Aktualizovat vlastnosti e-mailu emails.change_email_text=Opravdu chcete aktualizovat tuto e-mailovou adresu? - orgs.org_manage_panel=Správa organizací orgs.name=Název orgs.teams=Týmy orgs.members=Členové orgs.new_orga=Nová organizace - repos.repo_manage_panel=Správa repozitáře repos.unadopted=Nepřijaté repozitáře repos.unadopted.no_more=Nebyly nalezeny žádné další nepřijaté repositáře @@ -2592,7 +2478,6 @@ repos.stars=Oblíbení repos.forks=Rozštěpení repos.issues=Úkoly repos.size=Velikost - packages.package_manage_panel=Správa balíčků packages.total_size=Celková velikost: %s packages.owner=Vlastník @@ -2603,15 +2488,12 @@ packages.type=Typ packages.repository=Repozitář packages.size=Velikost packages.published=Publikováno - defaulthooks=Výchozí webové háčky defaulthooks.add_webhook=Přidat výchozí webový háček defaulthooks.update_webhook=Aktualizovat výchozí webový háček - systemhooks=Systémové webové háčky systemhooks.add_webhook=Přidat systémový webový háček systemhooks.update_webhook=Aktualizovat systémový webový háček - auths.auth_manage_panel=Správa zdroje ověřování auths.new=Přidat zdroj ověřování auths.name=Název @@ -2724,7 +2606,6 @@ auths.still_in_used=Zdroj ověřování je stále používán. Nejprve převeďt auths.deletion_success=Zdroj ověřování byl smazán. auths.login_source_of_type_exist=Zdroj ověřování tohoto typu již existuje. auths.invalid_openIdConnectAutoDiscoveryURL=Neplatná URL adresa pro automatické vyhledání (musí být platná adresa URL začínající http:// nebo https://) - config.server_config=Nastavení serveru config.app_name=Název stránky config.app_ver=Verze Gitea @@ -2742,7 +2623,6 @@ config.lfs_root_path=Kořenový adresář LFS config.log_file_root_path=Adresář logů config.script_type=Typ skriptu config.reverse_auth_user=Uživatel obráceného ověření - config.ssh_config=Nastavení SSH config.ssh_enabled=Zapnutý config.ssh_start_builtin_server=Použít vestavěný server @@ -2754,12 +2634,10 @@ config.ssh_key_test_path=Cesta testu klíčů config.ssh_keygen_path=Cesta ke generátoru klíčů ('ssh-keygen') config.ssh_minimum_key_size_check=Kontrola minimální velikosti klíčů config.ssh_minimum_key_sizes=Minimální velikost klíčů - config.lfs_config=Nastavení LFS config.lfs_enabled=Povoleno config.lfs_content_path=Cesta k obsahu LFS config.lfs_http_auth_expiry=Vypršení autorizace LFS HTTPS - config.db_config=Nastavení databáze config.db_type=Typ config.db_host=Server @@ -2768,7 +2646,6 @@ config.db_user=Uživatelské jméno config.db_schema=Schéma config.db_ssl_mode=SSL config.db_path=Cesta - config.service_config=Nastavení služby config.register_email_confirm=Pro registraci vyžadovat potvrzení e-mailu config.disable_register=Vypnout možnost uživatelské registrace @@ -2791,12 +2668,10 @@ config.default_allow_only_contributors_to_track_time=Povolit sledování času p config.no_reply_address=Skrytá e-mailová doména config.default_visibility_organization=Výchozí viditelnost pro nové organizace config.default_enable_dependencies=Povolit závislosti úkolů ve výchozím stavu - config.webhook_config=Nastavení webových háčků config.queue_length=Délka fronty config.deliver_timeout=Časový limit doručení config.skip_tls_verify=Přeskočit verifikaci TLS - config.mailer_config=Nastavení odesílání e-mailů config.mailer_enabled=Zapnutý config.mailer_enable_helo=Povolit HELO @@ -2812,16 +2687,13 @@ config.mailer_sendmail_timeout=Časový limit Sandmail config.mailer_use_dummy=Fiktivní config.test_email_placeholder=E-mail (např.: test@example.com) config.send_test_mail=Odeslat zkušební e-mail - config.oauth_config=Nastavení ověření OAuth config.oauth_enabled=Zapnutý - config.cache_config=Nastavení mezipaměti config.cache_adapter=Adaptér mezipaměti config.cache_interval=Interval mezipaměti config.cache_conn=Připojení mezipaměti config.cache_item_ttl=Čas vypršení položky v mezipaměti - config.session_config=Nastavení relace config.session_provider=Poskytovatel relace config.provider_config=Nastavení poskytovatele @@ -2830,12 +2702,10 @@ config.gc_interval_time=Čas intervalu GC config.session_life_time=Doba trvání relace config.https_only=Pouze protokol HTTPS config.cookie_life_time=Doba života souboru cookie - config.picture_config=Nastavení obrázku a avataru config.picture_service=Služba ikon uživatelů config.disable_gravatar=Zakázat službu Gravatar config.enable_federated_avatar=Povolit avatary z veřejných zdrojů - config.git_config=Konfigurace Gitu config.git_disable_diff_highlight=Zakázat zvýraznění syntaxe v rozdílovém zobrazení config.git_max_diff_lines=Maximální počet rozdílových řádků jednoho souboru @@ -2847,16 +2717,12 @@ config.git_mirror_timeout=Časový limit aktualizace zrcadla config.git_clone_timeout=Časový limit operace naklonování config.git_pull_timeout=Časový limit operace stažení config.git_gc_timeout=Časový limit operace GC - config.log_config=Nastavení logů config.disabled_logger=Zakázané config.access_log_mode=Režim logování přístupu config.xorm_log_sql=Logovat SQL - config.get_setting_failed=Získání nastavení %s se nezdařilo config.set_setting_failed=Nastavení %s se nezdařilo - - monitor.cron=Naplánované úlohy monitor.name=Název monitor.schedule=Rozvrh @@ -2873,7 +2739,6 @@ monitor.process.cancel=Zrušit proces monitor.process.cancel_desc=Zrušení procesu může způsobit ztrátu dat monitor.process.cancel_notices=Zrušit: %s? monitor.process.children=Potomek - monitor.queues=Fronty monitor.queue=Fronta: %s monitor.queue.name=Název @@ -2890,7 +2755,6 @@ monitor.queue.settings.submit=Aktualizovat nastavení monitor.queue.settings.changed=Nastavení aktualizováno monitor.queue.settings.remove_all_items=Odstranit vše monitor.queue.settings.remove_all_items_done=Všechny položky ve frontě byly odstraněny. - notices.system_notice_list=Systémová oznámení notices.view_detail_header=Zobrazit detaily oznámení notices.operations=Operace @@ -2910,16 +2774,16 @@ notices.delete_success=Systémové upozornění bylo smazáno. create_repo=vytvořil/a repozitář %s rename_repo=přejmenoval/a repozitář z %[1]s na %[3]s commit_repo=nahrál/a do %[3]s v %[4]s -create_issue=`otevřel/a úkol %[3]s#%[2]s` -close_issue=`uzavřel/a úkol %[3]s#%[2]s` -reopen_issue=`znovuotevřel/a úkol %[3]s#%[2]s` -create_pull_request=`vytvořil/a požadavek na natažení %[3]s#%[2]s` -close_pull_request=`uzavřel/a požadavek na natažení %[3]s#%[2]s` -reopen_pull_request=`znovuotevřel/a požadavek na natažení %[3]s#%[2]s` -comment_issue=`okomentoval/a problém %[3]s#%[2]s` -comment_pull=`okomentoval/a požadavek na natažení %[3]s#%[2]s` -merge_pull_request=`sloučil/a požadavek na natažení %[3]s#%[2]s` -auto_merge_pull_request=`automaticky sloučen požadavek na natažení %[3]s#%[2]s` +create_issue=otevřel/a úkol %[3]s#%[2]s +close_issue=uzavřel/a úkol %[3]s#%[2]s +reopen_issue=znovuotevřel/a úkol %[3]s#%[2]s +create_pull_request=vytvořil/a požadavek na natažení %[3]s#%[2]s +close_pull_request=uzavřel/a požadavek na natažení %[3]s#%[2]s +reopen_pull_request=znovuotevřel/a požadavek na natažení %[3]s#%[2]s +comment_issue=okomentoval/a problém %[3]s#%[2]s +comment_pull=okomentoval/a požadavek na natažení %[3]s#%[2]s +merge_pull_request=sloučil/a požadavek na natažení %[3]s#%[2]s +auto_merge_pull_request=automaticky sloučen požadavek na natažení %[3]s#%[2]s transfer_repo=předal/a repozitář %s uživateli/organizaci %s push_tag=nahrál/a značku %[3]s do %[4]s delete_tag=smazal/a značku %[2]s z %[3]s @@ -2930,10 +2794,10 @@ compare_commits_general=Porovnat revize mirror_sync_push=synchronizoval/a commity do %[3]s v %[4]s ze zrcadla mirror_sync_create=synchronizoval/a novou referenci %[3]s do %[4]s ze zrcadla mirror_sync_delete=synchronizoval/a a smazal/a referenci %[2]s v %[3]s ze zrcadla -approve_pull_request=`schválil/a %[3]s#%[2]s` -reject_pull_request=`navrhl/a změny pro %[3]s#%[2]s` -publish_release=`vydal/a "%[4]s" v %[3]s` -review_dismissed=`zamítl/a posouzení z %[4]s pro %[3]s#%[2]s` +approve_pull_request=schválil/a %[3]s#%[2]s +reject_pull_request=navrhl/a změny pro %[3]s#%[2]s +publish_release=vydal/a "%[4]s" v %[3]s +review_dismissed=zamítl/a posouzení z %[4]s pro %[3]s#%[2]s review_dismissed_reason=Důvod: create_branch=vytvořil/a větev %[3]s v %[4]s starred_repo=si oblíbil/a %[2]s @@ -3155,9 +3019,7 @@ management=Správa tajných klíčů [actions] actions=Akce - unit.desc=Spravovat akce - status.unknown=Neznámý status.waiting=Čekání status.running=Probíhá @@ -3165,7 +3027,6 @@ status.success=Úspěch status.failure=Chyba status.skipped=Přeskočeno status.blocked=Blokováno - runners.new=Vytvořit nový runner runners.new_notice=Jak spustit runner runners.status=Status @@ -3195,18 +3056,13 @@ runners.status.idle=Nečinný runners.status.active=Aktivní runners.status.offline=Offline runners.version=Verze - runs.all_workflows=Všechny pracovní postupy runs.commit=Commit runs.no_matching_runner_helper=Žádný odpovídající runner: %s runs.status=Status - - - [projects] type-3.display_name=Projekt organizace [git.filemode] symbolic_link=Symbolický odkaz - diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index e3ee15a873..abbbbd211f 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -24,7 +24,6 @@ enable_javascript=Diese Website benötigt JavaScript. toc=Inhaltsverzeichnis licenses=Lizenzen return_to_gitea=Zurück zu Gitea - username=Benutzername email=E-Mail-Adresse password=Passwort @@ -34,7 +33,6 @@ captcha=CAPTCHA twofa=Zwei-Faktor-Authentifizierung twofa_scratch=Zwei-Faktor-Einmalpasswort passcode=PIN - webauthn_insert_key=Hardware-Sicherheitsschlüssel einstecken webauthn_sign_in=Drücke den Knopf auf deinem Sicherheitsschlüssel. Wenn dein Sicherheitsschlüssel keinen Knopf hat, stecke ihn erneut ein. webauthn_press_button=Drücke den Knopf auf deinem Sicherheitsschlüssel… @@ -48,7 +46,6 @@ webauthn_error_duplicated=Für diese Anfrage ist der Sicherheitsschlüssel nicht webauthn_error_empty=Du musst einen Namen für diesen Schlüssel festlegen. webauthn_error_timeout=Das Zeitlimit wurde erreicht, bevor dein Schlüssel gelesen werden konnte. Bitte lade die Seite erneut. webauthn_reload=Neu laden - repository=Repository organization=Organisation mirror=Mirror @@ -66,18 +63,15 @@ settings=Einstellungen your_profile=Profil your_starred=Favoriten your_settings=Einstellungen - all=Alle sources=Quellen mirrors=Mirrors collaborative=Kollaborativ forks=Forks - activities=Aktivitäten pull_requests=Pull-Requests issues=Issues milestones=Meilensteine - ok=OK cancel=Abbrechen retry=Erneut versuchen @@ -90,11 +84,9 @@ remove=Löschen remove_all=Alle entfernen remove_label_str=Element "%s " entfernen edit=Bearbeiten - enabled=Aktiviert disabled=Deaktiviert locked=Gesperrt - copy=Kopieren copy_url=URL kopieren copy_content=Inhalt kopieren @@ -102,41 +94,32 @@ copy_branch=Branchenname kopieren copy_success=Kopiert! copy_error=Kopieren fehlgeschlagen copy_type_unsupported=Dieser Dateityp kann nicht kopiert werden - write=Verfassen preview=Vorschau loading=Laden… - step1=Schritt 1: step2=Schritt 2: - error=Fehler error404=Die Seite, die Du versuchst aufzurufen, existiert nicht oder Du bist nicht berechtigt, diese anzusehen. - never=Niemals unknown=Unbekannt - rss_feed=RSS Feed - pin=Anheften unpin=Loslösen - artifacts=Artefakte - concept_system_global=Global concept_user_individual=Individuum concept_code_repository=Repository concept_user_organization=Organisation - show_timestamps=Zeitstempel anzeigen show_log_seconds=Sekunden anzeigen show_full_screen=Vollbild anzeigen download_logs=Logs herunterladen - confirm_delete_selected=Alle ausgewählten Elemente löschen? - name=Name value=Wert +go_back=Zurück +archived=Archiviert [aria] navbar=Navigationsleiste @@ -187,6 +170,7 @@ lightweight=Leichtgewicht lightweight_desc=Gitea hat minimale Systemanforderungen und kann selbst auf einem günstigen und stromsparenden Raspberry Pi betrieben werden! license=Quelloffen license_desc=Der komplette Code befindet sich auf GitHub! Unterstütze uns bei der Verbesserung dieses Projekts. Trau dich! +install_desc=Starte einfach die Anwendung für deine Plattform oder nutze Docker. Es existieren auch paketierte Versionen. [install] install=Installation @@ -216,7 +200,6 @@ err_empty_admin_email=Die Administrator-E-Mail darf nicht leer sein. err_admin_name_is_reserved=Administratornutzername ist ungültig, der Nutzername ist reserviert err_admin_name_pattern_not_allowed=Administrator-Benutzername ist ungültig, der Benutzername entspricht einem reservierten Muster err_admin_name_is_invalid=Administratornutzername ist ungültig - general_title=Allgemeine Einstellungen app_name=Seitentitel app_name_helper=Du kannst hier den Namen deines Unternehmens eingeben. @@ -236,7 +219,6 @@ app_url=Gitea-Basis-URL app_url_helper=Adresse für HTTP(S)-Klon-URLs und E-Mail-Benachrichtigungen. log_root_path=Logdateipfad log_root_path_helper=Log-Dateien werden in diesem Verzeichnis gespeichert. - optional_title=Optionale Einstellungen email_title=E-Mail-Einstellungen smtp_addr=SMTP-Host @@ -313,19 +295,16 @@ view_home=%s ansehen search_repos=Finde ein Repository… filter=Andere Filter filter_by_team_repositories=Nach Team-Repositories filtern -feed_of=`Feed von "%s"` - +feed_of=Feed von "%s" show_archived=Archiviert archived=Archiviert show_both_archived_unarchived=Archivierte und nicht archivierte anzeigen show_only_archived=Nur archivierte anzeigen show_only_unarchived=Nur nicht archivierte anzeigen - show_private=Privat show_both_private_public=Öffentliche und private anzeigen show_only_private=Nur private anzeigen show_only_public=Nur öffentliche anzeigen - issues.in_your_repos=Eigene Repositories [explore] @@ -345,12 +324,11 @@ repo_no_results=Keine passenden Repositories gefunden. user_no_results=Keine passenden Benutzer gefunden. org_no_results=Keine passenden Organisationen gefunden. code_no_results=Es konnte kein passender Code für deinen Suchbegriff gefunden werden. -code_search_results=`Suchergebnisse für "%s"` +code_search_results=Suchergebnisse für "%s" code_last_indexed_at=Zuletzt indexiert %s relevant_repositories_tooltip=Repositories, die Forks sind oder die kein Thema, kein Symbol und keine Beschreibung haben, werden ausgeblendet. relevant_repositories=Es werden nur relevante Repositories angezeigt, ungefilterte Ergebnisse anzeigen. - [auth] create_new_account=Konto anlegen register_helper_msg=Hast du bereits ein Konto? Jetzt anmelden! @@ -410,40 +388,39 @@ authorize_application=Anwendung autorisieren authorize_redirect_notice=Du wirst zu %s weitergeleitet, wenn du diese Anwendung autorisierst. authorize_application_created_by=Diese Anwendung wurde von %s erstellt. authorize_application_description=Wenn du diese Anwendung autorisierst, wird sie die Berechtigung erhalten, alle Informationen zu deinem Account zu bearbeiten oder zu lesen. Dies beinhaltet auch private Repositories und Organisationen. -authorize_title=`"%s" den Zugriff auf deinen Account gestatten?` +authorize_title="%s" den Zugriff auf deinen Account gestatten? authorization_failed=Autorisierung fehlgeschlagen sspi_auth_failed=SSPI-Authentifizierung fehlgeschlagen password_pwned_err=Anfrage an HaveIBeenPwned konnte nicht abgeschlossen werden +prohibit_login_desc=Die Anmeldung mit diesem Konto ist nicht gestattet. Bitte kontaktiere den Administrator. +invalid_code_forgot_password=Dein Bestätigungscode ist ungültig oder abgelaufen. Klicke hier, um eine neue Sitzung zu starten. +reset_password_wrong_user=Du bist angemeldet als %s, aber der Link zur Kontowiederherstellung ist für %s +openid_signin_desc=Gib deine OpenID-URI ein, zum Beispiel alice.openid.example.org oder https://openid.example.org/alice. +authorization_failed_desc=Die Autorisierung ist fehlgeschlagen, da wir eine ungültige Anfrage erkannt haben. Bitte kontaktiere den Betreuer der App, die du zu autorisieren versucht hast. +password_pwned=Das von dir gewählte Passwort befindet sich auf einer List gestohlener Passwörter, die öffentlich verfügbar sind. Bitte versuche es erneut mit einem anderen Passwort und ziehe in Erwägung, auch anderswo deine Passwörter zu ändern. [mail] view_it_on=Auf %s ansehen reply=oder antworte direkt auf diese E-Mail link_not_working_do_paste=Link funktioniert nicht? Versuche ihn zu kopieren und im Browser einzufügen. hi_user_x=Hallo %s, - activate_account=Bitte aktiviere dein Konto activate_account.title=%s, bitte aktiviere dein Konto activate_account.text_1=Hallo %[1]s, danke für deine Registrierung bei %[2]s! activate_account.text_2=Bitte klicke innerhalb von %s auf folgenden Link, um dein Konto zu aktivieren: - activate_email=Bestätige deine E-Mail-Adresse activate_email.text=Bitte klicke innerhalb von %s auf folgenden Link, um dein Konto zu aktivieren: - register_notify=Willkommen bei Gitea register_notify.title=%[1]s, willkommen bei %[2]s register_notify.text_1=dies ist deine Bestätigungs-E-Mail für %s! register_notify.text_2=Du kannst dich jetzt mit dem Benutzernamen "%s" anmelden. register_notify.text_3=Wenn dieser Account von dir erstellt wurde, musst du zuerst dein Passwort setzen. - reset_password=Stelle dein Konto wieder her reset_password.title=%s, du hast um Wiederherstellung deines Kontos gebeten reset_password.text=Bitte klicke innerhalb von %s auf folgenden Link, um dein Konto wiederherzustellen: - register_success=Registrierung erfolgreich - issue_assigned.pull=@%[1]s hat dich im Repository %[3]s dem Pull Request %[2]s zugewiesen. issue_assigned.issue=@%[1]s hat dich im Repository %[3]s dem Issue %[2]s zugewiesen. - issue.x_mentioned_you=@%s hat dich erwähnt: issue.action.force_push=%[1]s hat %[3]s mit %[4]s auf %[2]s überschrieben. issue.action.push_1=@%[1]s hat einen Commit auf %[2]s gepusht @@ -458,7 +435,6 @@ issue.action.review_dismissed=@%[1]s hat das letzte Review von %[2]s für issue.action.ready_for_review=@%[1]s hat diesen Pull Request zum Review freigegeben. issue.action.new=@%[1]s hat #%[2]d geöffnet. issue.in_tree_path=In %s: - release.new.subject=Release %s in %s erschienen release.new.text=@%[1]s hat %[2]s in %[3]s released release.title=Titel: %s @@ -466,19 +442,17 @@ release.note=Anmerkung: release.downloads=Downloads: release.download.zip=Quellcode (ZIP Datei) release.download.targz=Quellcode (TAR.GZ Datei) - repo.transfer.subject_to=%s möchte "%s" an %s übertragen repo.transfer.subject_to_you=%s möchte dir "%s" übertragen repo.transfer.to_you=dir repo.transfer.body=Um es anzunehmen oder abzulehnen, öffne %s, oder ignoriere es einfach. - repo.collaborator.added.subject=%s hat dich zu %s hinzugefügt repo.collaborator.added.text=Du wurdest als Mitarbeiter für folgendes Repository hinzugefügt: - team_invite.subject=%[1]s hat dich eingeladen, der Organisation %[2]s beizutreten team_invite.text_1=%[1]s hat dich eingeladen, dem Team %[2]s in der Organisation %[3]s beizutreten. team_invite.text_2=Bitte klicke auf den folgenden Link, um dem Team beizutreten: team_invite.text_3=Hinweis: Diese Einladung war für %[1]s gedacht. Wenn du diese Einladung nicht erwartet hast, kannst du diese E-Mail ignorieren. +activate_email.title=%s, bitte verifiziere deine E-Mail-Adresse [modal] yes=Ja @@ -499,36 +473,32 @@ PayloadUrl=Payload-URL TeamName=Teamname AuthName=Name der Autorisierung AdminEmail=Administrator-E-Mail - NewBranchName=Neuer Branchname CommitSummary=Commit-Zusammenfassung CommitMessage=Commit-Nachricht CommitChoice=Commit-Auswahl TreeName=Dateipfad Content=Inhalt - SSPISeparatorReplacement=Trennzeichen SSPIDefaultLanguage=Standardsprache - -require_error=` darf nicht leer sein.` -alpha_dash_error=` sollte nur Buchstaben, Zahlen, Bindestriche („-“) und Unterstriche („_“) enthalten.` -alpha_dash_dot_error=` sollte nur Buchstaben, Zahlen, Bindestriche („-“), Unterstriche („_“) und Punkte („.“) enthalten.` -git_ref_name_error=` muss ein wohlgeformter Git-Referenzname sein.` -size_error=` muss die Größe %s haben.` -min_size_error=` muss mindestens %s Zeichen enthalten.` -max_size_error=` darf höchstens %s Zeichen enthalten.` -email_error=` ist keine gültige E-Mail-Adresse.` -url_error=`"%s" ist keine gültige URL.` -include_error=` muss den Text "%s" enthalten.` -glob_pattern_error=` Der Glob Pattern ist ungültig: %s.` -regex_pattern_error=` regex ist ungültig: %s.` -username_error=` darf nur alphanumerische Zeichen ('0-9','a-z','A-Z'), Bindestriche ('-'), Unterstriche ('_') und Punkte ('.') enthalten. Es kann nicht mit nicht-alphanumerischen Zeichen beginnen oder enden und aufeinanderfolgende nicht-alphanumerische Zeichen sind ebenfalls verboten.` -invalid_group_team_map_error=` Zuordnung ist ungültig: %s` +require_error=" darf nicht leer sein." +alpha_dash_error=" sollte nur Buchstaben, Zahlen, Bindestriche („-“) und Unterstriche („_“) enthalten." +alpha_dash_dot_error=" sollte nur Buchstaben, Zahlen, Bindestriche („-“), Unterstriche („_“) und Punkte („.“) enthalten." +git_ref_name_error=" muss ein wohlgeformter Git-Referenzname sein." +size_error=" muss die Größe %s haben." +min_size_error=" muss mindestens %s Zeichen enthalten." +max_size_error=" darf höchstens %s Zeichen enthalten." +email_error=" ist keine gültige E-Mail-Adresse." +url_error="%s" ist keine gültige URL. +include_error=" muss den Text "%s" enthalten." +glob_pattern_error=" Der Glob Pattern ist ungültig: %s." +regex_pattern_error=" regex ist ungültig: %s." +username_error=" darf nur alphanumerische Zeichen ('0-9','a-z','A-Z'), Bindestriche ('-'), Unterstriche ('_') und Punkte ('.') enthalten. Es kann nicht mit nicht-alphanumerischen Zeichen beginnen oder enden und aufeinanderfolgende nicht-alphanumerische Zeichen sind ebenfalls verboten." +invalid_group_team_map_error=" Zuordnung ist ungültig: %s" unknown_error=Unbekannter Fehler: captcha_incorrect=Der eingegebene CAPTCHA-Code ist falsch. password_not_match=Die Passwörter stimmen nicht überein. lang_select_error=Wähle eine Sprache aus der Liste aus. - username_been_taken=Der Benutzername ist bereits vergeben. username_change_not_local_user=Nicht-lokale Benutzer dürfen ihren Nutzernamen nicht ändern. username_has_not_been_changed=Benutzername wurde nicht geändert @@ -562,20 +532,17 @@ last_org_owner=Du kannst den letzten Benutzer nicht aus dem 'Besitzer'-Team entf cannot_add_org_to_team=Eine Organisation kann nicht als Teammitglied hinzugefügt werden. duplicate_invite_to_team=Der Benutzer wurde bereits als Teammitglied eingeladen. organization_leave_success=Du hast die Organisation %s erfolgreich verlassen. - invalid_ssh_key=Dein SSH-Key kann nicht überprüft werden: %s invalid_gpg_key=Dein GPG-Key kann nicht überprüft werden: %s invalid_ssh_principal=Ungültige Identität: %s must_use_public_key=Der von Dir bereitgestellte Key ist ein privater Key. Bitte lade Deinen privaten Key nirgendwo hoch. Verwende stattdessen Deinen öffentlichen Key. unable_verify_ssh_key=Der SSH-Key kann nicht verifiziert werden, überprüfe ihn auf Fehler. auth_failed=Authentifizierung fehlgeschlagen: %v - still_own_repo=Dein Konto besitzt ein oder mehrere Repositories. Diese müssen erst gelöscht oder übertragen werden. still_has_org=Dein Konto ist Mitglied einer oder mehrerer Organisationen, verlasse diese zuerst. still_own_packages=Dein Konto besitzt ein oder mehrere Pakete, lösche diese zuerst. org_still_own_repo=Diese Organisation besitzt noch ein oder mehrere Repositories. Diese müssen zuerst gelöscht oder übertragen werden. org_still_own_packages=Diese Organisation besitzt noch ein oder mehrere Pakete, lösche diese zuerst. - target_branch_not_exist=Der Ziel-Branch existiert nicht. [user] @@ -597,10 +564,10 @@ disabled_public_activity=Dieser Benutzer hat die öffentliche Sichtbarkeit der A email_visibility.limited=Ihre E-Mail-Adresse ist für alle authentifizierten Benutzer sichtbar email_visibility.private=Deine E-Mail-Adresse ist nur für Dich und Administratoren sichtbar settings=Benutzereinstellungen - form.name_reserved=Der Benutzername "%s" ist reserviert. form.name_pattern_not_allowed=Das Muster "%s" ist nicht in einem Benutzernamen erlaubt. form.name_chars_not_allowed=Benutzername "%s" enthält ungültige Zeichen. +show_on_map=Diesen Ort auf einer Karte anzeigen [settings] profile=Profil @@ -619,7 +586,6 @@ twofa=Zwei-Faktor-Authentifizierung account_link=Verknüpfte Benutzerkonten organization=Organisationen webauthn=Hardware-Sicherheitsschlüssel - public_profile=Öffentliches Profil password_username_disabled=Benutzer, die nicht von Gitea verwaltet werden können ihren Benutzernamen nicht ändern. Bitte kontaktiere deinen Administrator für mehr Details. full_name=Vollständiger Name @@ -657,7 +623,6 @@ comment_type_group_issue_ref=Issue-Referenz saved_successfully=Die Einstellungen wurden erfolgreich gespeichert. privacy=Datenschutz keep_activity_private_popup=Macht die Aktivität nur für dich und die Administratoren sichtbar - lookup_avatar_by_mail=Profilbild anhand der E-Mail-Addresse suchen federated_avatar_lookup=Suche nach föderierten Profilbildern enable_custom_avatar=Benutzerdefiniertes Profilbild benutzen @@ -667,7 +632,6 @@ delete_current_avatar=Aktuelles Profilbild löschen uploaded_avatar_not_a_image=Die hochgeladene Datei ist kein Bild. update_avatar_success=Dein Profilbild wurde geändert. update_user_avatar_success=Der Avatar des Benutzers wurde aktualisiert. - change_password=Passwort aktualisieren old_password=Aktuelles Passwort new_password=Neues Passwort @@ -675,7 +639,6 @@ retype_new_password=Neues Passwort bestätigen password_incorrect=Das aktuelle Passwort ist falsch. change_password_success=Dein Passwort wurde aktualisiert. Bitte verwende dieses beim nächsten Einloggen. password_change_disabled=Benutzer, die nicht von Gitea verwaltet werden, können ihr Passwort im Web-Interface nicht ändern. - emails=E-Mail-Adressen manage_emails=E-Mail-Adressen verwalten manage_themes=Standard-Theme auswählen @@ -706,7 +669,6 @@ email_preference_set_success=E-Mail-Einstellungen wurden erfolgreich aktualisier add_openid_success=Die neue OpenID-Adresse wurde hinzugefügt. keep_email_private=E-Mail-Adresse verbergen openid_desc=Mit OpenID kannst du dich über einen Drittanbieter authentifizieren. - manage_ssh_keys=SSH-Schlüssel verwalten manage_ssh_principals=SSH-Zertifikat's Identitäten verwalten manage_gpg_keys=GPG-Schlüssel verwalten @@ -784,7 +746,6 @@ ssh_signonly=SSH ist derzeit deaktiviert, sodass diese Schlüssel nur zur Commit ssh_externally_managed=Dieser SSH-Schlüssel wird extern für diesen Benutzer verwaltet manage_social=Verknüpfte soziale Konten verwalten unbind=Trennen - manage_access_token=Zugriffstokens verwalten generate_new_token=Neuen Token erzeugen tokens_desc=Diese Tokens gewähren vollen Zugriff auf dein Konto via die Gitea-API. @@ -806,7 +767,6 @@ permission_no_access=Kein Zugriff permission_read=Gelesen at_least_one_permission=Du musst mindestens eine Berechtigung auswählen, um ein Token zu erstellen permissions_list=Berechtigungen: - manage_oauth2_applications=OAuth2 Anwendungen verwalten edit_oauth2_application=OAuth2 Anwendung bearbeiten oauth2_applications_desc=OAuth2-Anwendungen ermöglichen die sichere Authentifizierung von Benutzern dieser Gitea-Instanz für deine Drittanwendung. @@ -825,12 +785,10 @@ oauth2_regenerate_secret=Secret neu generieren oauth2_regenerate_secret_hint=Secret verloren? oauth2_application_edit=Bearbeiten oauth2_application_create_description=OAuth2 Anwendungen geben deiner Drittanwendung Zugriff auf Benutzeraccounts dieser Gitea-Instanz. - authorized_oauth2_applications=Autorisierte OAuth2-Anwendungen revoke_key=Widerrufen revoke_oauth2_grant=Autorisierung widerrufen revoke_oauth2_grant_description=Wenn du die Autorisierung widerrufst, kann die Anwendung nicht mehr auf deine Daten zugreifen. Bist du dir sicher? - twofa_desc=Zwei-Faktor-Authentifizierung trägt zu einer höheren Accountsicherheit bei. twofa_is_enrolled=Für dein Konto ist die Zwei-Faktor-Authentifizierung eingeschaltet. twofa_not_enrolled=Für dein Konto ist die Zwei-Faktor-Authentifizierung momentan nicht eingeschaltet. @@ -847,13 +805,11 @@ then_enter_passcode=Und gebe dann die angezeigte PIN der Anwendung ein: passcode_invalid=Die PIN ist falsch. Probiere es erneut. twofa_enrolled=Die Zwei-Faktor-Authentifizierung wurde für dein Konto aktiviert. Bewahre dein Einmalpasswort (%s) an einem sicheren Ort auf, da es nicht wieder angezeigt werden wird. twofa_failed_get_secret=Fehler beim Abrufen des Secrets. - webauthn_desc=Sicherheitsschlüssel sind Geräte, die kryptografische Schlüssel beeinhalten. Diese können für die Zwei-Faktor-Authentifizierung verwendet werden. Der Sicherheitsschlüssel muss den Standard „WebAuthn“ unterstützen. webauthn_register_key=Sicherheitsschlüssel hinzufügen webauthn_nickname=Nickname webauthn_delete_key=Sicherheitsschlüssel entfernen webauthn_delete_key_desc=Wenn du einen Sicherheitsschlüssel entfernst, kannst du dich nicht mehr mit ihm anmelden. Fortfahren? - manage_account_links=Verknüpfte Accounts verwalten manage_account_links_desc=Diese externen Accounts sind mit deinem Gitea-Account verknüpft. account_links_not_available=Es sind keine externen Accounts mit diesem Gitea-Account verknüpft. @@ -861,28 +817,50 @@ link_account=Account verbinden remove_account_link=Verknüpften Account entfernen remove_account_link_desc=Wenn du den verknüpften Account entfernst, wirst du darüber nicht mehr auf deinen Gitea-Account zugreifen können. Fortfahren? remove_account_link_success=Der verknüpfte Account wurde entfernt. - - orgs_none=Du bist kein Mitglied in einer Organisation. - delete_account=Konto löschen delete_prompt=Wenn du fortfährst, wird dein Account permanent gelöscht. Dies KANN NICHT rückgängig gemacht werden. delete_with_all_comments=Dein Account existiert seit weniger als %s Tagen. Um Geisterkommentare zu vermeiden, werden alle deine Issue/PR-Kommentare gelöscht. confirm_delete_account=Löschen bestätigen delete_account_title=Benutzerkonto löschen delete_account_desc=Bist du sicher, dass du diesen Account dauerhaft löschen möchtest? - email_notifications.enable=E-Mail Benachrichtigungen aktivieren email_notifications.onmention=Nur E-Mail bei Erwähnung email_notifications.disable=E-Mail Benachrichtigungen deaktivieren email_notifications.submit=E-Mail-Einstellungen festlegen email_notifications.andyourown=Und deine Eigenen Benachrichtigungen - visibility=Nutzer Sichtbarkeit visibility.public=Öffentlich visibility.public_tooltip=Für alle sichtbar visibility.limited=Begrenzt visibility.private=Privat +uid=UID +biography_placeholder=Erzähle uns ein wenig über Dich selbst! (Du kannst Markdown verwenden) +location_placeholder=Teile Deinen ungefähren Standort mit anderen +profile_desc=Lege fest, wie dein Profil anderen Benutzern angezeigt wird. Deine primäre E-Mail-Adresse wird für Benachrichtigungen, Passwort-Wiederherstellung und webbasierte Git-Operationen verwendet. +change_username_prompt=Hinweis: Das Ändern deines Benutzernamen ändert auch deine Account-URL. +change_username_redirect_prompt=Der alte Benutzername wird auf den neuen Benutzernamen weiterleiten, bis er erneut als Benutzername verwendet wird. +keep_activity_private=Aktivität auf der Profilseite ausblenden +uploaded_avatar_is_too_big=Die hochgeladene Dateigröße (%d KiB) überschreitet die maximale Größe (%d KiB). +email_desc=Deine primäre E-Mail-Adresse wird für Benachrichtigungen, Passwort-Wiederherstellung und, sofern sie nicht versteckt ist, web-basierte Git-Operationen verwendet. +can_not_add_email_activations_pending=Es gibt eine ausstehende Aktivierung, versuche es in ein paar Minuten erneut, wenn du eine neue E-Mail hinzufügen möchtest. +keep_email_private_popup=Dies wird Deine E-Mail-Adresse nicht nur in Deinem Profil ausblenden, sondern auch, wenn Du einen Pull Request erstellst oder eine Datei über das Web-Interface bearbeitest. Gepushte Commits werden nicht geändert. +social_desc=Diese sozialen Konten können verwendet werden, um sich bei deinem Konto anzumelden. Stelle sicher, dass du sie alle zuordnen kannst. +unbind_success=Das soziale Konto wurde erfolgreich entfernt. +permission_write=Lesen und Schreiben +access_token_desc=Ausgewählte Token-Berechtigungen beschränken die Authentifizierung auf die entsprechenden API-Routen. Lies die Dokumentation für mehr Informationen. +create_oauth2_application_success=Du hast erfolgreich eine neue OAuth2-Anwendung erstellt. +update_oauth2_application_success=Du hast die OAuth2-Anwendung erfolgreich aktualisiert. +oauth2_client_secret_hint=Das Secret wird nach dem Verlassen oder Aktualisieren dieser Seite nicht mehr angezeigt. Bitte stelle sicher, dass du es gespeichert hast. +oauth2_application_remove_description=Das Entfernen einer OAuth2-Anwendung hat zur Folge, dass diese nicht mehr auf autorisierte Benutzeraccounts auf dieser Instanz zugreifen kann. Möchtest Du fortfahren? +oauth2_application_locked=Wenn es in der Konfiguration aktiviert ist, registriert Gitea einige OAuth2-Anwendungen beim Starten vor. Um unerwartetes Verhalten zu verhindern, können diese weder bearbeitet noch entfernt werden. Weitere Informationen findest Du in der OAuth2-Dokumentation. +authorized_oauth2_applications_description=Den folgenden Drittanbieter-Apps hast Du Zugriff auf Deinen persönlichen Gitea-Account gewährt. Bitte widerrufe die Autorisierung für Apps, die Du nicht mehr nutzt. +revoke_oauth2_grant_success=Zugriff erfolgreich widerrufen. +twofa_scratch_token_regenerated=Dein temporärer Token ist jetzt %s. Speichere ihn an einem sicheren Ort, er wird nie wieder angezeigt. +hooks.desc=Webhooks hinzufügen, die für alle Repositories, die dir gehören, ausgelöst werden. +repos_none=Du besitzt keine Repositories. +visibility.limited_tooltip=Nur für authentifizierte Benutzer sichtbar +visibility.private_tooltip=Sichtbar nur für Mitglieder von Organisationen, denen du beigetreten bist [repo] owner=Besitzer @@ -966,12 +944,10 @@ delete_preexisting_content=Dateien in %s löschen delete_preexisting_success=Nicht übernommene Dateien in %s gelöscht blame_prior=Blame vor dieser Änderung anzeigen author_search_tooltip=Zeigt maximal 30 Benutzer - transfer.accept=Übertragung Akzeptieren -transfer.accept_desc=`Übertragung nach "%s"` +transfer.accept_desc=Übertragung nach "%s" transfer.reject=Übertragung Ablehnen transfer.reject_desc=Übertragung nach "%s " abbrechen - desc.private=Privat desc.public=Öffentlich desc.private_template=Private Vorlage @@ -979,7 +955,6 @@ desc.public_template=Vorlage desc.internal=Intern desc.internal_template=Interne Vorlage desc.archived=Archiviert - template.items=Template-Elemente template.git_content=Git Inhalt (Standardbranch) template.git_hooks=Git-Hooks @@ -990,15 +965,12 @@ template.avatar=Profilbild template.issue_labels=Issue Label template.one_item=Es muss mindestens ein Template ausgewählt werden template.invalid=Es muss ein Template-Repository ausgewählt werden - archive.issue.nocomment=Dieses Repo ist archiviert. Du kannst Issues nicht kommentieren. archive.pull.nocomment=Dieses Repo ist archiviert. Du kannst Pull-Requests nicht kommentieren. - form.reach_limit_of_creation_1=Du hast bereits dein Limit von %d Repository erreicht. form.reach_limit_of_creation_n=Du hast bereits dein Limit von %d Repositories erreicht. form.name_reserved=Der Repository-Name "%s" ist reserviert. form.name_pattern_not_allowed=Das Muster "%s" ist in Repository-Namen nicht erlaubt. - need_auth=Authentifizierung migrate_options=Migrationsoptionen migrate_service=Migrationsdienst @@ -1050,7 +1022,6 @@ migrate.migrating_issues=Issues werden migriert migrate.migrating_pulls=Pull Requests werden migriert migrate.cancel_migrating_title=Migration abbrechen migrate.cancel_migrating_confirm=Möchtest du diese Migration abbrechen? - mirror_from=Mirror von forked_from=geforkt von generated_from=erzeugt von @@ -1065,7 +1036,6 @@ star=Favorisieren fork=Fork download_archive=Repository herunterladen more_operations=Weitere Operationen - no_desc=Keine Beschreibung quick_guide=Kurzanleitung clone_this_repo=Dieses Repository klonen @@ -1074,12 +1044,11 @@ create_new_repo_command=Erstelle ein neues Repository von der Kommandozeile aus push_exist_repo=Bestehendes Repository via Kommandozeile pushen empty_message=Dieses Repository hat keinen Inhalt. broken_message=Die Git-Daten, die diesem Repository zugrunde liegen, können nicht gelesen werden. Kontaktiere den Administrator deiner Instanz oder lösche dieses Repository. - code=Code code.desc=Zugriff auf Quellcode, Dateien, Commits und Branches. branch=Branch tree=Struktur -clear_ref=`Aktuelle Referenz löschen` +clear_ref=Aktuelle Referenz löschen filter_branch_and_tag=Branch oder Tag filtern find_tag=Tag finden branches=Branches @@ -1092,7 +1061,6 @@ actions=Actions labels=Label org_labels_desc=Labels der Organisationsebene, die mit allen Repositories in dieser Organisation verwendet werden können org_labels_desc_manage=verwalten - milestones=Meilensteine commits=Commits commit=Commit @@ -1109,10 +1077,9 @@ file_view_rendered=Ansicht rendern file_view_raw=Originalformat anzeigen file_permalink=Permalink file_too_large=Die Datei ist zu groß zum Anzeigen. -invisible_runes_line=`Diese Zeile enthält unsichtbare Unicode-Zeichen` -ambiguous_runes_line=`Diese Zeile enthält mehrdeutige Unicode-Zeichen` -ambiguous_character=`%[1]c [U+%04[1]X] kann mit %[2]c [U+%04[2]X] verwechselt werden` - +invisible_runes_line=Diese Zeile enthält unsichtbare Unicode-Zeichen +ambiguous_runes_line=Diese Zeile enthält mehrdeutige Unicode-Zeichen +ambiguous_character=%[1]c [U+%04[1]X] kann mit %[2]c [U+%04[2]X] verwechselt werden escape_control_characters=Escapen unescape_control_characters=Unescapen file_copy_permalink=Permalink kopieren @@ -1132,7 +1099,6 @@ normal_view=Normale Ansicht line=zeile lines=Zeilen from_comment=(Kommentar) - editor.add_file=Datei hinzufügen editor.new_file=Neue Datei editor.upload_file=Datei hochladen @@ -1175,7 +1141,7 @@ editor.filename_is_invalid=Ungültiger Dateiname: "%s". editor.branch_does_not_exist=Branch "%s" existiert nicht in diesem Repository. editor.branch_already_exists=Branch "%s" existiert bereits in diesem Repository. editor.directory_is_a_file=Der Verzeichnisname "%s" wird bereits als Dateiname in diesem Repository verwendet. -editor.file_is_a_symlink=`"%s" ist ein symbolischer Link. Symbolische Links können mit dem Web-Editor nicht bearbeitet werden` +editor.file_is_a_symlink="%s" ist ein symbolischer Link. Symbolische Links können mit dem Web-Editor nicht bearbeitet werden editor.filename_is_a_directory=Der Dateiname "%s" wird bereits als Verzeichnisname in diesem Repository verwendet. editor.file_editing_no_longer_exists=Die bearbeitete Datei "%s" existiert nicht mehr in diesem Repository. editor.file_deleting_no_longer_exists=Die zu löschende Datei "%s" existiert nicht mehr in diesem Repository. @@ -1199,7 +1165,6 @@ editor.user_no_push_to_branch=Benutzer kann nicht in die Branch pushen editor.require_signed_commit=Branch erfordert einen signierten Commit editor.cherry_pick=Cherry-Picke %s von: editor.revert=%s zurücksetzen auf: - commits.desc=Durchsuche die Quellcode-Änderungshistorie. commits.commits=Commits commits.no_commits=Keine gemeinsamen Commits. "%s" und "%s" haben vollständig unterschiedliche Historien. @@ -1218,7 +1183,6 @@ commits.signed_by_untrusted_user=Signiert von nicht vertrauenswürdigen Benutzer commits.signed_by_untrusted_user_unmatched=Signiert von nicht vertrauenswürdigen Benutzern, der nicht mit dem Committer übereinstimmt commits.gpg_key_id=GPG-Schlüssel-ID commits.ssh_key_fingerprint=SSH-Key-Fingerabdruck - commit.operations=Operationen commit.revert=Zurücksetzen commit.revert-header=Setze zurück: %s @@ -1226,15 +1190,12 @@ commit.revert-content=Branch auswählen, der zurückgesetzt werden soll: commit.cherry-pick=Cherry-Pick commit.cherry-pick-header=Cherry-Picke: %s commit.cherry-pick-content=Branch auswählen, auf dem Cherry-Picked werden soll: - commitstatus.error=Fehler commitstatus.failure=Fehler commitstatus.pending=Ausstehend commitstatus.success=Erfolg - ext_issues=Zugriff auf Externe Issues ext_issues.desc=Link zu externem Issuetracker. - projects=Projekte projects.desc=Verwalte Issues und Pull-Requests in Projektboards. projects.description=Beschreibung (optional) @@ -1275,7 +1236,6 @@ projects.column.assigned_to=Zugewiesen an projects.card_type.desc=Kartenvorschau projects.card_type.images_and_text=Bilder und Text projects.card_type.text_only=Nur Text - issues.desc=Verwalte Bug-Reports, Aufgaben und Meilensteine. issues.filter_assignees=Filter issues.filter_milestones=Meilenstein filtern @@ -1325,23 +1285,23 @@ issues.add_labels=hat die Labels %s %s hinzugefügt issues.remove_label=hat das Label %s %s entfernt issues.remove_labels=hat die Labels %s %s entfernt issues.add_remove_labels=hat %s hinzugefügt, und %s %s entfernt -issues.add_milestone_at=`hat diesen Issue %[2]s zum %[1]s Meilenstein hinzugefügt` -issues.add_project_at=`hat dieses zum %s projekt %s hinzugefügt` -issues.change_milestone_at=`hat den Meilenstein %[3]s von %[1]s zu %[2]s geändert` -issues.change_project_at=`hat das Projekt %[3]s von %[1]s zu %[2]s geändert` -issues.remove_milestone_at=`hat dieses Issue %[2]s vom %[1]s Meilenstein entfernt` -issues.remove_project_at=`hat dieses vom %s Projekt %s entfernt` -issues.deleted_milestone=`(gelöscht)` -issues.deleted_project=`(gelöscht)` -issues.self_assign_at=`hat sich das Issue %s selbst zugewiesen` -issues.add_assignee_at=`wurde von %s %s zugewiesen` -issues.remove_assignee_at=`wurde von %s %s nicht zugewiesen` -issues.remove_self_assignment=`hat seine Zuweisung %s entfernt` -issues.change_title_at=`hat den Titel von %s zu %s %s geändert` -issues.change_ref_at=`hat die Referenz von %s zu %s %s geändert` -issues.remove_ref_at=`hat die Referenz %s entfernt %s` -issues.add_ref_at=`hat die Referenz %s hinzugefügt %s` -issues.delete_branch_at=`löschte die Branch %s %s` +issues.add_milestone_at=hat diesen Issue %[2]s zum %[1]s Meilenstein hinzugefügt +issues.add_project_at=hat dieses zum %s projekt %s hinzugefügt +issues.change_milestone_at=hat den Meilenstein %[3]s von %[1]s zu %[2]s geändert +issues.change_project_at=hat das Projekt %[3]s von %[1]s zu %[2]s geändert +issues.remove_milestone_at=hat dieses Issue %[2]s vom %[1]s Meilenstein entfernt +issues.remove_project_at=hat dieses vom %s Projekt %s entfernt +issues.deleted_milestone=(gelöscht) +issues.deleted_project=(gelöscht) +issues.self_assign_at=hat sich das Issue %s selbst zugewiesen +issues.add_assignee_at=wurde von %s %s zugewiesen +issues.remove_assignee_at=wurde von %s %s nicht zugewiesen +issues.remove_self_assignment=hat seine Zuweisung %s entfernt +issues.change_title_at=hat den Titel von %s zu %s %s geändert +issues.change_ref_at=hat die Referenz von %s zu %s %s geändert +issues.remove_ref_at=hat die Referenz %s entfernt %s +issues.add_ref_at=hat die Referenz %s hinzugefügt %s +issues.delete_branch_at=löschte die Branch %s %s issues.filter_label=Label issues.filter_label_exclude=„Alt + Klick/Enter verwenden, um Label auszuschließen” issues.filter_label_no_select=Alle Label @@ -1401,7 +1361,7 @@ issues.closed_title=Geschlossen issues.draft_title=Entwurf issues.num_comments_1=%d Kommentar issues.num_comments=%d Kommentare -issues.commented_at=`hat %s kommentiert` +issues.commented_at=hat %s kommentiert issues.delete_comment_confirm=Bist du sicher dass du diesen Kommentar löschen möchtest? issues.context.copy_link=Link kopieren issues.context.quote_reply=Antwort zitieren @@ -1416,16 +1376,16 @@ issues.close_comment_issue=Kommentieren und schließen issues.reopen_issue=Wieder öffnen issues.reopen_comment_issue=Kommentieren und wieder öffnen issues.create_comment=Kommentieren -issues.closed_at=`hat diesen Issue %[2]s geschlossen` -issues.reopened_at=`hat diesen Issue %[2]s wieder geöffnet` -issues.commit_ref_at=`hat dieses Issue %[2]s aus einem Commit referenziert` -issues.ref_issue_from=`hat %[2]s auf dieses Issue verwiesen %[4]s` -issues.ref_pull_from=`hat %[2]s auf diesen Pull Request verwiesen %[4]s` -issues.ref_closing_from=`hat %[2]s auf einen Pull Request %[4]s verwiesen, welcher das Issue schließen wird` -issues.ref_reopening_from=`hat auf einen Pull Request %[4]s verwiesen, welcher das Issue %[2]s erneut öffnen wird` -issues.ref_closed_from=`hat dieses Issue %[4]s geschlossen %[2]s` -issues.ref_reopened_from=`hat dieses Issue %[4]s %[2]s wieder geöffnet` -issues.ref_from=`von %[1]s` +issues.closed_at=hat diesen Issue %[2]s geschlossen +issues.reopened_at=hat diesen Issue %[2]s wieder geöffnet +issues.commit_ref_at=hat dieses Issue %[2]s aus einem Commit referenziert +issues.ref_issue_from=hat %[2]s auf dieses Issue verwiesen %[4]s +issues.ref_pull_from=hat %[2]s auf diesen Pull Request verwiesen %[4]s +issues.ref_closing_from=hat %[2]s auf einen Pull Request %[4]s verwiesen, welcher das Issue schließen wird +issues.ref_reopening_from=hat auf einen Pull Request %[4]s verwiesen, welcher das Issue %[2]s erneut öffnen wird +issues.ref_closed_from=hat dieses Issue %[4]s geschlossen %[2]s +issues.ref_reopened_from=hat dieses Issue %[4]s %[2]s wieder geöffnet +issues.ref_from=von %[1]s issues.author=Autor issues.role.owner=Besitzer issues.role.member=Mitglied @@ -1458,8 +1418,8 @@ issues.label.filter_sort.reverse_alphabetically=Umgekehrt alphabetisch issues.label.filter_sort.by_size=Kleinste Größe issues.label.filter_sort.reverse_by_size=Größte Größe issues.num_participants=%d Beteiligte -issues.attachment.open_tab=`Klicken, um „%s“ in einem neuen Tab zu öffnen` -issues.attachment.download=`Klicken, um „%s“ herunterzuladen` +issues.attachment.open_tab=Klicken, um „%s“ in einem neuen Tab zu öffnen +issues.attachment.download=Klicken, um „%s“ herunterzuladen issues.subscribe=Abonnieren issues.unsubscribe=Abbestellen issues.unpin_issue=Issue abheften @@ -1493,7 +1453,7 @@ issues.start_tracking_short=Zeiterfassung starten issues.start_tracking=Zeiterfassung starten issues.start_tracking_history=hat die Zeiterfassung %s gestartet issues.tracker_auto_close=Der Timer wird automatisch gestoppt, wenn dieser Issue geschlossen wird -issues.tracking_already_started=`Du hast die Zeiterfassung bereits in diesem Issue gestartet!` +issues.tracking_already_started=Du hast die Zeiterfassung bereits in diesem Issue gestartet! issues.stop_tracking=Zeiterfassung stoppen issues.stop_tracking_history=hat die Zeiterfassung %s angehalten issues.cancel_tracking=Verwerfen @@ -1501,20 +1461,20 @@ issues.add_time=Zeit manuell hinzufügen issues.del_time=Diese Zeiterfassung löschen issues.add_time_short=Zeit hinzufügen issues.add_time_cancel=Abbrechen -issues.add_time_history=`hat %s gearbeitete Zeit hinzugefügt` -issues.del_time_history=`hat %s gearbeitete Zeit gelöscht` +issues.add_time_history=hat %s gearbeitete Zeit hinzugefügt +issues.del_time_history=hat %s gearbeitete Zeit gelöscht issues.add_time_hours=Stunden issues.add_time_minutes=Minuten issues.add_time_sum_to_small=Es wurde keine Zeit eingegeben. issues.time_spent_total=Zeitaufwand insgesamt -issues.time_spent_from_all_authors=`Aufgewendete Zeit: %s` +issues.time_spent_from_all_authors=Aufgewendete Zeit: %s issues.due_date=Fällig am issues.invalid_due_date_format=Das Fälligkeitsdatum muss das Format „JJJJ-MM-TT“ haben. issues.error_modifying_due_date=Fehler beim Ändern des Fälligkeitsdatums. issues.error_removing_due_date=Fehler beim Entfernen des Fälligkeitsdatums. issues.push_commit_1=hat %d Commit %s hinzugefügt issues.push_commits_n=hat %d Commits %s hinzugefügt -issues.force_push_codes=`hat %[6]s %[1]s von %[2]s zu %[4]s force-gepusht` +issues.force_push_codes=hat %[6]s %[1]s von %[2]s zu %[4]s force-gepusht issues.force_push_compare=Vergleichen issues.due_date_form=JJJJ-MM-TT issues.due_date_form_add=Fälligkeitsdatum hinzufügen @@ -1536,8 +1496,8 @@ issues.dependency.add=Abhängigkeit hinzufügen… issues.dependency.cancel=Abbrechen issues.dependency.remove=Entfernen issues.dependency.remove_info=Abhängigkeit löschen -issues.dependency.added_dependency=`hat eine neue Abhängigkeit %s hinzugefügt` -issues.dependency.removed_dependency=`hat eine Abhängigkeit %s entfernt` +issues.dependency.added_dependency=hat eine neue Abhängigkeit %s hinzugefügt +issues.dependency.removed_dependency=hat eine Abhängigkeit %s entfernt issues.dependency.pr_closing_blockedby=Das Schließen dieses Pull-Requests wird von den folgenden Issues blockiert issues.dependency.issue_closing_blockedby=Das Schließen dieses Issues wird von den folgenden Issues blockiert issues.dependency.issue_close_blocks=Dieses Issue blockiert die Schließung der folgenden Issues @@ -1593,10 +1553,8 @@ issues.content_history.delete_from_history=Aus dem Verlauf entfernen issues.content_history.delete_from_history_confirm=Aus dem Verlauf löschen? issues.content_history.options=Optionen issues.reference_link=Referenz: %s - compare.compare_base=Basis compare.compare_head=vergleichen - pulls.desc=Pull-Requests und Code-Reviews aktivieren. pulls.new=Neuer Pull-Request pulls.view=Pull-Request ansehen @@ -1618,11 +1576,11 @@ pulls.filter_branch=Branch filtern pulls.no_results=Keine Ergebnisse verfügbar. pulls.nothing_to_compare=Diese Branches sind identisch. Es muss kein Pull-Request erstellt werden. pulls.nothing_to_compare_and_allow_empty_pr=Diese Branches sind gleich. Der Pull-Request wird leer sein. -pulls.has_pull_request=`Es existiert bereits ein Pull-Request zwischen diesen beiden Branches: %[2]s#%[3]d` +pulls.has_pull_request=Es existiert bereits ein Pull-Request zwischen diesen beiden Branches: %[2]s#%[3]d pulls.create=Pull-Request erstellen pulls.title_desc=möchte %[1]d Commits von %[2]s nach %[3]s mergen pulls.merged_title_desc=hat %[1]d Commits von %[2]s nach %[3]s %[4]s zusammengeführt -pulls.change_target_branch_at=`hat den Zielbranch von %s nach %s %s geändert` +pulls.change_target_branch_at=hat den Zielbranch von %s nach %s %s geändert pulls.tab_conversation=Diskussion pulls.tab_commits=Commits pulls.tab_files=Geänderte Dateien @@ -1634,7 +1592,7 @@ pulls.closed=Pull-Request geschlossen pulls.manually_merged=Manuell gemergt pulls.merged_info_text=Der Branch %s kann jetzt gelöscht werden. pulls.is_closed=Der Pull-Request wurde geschlossen. -pulls.title_wip_desc=`Beginne den Titel mit %s um zu verhindern, dass der Pull Request versehentlich gemergt wird.` +pulls.title_wip_desc=Beginne den Titel mit %s um zu verhindern, dass der Pull Request versehentlich gemergt wird. pulls.cannot_merge_work_in_progress=Dieser Pull Request ist als Work in Progress markiert. pulls.still_in_progress=Noch in Bearbeitung? pulls.add_prefix=%s Präfix hinzufügen @@ -1659,7 +1617,6 @@ pulls.reject_count_n=%d Änderungsanfragen pulls.waiting_count_1=%d wartendes Review pulls.waiting_count_n=%d wartende Reviews pulls.wrong_commit_id=die Commit ID muss eine Commit ID auf dem Zielbranch sein - pulls.no_merge_desc=Dieser Pull-Request kann nicht gemerged werden, da keine Mergeoptionen aktiviert sind. pulls.no_merge_helper=Aktiviere Mergeoptionen in den Repositoryeinstellungen oder merge den Pull-Request manuell. pulls.no_merge_wip=Dieser Pull Request kann nicht gemergt werden, da er als Work In Progress gekennzeichnet ist. @@ -1672,7 +1629,6 @@ pulls.squash_merge_pull_request=Squash Commit erstellen pulls.merge_manually=Manuell mergen pulls.merge_commit_id=Der Mergecommit ID pulls.require_signed_wont_sign=Der Branch erfordert einen signierten Commit, aber dieser Merge wird nicht signiert - pulls.invalid_merge_option=Du kannst diese Mergeoption auf diesen Pull-Request nicht anwenden. pulls.merge_conflict=Merge fehlgeschlagen: Beim Mergen gab es einen Konflikt. Hinweis: Probiere eine andere Strategie pulls.merge_conflict_summary=Fehlermeldung @@ -1684,7 +1640,7 @@ pulls.head_out_of_date=Mergen fehlgeschlagen: Der Head wurde aktualisiert währe pulls.push_rejected=Mergen fehlgeschlagen: Der Push wurde abgelehnt. Überprüfe die Git Hooks für dieses Repository. pulls.push_rejected_summary=Vollständige Ablehnungsmeldung pulls.push_rejected_no_message=Mergen fehlgeschlagen: Der Push wurde abgelehnt, aber es gab keine Fehlermeldung.
Überprüfe die Git Hooks für dieses Repository -pulls.open_unmerged_pull_exists=`Du kannst diesen Pull-Request nicht erneut öffnen, da noch ein anderer (#%d) mit identischen Eigenschaften offen ist.` +pulls.open_unmerged_pull_exists=Du kannst diesen Pull-Request nicht erneut öffnen, da noch ein anderer (#%d) mit identischen Eigenschaften offen ist. pulls.status_checking=Einige Prüfungen sind noch ausstehend pulls.status_checks_success=Alle Prüfungen waren erfolgreich pulls.status_checks_warning=Einige Prüfungen meldeten Warnungen @@ -1698,32 +1654,25 @@ pulls.update_branch_success=Branch-Aktualisierung erfolgreich pulls.update_not_allowed=Du hast keine Berechtigung, die Branch zu Updaten pulls.outdated_with_base_branch=Dieser Branch enthält nicht die neusten Commits der Basis-Branch pulls.close=Pull-Request schließen -pulls.closed_at=`hat diesen Pull-Request %[2]s geschlossen` -pulls.reopened_at=`hat diesen Pull-Request %[2]s wieder geöffnet` -pulls.merge_instruction_hint=`Siehe auch die Anleitung für die Kommandozeile.` +pulls.closed_at=hat diesen Pull-Request %[2]s geschlossen +pulls.reopened_at=hat diesen Pull-Request %[2]s wieder geöffnet +pulls.merge_instruction_hint=Siehe auch die Anleitung für die Kommandozeile. pulls.merge_instruction_step1_desc=Wechsle auf einen neuen Branch in deinem lokalen Repository und teste die Änderungen. pulls.merge_instruction_step2_desc=Führe die Änderungen zusammen und aktualisiere den Stand online auf Gitea. pulls.clear_merge_message=Merge-Nachricht löschen pulls.clear_merge_message_hint=Das Löschen der Merge-Nachricht wird nur den Inhalt der Commit-Nachricht entfernen und generierte Git-Trailer wie "Co-Authored-By …" erhalten. - pulls.auto_merge_button_when_succeed=(Wenn die Checks erfolgreich sind) pulls.auto_merge_when_succeed=Automergen, sobald alle Checks erfüllt sind pulls.auto_merge_newly_scheduled=Der Pull-Request wird automatisch gemergt, wenn alle Checks erfolgreich sind. pulls.auto_merge_has_pending_schedule=%[1]s hat einen Automerge für diesen Pull-Request %[2]s geplant. - pulls.auto_merge_cancel_schedule=Automerge abbrechen pulls.auto_merge_not_scheduled=Dieser Pull Request hat keinen geplanten Automerge. pulls.auto_merge_canceled_schedule=Der Automerge dieses Pull-Requests wurde abgebrochen. - -pulls.auto_merge_newly_scheduled_comment=`hat einen Automerge für diesen Pull-Request %[1]s geplant` -pulls.auto_merge_canceled_schedule_comment=`hat den Automerge für diesen Pull-Request %[1]s abgebrochen` - +pulls.auto_merge_newly_scheduled_comment=hat einen Automerge für diesen Pull-Request %[1]s geplant +pulls.auto_merge_canceled_schedule_comment=hat den Automerge für diesen Pull-Request %[1]s abgebrochen pulls.delete.title=Diesen Pull-Request löschen? pulls.delete.text=Willst du diesen Pull-Request wirklich löschen? (Dies wird den Inhalt unwiderruflich löschen. Überlege, ob du ihn nicht lieber schließen willst, um ihn zu archivieren) - - pull.deleted_branch=(gelöscht):%s - milestones.new=Neuer Meilenstein milestones.closed=Geschlossen %s milestones.update_ago=%s aktualisiert @@ -1750,11 +1699,8 @@ milestones.filter_sort.least_complete=Am wenigsten vollständig milestones.filter_sort.most_complete=Vollständigste milestones.filter_sort.most_issues=Meiste Issues milestones.filter_sort.least_issues=Wenigste Issues - - ext_wiki=Zugriff auf externes Wiki ext_wiki.desc=Verweis auf externes Wiki. - wiki=Wiki wiki.welcome=Willkommen im Wiki. wiki.welcome_desc=Im Wiki kannst du Dokumentation schreiben und sie mit Mitarbeitern teilen. @@ -1781,7 +1727,6 @@ wiki.pages=Seiten wiki.last_updated=Zuletzt aktualisiert %s wiki.page_name_desc=Gib einen Namen für diese Wiki-Seite ein. Spezielle Namen sind: 'Home', '_Sidebar' und '_Footer'. wiki.original_git_entry_tooltip=Originale Git-Datei anstatt eines benutzerfreundlichen Links anzeigen. - activity=Aktivität activity.period.filter_label=Zeitraum: activity.period.daily=1 Tag @@ -1847,7 +1792,6 @@ activity.git_stats_addition_n=%d Einfügungen activity.git_stats_and_deletions=und activity.git_stats_deletion_1=%d Löschung activity.git_stats_deletion_n=%d Löschungen - search=Suchen search.search_repo=Repository durchsuchen search.type.tooltip=Suchmodus @@ -1858,7 +1802,6 @@ search.match.tooltip=Zeige nur Ergebnisse, die exakt mit dem Suchbegriff überei search.results=Suchergebnisse für „%s“ in %s search.code_no_results=Es konnte kein passender Code für deinen Suchbegriff gefunden werden. search.code_search_unavailable=Derzeit ist die Code-Suche nicht verfügbar. Bitte wende dich an den Website-Administrator. - settings=Einstellungen settings.desc=In den Einstellungen kannst du die Einstellungen des Repositories anpassen settings.options=Repository @@ -1890,7 +1833,6 @@ settings.mirror_settings.last_update=Letzte Aktualisierung settings.mirror_settings.push_mirror.none=Keine Push-Mirrors konfiguriert settings.mirror_settings.push_mirror.remote_url=URL zum Git-Remote-Repository settings.mirror_settings.push_mirror.add=Push-Mirror hinzufügen - settings.sync_mirror=Jetzt synchronisieren settings.mirror_sync_in_progress=Mirror-Synchronisierung wird zurzeit ausgeführt. Komm in ein paar Minuten zurück. settings.site=Webseite @@ -1967,7 +1909,7 @@ settings.transfer_notices_2=– Du wirst weiterhin Zugriff haben, wenn der neue settings.transfer_notices_3=- Wenn das Repository privat ist und an einen einzelnen Benutzer übertragen wird, wird sichergestellt, dass der Benutzer mindestens Leserechte hat (und die Berechtigungen werden gegebenenfalls ändert). settings.transfer_owner=Neuer Besitzer settings.transfer_perform=Übertragung durchführen -settings.transfer_started=`Für dieses Repository wurde eine Übertragung eingeleitet und wartet nun auf die Bestätigung von "%s"` +settings.transfer_started=Für dieses Repository wurde eine Übertragung eingeleitet und wartet nun auf die Bestätigung von "%s" settings.transfer_succeed=Das Repository wurde transferiert. settings.signing_settings=Signaturüberprüfungseinstellungen settings.trust_model=Signaturvertrauensmodell @@ -2270,7 +2212,6 @@ settings.rename_branch_success=Branch %s wurde erfolgreich in %s umbenannt. settings.rename_branch_from=alter Branchname settings.rename_branch_to=neuer Branchname settings.rename_branch=Branch umbenennen - diff.browse_source=Quellcode durchsuchen diff.parent=Ursprung diff.commit=Commit @@ -2326,7 +2267,6 @@ diff.image.overlay=Überlagert diff.has_escaped=Diese Zeile enthält versteckte Unicode-Zeichen diff.show_file_tree=Dateibaum anzeigen diff.hide_file_tree=Dateibaum ausblenden - releases.desc=Behalte den Überblick über Versionen und Downloads. release.releases=Releases release.detail=Release-Details @@ -2373,9 +2313,8 @@ release.add_tag_msg=Titel und Beschreibung des Releases als Tag Nachricht verwen release.add_tag=Nur Tag erstellen release.releases_for=Releases für %s release.tags_for=Tags für %s - branch.name=Branchname -branch.search=Branches durchsuchen +branch.search=Branch suchen branch.already_exists=Ein Branch mit dem Namen "%s" existiert bereits. branch.delete_head=Löschen branch.delete=Branch "%s" löschen @@ -2384,7 +2323,7 @@ branch.deletion_success=Branch "%s" wurde gelöscht. branch.deletion_failed=Branch "%s" konnte nicht gelöscht werden. branch.delete_branch_has_new_commits=Der Branch "%s" kann nicht gelöscht werden, da seit dem letzten Merge neue Commits hinzugefügt wurden. branch.create_branch=Erstelle Branch %s -branch.create_from=`von "%s"` +branch.create_from=von "%s" branch.create_success=Branch "%s" wurde erstellt. branch.branch_already_exists=Branch "%s" existiert bereits in diesem Repository. branch.branch_name_conflict=Der Branch-Name "%s" steht in Konflikt mit dem bestehenden Branch "%s". @@ -2408,24 +2347,109 @@ branch.create_branch_operation=Branch erstellen branch.new_branch=Neue Branch erstellen branch.new_branch_from=Neuen Branch von "%s" erstellen branch.renamed=Branch %s wurde in %s umbenannt. - tag.create_tag=Tag %s erstellen tag.create_tag_operation=Tag erstellen tag.confirm_create_tag=Tag erstellen tag.create_tag_from=Neuen Tag von "%s" erstellen - tag.create_success=Tag "%s" wurde erstellt. - topic.manage_topics=Themen verwalten topic.done=Fertig topic.count_prompt=Du kannst nicht mehr als 25 Themen auswählen - find_file.go_to_file=Datei suchen find_file.no_matching=Keine passende Datei gefunden - error.csv.too_large=Diese Datei kann nicht gerendert werden, da sie zu groß ist. error.csv.unexpected=Diese Datei kann nicht gerendert werden, da sie ein unerwartetes Zeichen in Zeile %d und Spalte %d enthält. error.csv.invalid_field_count=Diese Datei kann nicht gerendert werden, da sie eine falsche Anzahl an Feldern in Zeile %d hat. +new_repo_helper=Ein Repository enthält alle Projektdateien, einschließlich des Änderungsverlaufs. Schon woanders vorhanden? Migriere das Repository. +visibility_helper=In privates Repository umwandeln +fork_no_valid_owners=Dieses Repository kann nicht geforkt werden, da keine gültigen Besitzer vorhanden sind. +default_branch_label=Standard +mirror_address_url_invalid=Die angegebene URL ist ungültig. Achte darauf, alle URL-Komponenten korrekt zu maskieren. +mirror_address_protocol_invalid=Die angegebene URL ist ungültig. Nur URLs beginnend mit http(s):// oder git:// sind möglich. +blame.ignore_revs=Revisionen in .git-blame-ignore-revs werden ignoriert. Klicke hier, um das zu umgehen und die normale Blame-Ansicht zu sehen. +blame.ignore_revs.failed=Fehler beim Ignorieren der Revisionen in .git-blame-ignore-revs. +tree_path_not_found_commit=Pfad %[1]s existiert nicht in Commit%[2]s +tree_path_not_found_branch=Pfad %[1]s existiert nicht in Branch %[2]s +tree_path_not_found_tag=Pfad %[1]s existiert nicht in Tag %[2]s +transfer.no_permission_to_accept=Du hast keine Berechtigung, diesen Transfer anzunehmen. +transfer.no_permission_to_reject=Du hast keine Berechtigung, diesen Transfer abzulehnen. +archive.title=Dieses Repository ist archiviert. Du kannst Dateien ansehen und es klonen, kannst aber nicht pushen oder Issues/Pull-Requests öffnen. +archive.title_date=Dieses Repository wurde am %s archiviert. Du kannst Dateien ansehen und es klonen, aber nicht pushen oder Issues/Pull-Requests öffnen. +migrate_options_lfs_endpoint.placeholder=Wenn leer gelassen, wird der Endpunkt von der Clone-URL abgeleitet +invisible_runes_header=Diese Datei enthält unsichtbare Unicode-Zeichen +invisible_runes_description=Diese Datei enthält unsichtbare Unicode-Zeichen, die für Menschen nicht unterscheidbar sind, aber von einem Computer unterschiedlich verarbeitet werden können. Wenn du glaubst, dass das absichtlich so ist, kannst du diese Warnung ignorieren. Benutze den „Escape“-Button, um versteckte Zeichen anzuzeigen. +ambiguous_runes_header=Diese Datei enthält mehrdeutige Unicode-Zeichen +ambiguous_runes_description=Diese Datei enthält Unicode-Zeichen, die mit anderen Zeichen verwechselt werden können. Wenn du glaubst, dass das absichtlich so ist, kannst du diese Warnung ignorieren. Benutze den „Escape“-Button, um versteckte Zeichen anzuzeigen. +executable_file=Ausführbare Datei +commit.contained_in=Dieser Commit ist enthalten in: +commit.contained_in_default_branch=Dieser Commit ist im Standard-Branch enthalten +commit.load_referencing_branches_and_tags=Lade Branches und Tags, die diesen Commit referenzieren +issues.keyword_search_unavailable=Zurzeit ist die Stichwort-Suche nicht verfügbar. Bitte wende dich an den Website-Administrator. +issues.author_helper=Dieser Benutzer ist der Autor. +issues.role.owner_helper=Dieser Benutzer ist der Besitzer dieses Repositorys. +issues.role.member_helper=Dieser Benutzer ist Mitglied der Organisation, die dieses Repository besitzt. +issues.role.collaborator=Mitarbeiter +issues.role.collaborator_helper=Dieser Benutzer wurde zur Zusammenarbeit am Repository eingeladen. +issues.role.first_time_contributor=Erstmaliger Mitwirkender +issues.role.first_time_contributor_helper=Dies ist der erste Beitrag dieses Benutzers zum Repository. +issues.role.contributor=Mitwirkender +issues.role.contributor_helper=Dieser Benutzer hat schon zuvor zu dem Repository beigetragen. +issues.label_archive=Label archivieren +issues.label_archived_filter=Archivierte Labels anzeigen +issues.label_archive_tooltip=Archivierte Labels werden bei der Suche nach Label standardmäßig von den Vorschlägen ausgeschlossen. +issues.cancel_tracking_history=hat die Zeiterfassung %s abgebrochen +issues.due_date_not_writer=Du musst Schreibrechte für dieses Repository haben, um das Fälligkeitsdatum zu ändern. +issues.review.outdated_description=Der Inhalt hat sich geändert, seit dieser Kommentar abgegeben wurde +pulls.show_all_commits=Alle Commits anzeigen +pulls.show_changes_since_your_last_review=Zeige Änderungen seit deinem letzten Review +pulls.showing_only_single_commit=Nur Änderungen aus Commit %[1]s werden angezeigt +pulls.showing_specified_commit_range=Zeige nur die Änderungen zwischen %[1]s..%[2]s +pulls.select_commit_hold_shift_for_range=Commit auswählen. Halte Shift + klicke, um eine Reihe auszuwählen +pulls.review_only_possible_for_full_diff=Ein Review ist nur möglich, wenn das vollständige Diff angezeigt wird +pulls.filter_changes_by_commit=Nach Commit filtern +pulls.blocked_by_approvals=Dieser Pull-Request hat noch nicht genügend Zustimmungen. %d von %d Zustimmungen erteilt. +pulls.blocked_by_rejection=Dieser Pull-Request hat Änderungen, die von einem offiziellen Reviewer angefragt wurden. +pulls.blocked_by_official_review_requests=Dieser Pull Request hat offizielle Review-Anfragen. +pulls.blocked_by_outdated_branch=Dieser Pull Request ist blockiert, da er veraltet ist. +pulls.blocked_by_changed_protected_files_1=Dieser Pull Request ist blockiert, weil er eine geschützte Datei ändert: +pulls.blocked_by_changed_protected_files_n=Dieser Pull Request ist blockiert, weil er geschützte Dateien ändert: +pulls.has_merged=Fehler: Der Pull-Request wurde gemerged, du kannst den Zielbranch nicht wieder mergen oder ändern. +pulls.recently_pushed_new_branches=Du hast auf den Branch %[1]s %[2]s gepusht +milestones.new_subheader=Benutze Meilensteine, um Issues zu organisieren und den Fortschritt darzustellen. +milestones.filter_sort.earliest_due_data=Frühestes Fälligkeitsdatum +milestones.filter_sort.latest_due_date=Spätestes Fälligkeitsdatum +signing.will_sign=Dieser Commit wird mit dem Key "%s" signiert werden. +signing.wont_sign.error=Es gab einen Fehler bei der Prüfung, ob der Commit signiert werden kann. +signing.wont_sign.nokey=Es ist kein Schlüssel zum Signieren dieses Commits verfügbar. +signing.wont_sign.never=Commits werden nie signiert. +signing.wont_sign.always=Commits werden immer signiert. +signing.wont_sign.pubkey=Der Commit wird nicht signiert, da du keinen öffentlichen Schlüssel mit deinem Account verknüpft hast. +signing.wont_sign.twofa=Du musst Zwei-Faktor-Authentifizierung aktivieren, damit Commits signiert werden. +signing.wont_sign.parentsigned=Der Commit wird nicht signiert werden, da der vorherige Commit nicht signiert ist. +signing.wont_sign.basesigned=Der Merge Commit wird nicht signiert werden, da der Basis-Commit nicht signiert ist. +signing.wont_sign.headsigned=Der Merge Commit wird nicht signiert werden, da der Head-Commit nicht signiert ist. +signing.wont_sign.commitssigned=Der Merge Commit wird nicht signiert werden, da alle zugehörigen Commits nicht signiert sind. +signing.wont_sign.approved=Der Merge Commit wird nicht signiert werden, da der Pull Request nicht genehmigt wurde. +signing.wont_sign.not_signed_in=Du bist nicht eingeloggt. +settings.mirror_settings.docs.more_information_if_disabled=Hier kannst du mehr über Push- und Pull-Mirrors erfahren: +settings.mirror_settings.docs.doc_link_pull_section=den Abschnitt "Von einem entfernten Repository pullen" in der Dokumentation. +settings.mirror_settings.push_mirror.edit_sync_time=Mirror-Sync-Intervall anpassen +settings.transfer_abort_success=Die Repository-Übertragung zu %s wurde abgebrochen. +settings.webhook.test_delivery_desc_disabled=Um diesen Webhook mit einem Fake-Event zu testen, aktiviere ihn. +settings.webhook.replay.description_disabled=Um diesen Webhook wiederzugeben, aktiviere ihn. +settings.protect_branch_name_pattern_desc=Geschützte Branch-Namensmuster. Lies die Dokumentation für die Muster-Syntax. Beispiele: main, release/** +settings.tags.protection.pattern.description=Du kannst einen einzigen Namen oder ein globales Schema oder einen regulären Ausdruck verwenden, um mehrere Tags zu schützen. Mehr dazu im Guide für geschützte Tags (Englisch). +settings.thread_id=Thread-ID +settings.archive.text=Durch das Archivieren wird ein Repo vollständig schreibgeschützt. Es wird vom Dashboard versteckt. Niemand (nicht einmal du!) wird in der Lage sein, neue Commits zu erstellen oder Issues oder Pull-Requests zu öffnen. +settings.unarchive.button=Archivieren rückgängig machen +settings.unarchive.header=Archivieren dieses Repositories rückgängig machen +settings.unarchive.text=Durch das Aufheben der Archivierung kann das Repo wieder Commits und Pushes sowie neue Issues und Pull-Requests empfangen. +settings.unarchive.success=Die Archivierung des Repos wurde erfolgreich wieder rückgängig gemacht. +settings.unarchive.error=Beim Versuch, die Archivierung des Repos aufzuheben, ist ein Fehler aufgetreten. Weitere Details finden sich im Log. +diff.comment.add_line_comment=Einzelnen Kommentar hinzufügen +release.deletion_desc=Beim Entfernen wird ein Release nur von Gitea gelöscht. Es betrifft weder den Git-Tag, noch den Inhalt des Repos oder seinen Änderungsverlauf. Fortfahren? +branch.delete_desc=Das Löschen eines Branches ist permanent. Obwohl der Branch für eine kurze Zeit weiter existieren könnte, kann diese Aktion in den meisten Fällen NICHT rückgängig gemacht werden. Fortfahren? +topic.format_prompt=Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) und Punkte ('.') enthalten und bis zu 35 Zeichen lang sein. Nur Kleinbuchstaben sind zulässig. [org] org_name_holder=Name der Organisation @@ -2449,11 +2473,9 @@ team_access_desc=Zugriff auf das Repository team_permission_desc=Berechtigungen team_unit_desc=Zugriff auf Repositorybereiche erlauben team_unit_disabled=(Deaktiviert) - form.name_reserved=Der Organisationsname "%s" ist reserviert. form.name_pattern_not_allowed=Das Muster "%s" ist in Organisationsnamen nicht erlaubt. form.create_org_not_allowed=Du bist nicht berechtigt, eine Organisation zu erstellen. - settings=Einstellungen settings.options=Organisation settings.full_name=Vollständiger Name @@ -2467,7 +2489,6 @@ settings.visibility.limited=Begrenzt (nur für authentifizierte Benutzer sichtba settings.visibility.limited_shortname=Begrenzt settings.visibility.private=Privat (nur für Organisationsmitglieder sichtbar) settings.visibility.private_shortname=Privat - settings.update_settings=Einstellungen speichern settings.update_setting_success=Organisationseinstellungen wurden aktualisiert. settings.change_orgname_redirect_prompt=Der alte Name wird weiterleiten, bis er wieder beansprucht wird. @@ -2479,9 +2500,7 @@ settings.confirm_delete_account=Löschen bestätigen settings.delete_org_title=Organisation löschen settings.delete_org_desc=Diese Organisation wird dauerhaft gelöscht. Fortfahren? settings.hooks_desc=Webhooks hinzufügen, die für alle Repositories dieser Organisation ausgelöst werden. - settings.labels_desc=Labels hinzufügen, die für alle Repositories dieser Organisation genutzt werden können. - members.membership_visibility=Sichtbarkeit der Mitgliedschaft: members.public=Sichtbar members.public_helper=verstecken @@ -2496,7 +2515,6 @@ members.leave=Verlassen members.leave.detail=%s verlassen? members.invite_desc=Neues Mitglied zu %s hinzufügen: members.invite_now=Jetzt einladen - teams.join=Beitreten teams.leave=Verlassen teams.leave.detail=%s verlassen? @@ -2547,6 +2565,9 @@ teams.all_repositories_write_permission_desc=Dieses Team gewährt Schrei teams.all_repositories_admin_permission_desc=Dieses Team gewährt Administrator-Zugriff auf alle Repositories: Mitglieder können Repositories lesen, auf sie pushen und Mitwirkende zu Repositorys hinzufügen. teams.invite.by=Von %s eingeladen teams.invite.description=Bitte klicke auf die folgende Schaltfläche, um dem Team beizutreten. +settings.email=Kontakt-E-Mail-Adresse +settings.change_orgname_prompt=Hinweis: Das Ändern des Organisationsnamens wird auch die URL deiner Organisation ändern und den alten Namen freigeben. +teams.invite.title=Du wurdest eingeladen, dem Team %s in der Organisation %s beizutreten. [admin] dashboard=Dashboard @@ -2562,7 +2583,6 @@ monitor=Monitoring first_page=Erste last_page=Letzte total=Gesamt: %d - dashboard.new_version_hint=Gitea %s ist jetzt verfügbar, deine derzeitige Version ist %s. Weitere Details findest du im Blog. dashboard.statistic=Übersicht dashboard.operations=Wartungsoperationen @@ -2641,7 +2661,6 @@ dashboard.gc_lfs=Garbage-Collection für LFS Meta-Objekte ausführen dashboard.stop_zombie_tasks=Zombie-Aufgaben stoppen dashboard.stop_endless_tasks=Endlose Aufgaben stoppen dashboard.cancel_abandoned_jobs=Aufgegebene Jobs abbrechen - users.user_manage_panel=Benutzerkontenverwaltung users.new_account=Benutzerkonto erstellen users.name=Benutzername @@ -2695,7 +2714,6 @@ users.list_status_filter.is_prohibit_login=Login verbieten users.list_status_filter.not_prohibit_login=Login erlaubt users.list_status_filter.is_2fa_enabled=2FA aktiviert users.list_status_filter.not_2fa_enabled=2FA deaktiviert - emails.email_manage_panel=Benutzer-E-Mail-Verwaltung emails.primary=Primär emails.activated=Aktiviert @@ -2708,13 +2726,11 @@ emails.not_updated=Fehler beim Aktualisieren der angeforderten E-Mail-Adresse: % emails.duplicate_active=Diese E-Mail-Adresse wird bereits von einem Nutzer verwendet. emails.change_email_header=E-Mail-Eigenschaften aktualisieren emails.change_email_text=Bist du dir sicher, diese E-Mail-Adresse zu aktualisieren? - orgs.org_manage_panel=Organisationsverwaltung orgs.name=Name orgs.teams=Teams orgs.members=Mitglieder orgs.new_orga=Neue Organisation - repos.repo_manage_panel=Repositoryverwaltung repos.unadopted=Nicht übernommene Repositories repos.unadopted.no_more=Keine weiteren nicht übernommenen Repositories gefunden @@ -2726,7 +2742,6 @@ repos.stars=Favoriten repos.forks=Forks repos.issues=Issues repos.size=Größe - packages.package_manage_panel=Paketverwaltung packages.total_size=Gesamtgröße: %s packages.unreferenced_size=Nicht referenzierte Größe: %s @@ -2738,15 +2753,12 @@ packages.type=Typ packages.repository=Repository packages.size=Größe packages.published=Veröffentlicht - defaulthooks=Standard-Webhooks defaulthooks.add_webhook=Standard-Webhook hinzufügen defaulthooks.update_webhook=Standard-Webhook aktualisieren - systemhooks=System-Webhooks systemhooks.add_webhook=System-Webhook hinzufügen systemhooks.update_webhook=System-Webhook aktualisieren - auths.auth_manage_panel=Authentifikationsquellen verwalten auths.new=Authentifizierungsquelle hinzufügen auths.name=Name @@ -2847,7 +2859,7 @@ auths.tip.google_plus=Du erhältst die OAuth2-Client-Zugangsdaten in der Google- auths.tip.openid_connect=Benutze die OpenID-Connect-Discovery-URL (/.well-known/openid-configuration), um die Endpunkte zu spezifizieren auths.tip.twitter=Gehe auf https://dev.twitter.com/apps, erstelle eine Anwendung und stelle sicher, dass die Option „Allow this application to be used to Sign in with Twitter“ aktiviert ist auths.tip.discord=Erstelle unter https://discordapp.com/developers/applications/me eine neue Anwendung. -auths.tip.yandex=`Erstelle eine neue Anwendung auf https://oauth.yandex.com/client/new. Wähle folgende Berechtigungen aus dem "Yandex.Passport API" Bereich: "Zugriff auf E-Mail-Adresse", "Zugriff auf Benutzeravatar" und "Zugriff auf Benutzername, Vor- und Nachname, Geschlecht"` +auths.tip.yandex=Erstelle eine neue Anwendung auf https://oauth.yandex.com/client/new. Wähle folgende Berechtigungen aus dem "Yandex.Passport API" Bereich: "Zugriff auf E-Mail-Adresse", "Zugriff auf Benutzeravatar" und "Zugriff auf Benutzername, Vor- und Nachname, Geschlecht" auths.tip.mastodon=Gebe eine benutzerdefinierte URL für die Mastodon-Instanz ein, mit der du dich authentifizieren möchtest (oder benutze die standardmäßige) auths.edit=Authentifikationsquelle bearbeiten auths.activated=Diese Authentifikationsquelle ist aktiviert @@ -2863,7 +2875,6 @@ auths.login_source_exist=Die Authentifizierungsquelle "%s" existiert bereits. auths.login_source_of_type_exist=Eine Authentifizierungart dieses Typs existiert bereits. auths.unable_to_initialize_openid=OpenID Connect Provider konnte nicht initialisiert werden: %s auths.invalid_openIdConnectAutoDiscoveryURL=Ungültige Auto-Discovery-URL (dies muss eine gültige URL sein, die mit http:// oder https:// beginnt) - config.server_config=Serverkonfiguration config.app_name=Seitentitel config.app_ver=Gitea-Version @@ -2881,7 +2892,6 @@ config.lfs_root_path=LFS-Wurzelpfad config.log_file_root_path=Logdateipfad config.script_type=Skript-Typ config.reverse_auth_user=Nutzer bei Reverse-Authentifizierung - config.ssh_config=SSH-Konfiguration config.ssh_enabled=Aktiviert config.ssh_start_builtin_server=Eingebauten Server verwenden @@ -2893,12 +2903,10 @@ config.ssh_key_test_path=Schlüssel-Test-Pfad config.ssh_keygen_path=Keygen-Pfad („ssh-keygen“) config.ssh_minimum_key_size_check=Prüfung der Mindestschlüssellänge config.ssh_minimum_key_sizes=Mindestschlüssellängen - config.lfs_config=LFS-Konfiguration config.lfs_enabled=Aktiviert config.lfs_content_path=LFS Content-Pfad config.lfs_http_auth_expiry=Ablauf der LFS HTTP Authentifizierung - config.db_config=Datenbankkonfiguration config.db_type=Typ config.db_host=Host @@ -2907,7 +2915,6 @@ config.db_user=Benutzername config.db_schema=Schema config.db_ssl_mode=SSL config.db_path=Verzeichnis - config.service_config=Service-Konfiguration config.register_email_confirm=E-Mail-Bestätigung benötigt zum Registrieren config.disable_register=Selbstregistrierung deaktivieren @@ -2930,12 +2937,10 @@ config.default_allow_only_contributors_to_track_time=Nur Mitarbeitern erlauben, config.no_reply_address=Versteckte E-Mail-Domain config.default_visibility_organization=Standard-Sichtbarkeit für neue Organisationen config.default_enable_dependencies=Issue-Abhängigkeiten standardmäßig aktivieren - config.webhook_config=Webhook-Konfiguration config.queue_length=Warteschlangenlänge config.deliver_timeout=Zeitlimit für Zustellung config.skip_tls_verify=TLS-Verifikation überspringen - config.mailer_config=Mailer-Konfiguration config.mailer_enabled=Aktiviert config.mailer_enable_helo=HELO aktivieren @@ -2954,16 +2959,13 @@ config.send_test_mail=Test-E-Mail senden config.send_test_mail_submit=Senden config.test_mail_failed=Das Senden der Test-E-Mail an '%s' ist fehlgeschlagen: %v config.test_mail_sent=Eine Test-E-Mail wurde an "%s" gesendet. - config.oauth_config=OAuth-Konfiguration config.oauth_enabled=Aktiviert - config.cache_config=Cache-Konfiguration config.cache_adapter=Cache-Adapter config.cache_interval=Cache-Intervall config.cache_conn=Cache-Anbindung config.cache_item_ttl=Cache Item-TTL - config.session_config=Session-Konfiguration config.session_provider=Session-Provider config.provider_config=Provider-Einstellungen @@ -2972,12 +2974,10 @@ config.gc_interval_time=GC-Intervall config.session_life_time=Session-Lebensdauer config.https_only=Nur HTTPS config.cookie_life_time=Cookie-Lebensdauer - config.picture_config=Bild-und-Profilbild-Konfiguration config.picture_service=Bilderservice config.disable_gravatar=Gravatar deaktivieren config.enable_federated_avatar=Föderierte Profilbilder einschalten - config.git_config=Git-Konfiguration config.git_disable_diff_highlight=Diff-Syntaxhervorhebung ausschalten config.git_max_diff_lines=Max. Diff-Zeilen (in einer Datei) @@ -2989,19 +2989,15 @@ config.git_mirror_timeout=Zeitlimit für Mirror-Aktualisierung config.git_clone_timeout=Zeitlimit für Clone config.git_pull_timeout=Zeitlimit für Pull config.git_gc_timeout=Zeitlimit für GC - config.log_config=Konfiguration des Loggings config.logger_name_fmt=Logger: %s config.disabled_logger=Deaktiviert config.access_log_mode=Access-Log-Modus config.access_log_template=Zugriffslog-Vorlage config.xorm_log_sql=SQL protokollieren - config.get_setting_failed=Holen der Einstellung %s fehlgeschlagen config.set_setting_failed=Konfiguration von %s ist fehlgeschlagen - monitor.stats=Statistiken - monitor.cron=Cron-Aufgaben monitor.name=Name monitor.schedule=Zeitplan @@ -3020,7 +3016,6 @@ monitor.process.cancel=Prozess abbrechen monitor.process.cancel_desc=Abbrechen eines Prozesses kann Datenverlust verursachen monitor.process.cancel_notices=Abbrechen: %s? monitor.process.children=Subprozesse - monitor.queues=Warteschlangen monitor.queue=Warteschlange: %s monitor.queue.name=Name @@ -3038,7 +3033,6 @@ monitor.queue.settings.submit=Einstellungen aktualisieren monitor.queue.settings.changed=Einstellungen aktualisiert monitor.queue.settings.remove_all_items=Alle entfernen monitor.queue.settings.remove_all_items_done=Alle Elemente in der Warteschlange wurden entfernt. - notices.system_notice_list=Systemmitteilungen notices.view_detail_header=Meldungsdetails ansehen notices.operations=Operationen @@ -3053,21 +3047,45 @@ notices.type_2=Aufgabe notices.desc=Beschreibung notices.op=Aktion notices.delete_success=Diese Systemmeldung wurde gelöscht. +identity_access=Identität & Zugriff +assets=Code-Assets +integrations=Integrationen +settings=Administratoreinstellungen +dashboard.task.cancelled=Aufgabe: %[1]s abgebrochen: %[3]s +dashboard.cron.cancelled=Cron: %[1]s abgebrochen: %[3]s +dashboard.sync_repo_branches=Fehlende Branches aus den Git-Daten in die Datenbank synchronisieren +dashboard.cleanup_actions=Abgelaufene Logs und Artefakte von Actions bereinigen +dashboard.start_schedule_tasks=Terminierte Aufgaben starten +dashboard.sync_branch.started=Synchronisierung der Branches gestartet +dashboard.rebuild_issue_indexer=Issue-Indexer neu bauen +users.reserved=Reserviert +users.bot=Bot +users.remote=Remote +users.details=Benutzerdetails +repos.lfs_size=LFS-Größe +packages.cleanup=Veraltete Daten löschen +defaulthooks.desc=Webhooks senden automatisch eine HTTP-POST-Anfrage an einen Server, wenn bestimmte Gitea-Events ausgelöst werden. Hier definierte Webhooks sind die Standardwerte, die in alle neuen Repositories kopiert werden. Mehr Infos findest du in der Webhooks-Anleitung (auf Englisch). +systemhooks.desc=Webhooks senden automatisch HTTP-POST-Anfragen an einen Server, wenn bestimmte Gitea-Events ausgelöst werden. Hier definierte Webhooks werden auf alle Repositories des Systems übertragen, beachte daher mögliche Performance-Einbrüche. Mehr Infos findest du in der Webhooks-Anleitung (auf Englisch). +auths.tips.oauth2.general.tip=Beim Registrieren einer OAuth2-Anwendung sollte die Callback-URL folgendermaßen lauten: +auths.tip.gitea=Registriere eine neue OAuth2-Anwendung. Eine Anleitung findest du unter https://docs.gitea.com/development/oauth2-provider/ +config.app_data_path=App-Datenpfad +monitor.queue.activeworkers=Aktive Worker +monitor.queue.review_add=Worker hinzufügen / prüfen [action] create_repo=hat das Repository %s erstellt rename_repo=hat das Repository von %[1]s zu %[3]s umbenannt commit_repo=hat %[3]s auf %[4]s gepusht -create_issue=`hat das Issue %[3]s#%[2]s geöffnet` -close_issue=`hat das Issue %[3]s#%[2]s geschlossen` -reopen_issue=`hat das Issue %[3]s#%[2]s wiedereröffnet` -create_pull_request=`hat den Pull-Request %[3]s#%[2]s erstellt` -close_pull_request=`Pull-Request %[3]s#%[2]s wurde geschlossen` -reopen_pull_request=`Pull-Request %[3]s#%[2]s wurde wiedereröffnet` -comment_issue=`hat das Issue %[3]s#%[2]s kommentiert` -comment_pull=`Pull-Request %[3]s#%[2]s wurde kommentiert` -merge_pull_request=`Pull-Request %[3]s#%[2]s wurde zusammengeführt` -auto_merge_pull_request=`Pull-Request %[3]s#%[2]s wurde automatisch zusammengeführt` +create_issue=hat das Issue %[3]s#%[2]s geöffnet +close_issue=hat das Issue %[3]s#%[2]s geschlossen +reopen_issue=hat das Issue %[3]s#%[2]s wiedereröffnet +create_pull_request=hat den Pull-Request %[3]s#%[2]s erstellt +close_pull_request=Pull-Request %[3]s#%[2]s wurde geschlossen +reopen_pull_request=Pull-Request %[3]s#%[2]s wurde wiedereröffnet +comment_issue=hat das Issue %[3]s#%[2]s kommentiert +comment_pull=Pull-Request %[3]s#%[2]s wurde kommentiert +merge_pull_request=Pull-Request %[3]s#%[2]s wurde zusammengeführt +auto_merge_pull_request=Pull-Request %[3]s#%[2]s wurde automatisch zusammengeführt transfer_repo=hat Repository %s transferiert an %s push_tag=Tag %[3]s nach %[4]s wurde gepusht delete_tag=hat Tag %[2]s in %[3]s gelöscht @@ -3078,10 +3096,10 @@ compare_commits_general=Commits vergleichen mirror_sync_push=Commits zu %[3]s bei %[4]s wurden von einem Spiegel synchronisiert mirror_sync_create=neue Referenz %[3]s bei %[4]s wurde von einem Spiegel synchronisiert mirror_sync_delete=hat die Referenz des Mirrors %[2]s in %[3]s synchronisiert und gelöscht -approve_pull_request=`hat %[3]s#%[2]s approved` -reject_pull_request=`schlug Änderungen für %[3]s#%[2]s vor` -publish_release=`veröffentlichte Release "%[4]s" in %[3]s` -review_dismissed=`verwarf das Review von %[4]s in %[3]s#%[2]s` +approve_pull_request=hat %[3]s#%[2]s approved +reject_pull_request=schlug Änderungen für %[3]s#%[2]s vor +publish_release=veröffentlichte Release "%[4]s" in %[3]s +review_dismissed=verwarf das Review von %[4]s in %[3]s#%[2]s review_dismissed_reason=Grund: create_branch=legte den Branch %[3]s in %[4]s an starred_repo=markiert %[2]s @@ -3288,6 +3306,13 @@ owner.settings.cleanuprules.success.update=Bereinigungsregel wurde aktualisiert. owner.settings.cleanuprules.success.delete=Bereinigungsregel wurde gelöscht. owner.settings.chef.title=Chef-Registry owner.settings.chef.keypair=Schlüsselpaar generieren +registry.documentation=Für weitere Informationen zur %s-Registry, schaue in der Dokumentation nach. +rpm.distros.redhat=auf RedHat-basierten Distributionen +rpm.distros.suse=auf SUSE-basierten Distributionen +owner.settings.cargo.initialize.description=Ein spezielles Index-Repository wird benötigt, um die Cargo-Registry zu nutzen. Diese Option wird dieses Repository (neu) erstellen und automatisch konfigurieren. +owner.settings.cargo.rebuild.description=Neubauen kann hilfreich sein, wenn der Index nicht mit den gespeicherten Cargo-Paketen synchronisiert ist. +owner.settings.cleanuprules.none=Keine Bereinigungs-Regeln verfügbar. Bitte konsultiere die Dokumentation. +owner.settings.chef.keypair.description=Ein Schlüsselpaar ist notwendig, um mit der Chef-Registry zu authentifizieren. Wenn du bereits eins erstellt hast, wird dieses durch eine Neuerstellung verworfen. [secrets] secrets=Secrets @@ -3306,9 +3331,7 @@ management=Secret-Verwaltung [actions] actions=Actions - unit.desc=Actions verwalten - status.unknown=Unbekannt status.waiting=Wartend status.running=Laufend @@ -3316,7 +3339,6 @@ status.success=Erfolg status.failure=Fehler status.skipped=Übersprungen status.blocked=Blockiert - runners=Runner runners.runner_manage_panel=Runner-Verwaltung runners.new=Neuen Runner erstellen @@ -3351,16 +3373,42 @@ runners.status.active=Aktiv runners.status.offline=Offline runners.version=Version runners.reset_registration_token_success=Runner-Registrierungstoken erfolgreich zurückgesetzt - runs.all_workflows=Alle Workflows runs.commit=Commit runs.invalid_workflow_helper=Die Workflow-Konfigurationsdatei ist ungültig. Bitte überprüfe Deine Konfigurationsdatei: %s runs.no_matching_runner_helper=Kein passender Runner: %s runs.status=Status - - need_approval_desc=Um Workflows für den Pull-Request eines Forks auszuführen, ist eine Genehmigung erforderlich. - +status.cancelled=Abgebrochen +runners.task_list.no_tasks=Es gibt noch keine Aufgabe. +runners.reset_registration_token=Registrierungs-Token zurücksetzen +runs.scheduled=Geplant +runs.pushed_by=gepusht von +runs.actor=Initiator +runs.actors_no_select=Alle Initiatoren +runs.status_no_select=Alle Status +runs.no_results=Keine passenden Ergebnisse gefunden. +runs.no_runs=Der Workflow hat noch keine Ausführungen. +workflow.disable=Workflow deaktivieren +workflow.disable_success=Workflow '%s' erfolgreich deaktiviert. +workflow.enable=Workflow aktivieren +workflow.enable_success=Workflow '%s' erfolgreich aktiviert. +workflow.disabled=Workflow ist deaktiviert. +variables=Variablen +variables.management=Variablenverwaltung +variables.creation=Variable hinzufügen +variables.none=Es gibt noch keine Variablen. +variables.deletion=Variable entfernen +variables.deletion.description=Das Entfernen einer Variable ist dauerhaft und kann nicht rückgängig gemacht werden. Fortfahren? +variables.description=Variablen werden an bestimmte Aktionen übergeben und können nicht anderweitig gelesen werden. +variables.id_not_exist=Variable mit ID %d existiert nicht. +variables.edit=Variable bearbeiten +variables.deletion.failed=Fehler beim Entfernen der Variable. +variables.deletion.success=Die Variable wurde entfernt. +variables.creation.failed=Fehler beim Hinzufügen der Variable. +variables.creation.success=Die Variable „%s“ wurde hinzugefügt. +variables.update.failed=Fehler beim Bearbeiten der Variable. +variables.update.success=Die Variable wurde bearbeitet. [projects] type-1.display_name=Individuelles Projekt @@ -3369,4 +3417,8 @@ type-3.display_name=Organisationsprojekt [git.filemode] symbolic_link=Softlink - +changed_filemode=%[1]s → %[2]s +directory=Verzeichnis +normal_file=Normale Datei +executable_file=Ausführbare Datei +submodule=Submodul diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 63cc1ca2e5..99b4897415 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -23,7 +23,6 @@ enable_javascript=Απαιτείται JavaScript για να εμφανιστε toc=Πίνακας Περιεχομένων licenses=Άδειες return_to_gitea=Επιστροφή στο Gitea - username=Όνομα Χρήστη email=Διεύθυνση Email password=Κωδικός πρόσβασης @@ -33,7 +32,6 @@ captcha=CAPTCHA twofa=Έλεγχος Ταυτότητας Δύο Παραγόντων twofa_scratch=Κωδικός Μίας Χρήσης Δύο Παραγόντων passcode=Κωδικός - webauthn_insert_key=Εισάγετε το κλειδί ασφαλείας σας webauthn_sign_in=Πατήστε το κουμπί στο κλειδί ασφαλείας. Αν το κλειδί ασφαλείας σας δεν έχει κουμπί, τοποθετήστε το ξανά. webauthn_press_button=Παρακαλώ πατήστε το κουμπί στο κλειδί ασφαλείας… @@ -41,13 +39,12 @@ webauthn_use_twofa=Χρησιμοποιήστε έναν κωδικό δύο π webauthn_error=Αδύνατη η ανάγνωση του κλειδιού ασφαλείας. webauthn_unsupported_browser=Το πρόγραμμα περιήγησής σας δεν υποστηρίζει επί του παρόντος WebAuthn. webauthn_error_unknown=Παρουσιάστηκε ένα άγνωστο σφάλμα. Παρακαλώ προσπαθήστε ξανά. -webauthn_error_insecure=`Το WebAuthn υποστηρίζει μόνο ασφαλείς συνδέσεις. Για δοκιμές πάνω από HTTP, μπορείτε να χρησιμοποιήσετε την προέλευση "localhost" ή "127.0.0.1"` +webauthn_error_insecure=Το WebAuthn υποστηρίζει μόνο ασφαλείς συνδέσεις. Για δοκιμές πάνω από HTTP, μπορείτε να χρησιμοποιήσετε την προέλευση "localhost" ή "127.0.0.1" webauthn_error_unable_to_process=Ο διακομιστής δεν μπόρεσε να επεξεργαστεί το αίτημά σας. webauthn_error_duplicated=Το κλειδί ασφαλείας δεν επιτρέπεται για αυτό το αίτημα. Βεβαιωθείτε ότι το κλειδί δεν έχει ήδη καταχωρηθεί. webauthn_error_empty=Πρέπει να ορίσετε ένα όνομα για αυτό το κλειδί. webauthn_error_timeout=Το χρονικό όριο έφτασε πριν το κλειδί να διαβαστεί. Παρακαλώ ανανεώστε τη σελίδα και προσπαθήστε ξανά. webauthn_reload=Ανανέωση - repository=Αποθετήριο organization=Οργανισμός mirror=Αντίγραφο @@ -65,18 +62,15 @@ settings=Ρυθμίσεις your_profile=Προφίλ your_starred=Με αστέρι your_settings=Ρυθμίσεις - all=Όλα sources=Πηγές mirrors=Είδωλα collaborative=Συνεργατικά forks=Forks - activities=Δραστηριότητες pull_requests=Pull Requests issues=Ζητήματα milestones=Ορόσημα - ok=OK cancel=Ακύρωση rerun=Επανεκτέλεση @@ -86,12 +80,10 @@ add=Προσθήκη add_all=Προσθήκη Όλων remove=Αφαίρεση remove_all=Αφαίρεση Όλων -remove_label_str=`Αφαίρεση του αντικειμένου "%s"` +remove_label_str=Αφαίρεση του αντικειμένου "%s" edit=Επεξεργασία - enabled=Ενεργοποιημένο disabled=Απενεργοποιημένο - copy=Αντιγραφή copy_url=Αντιγραφή URL copy_content=Αντιγραφή περιεχομένου @@ -99,39 +91,29 @@ copy_branch=Αντιγραφή ονόματος κλάδου copy_success=Αντιγράφηκε! copy_error=Η αντιγραφή απέτυχε copy_type_unsupported=Αυτός ο τύπος αρχείου δεν μπορεί να αντιγραφεί - write=Σύνταξη preview=Προεπισκόπηση loading=Φόρτωση… - step1=Βήμα 1: step2=Βήμα 2: - error=Σφάλμα error404=Η σελίδα που προσπαθείτε να φτάσετε είτε δεν υπάρχει είτε δεν είστε εξουσιοδοτημένοι για να την δείτε. - never=Ποτέ unknown=Άγνωστη - rss_feed=Ροή RSS - pin=Στήριξη unpin=Άφεση - artifacts=Αντικείμενα - concept_system_global=Γενικό concept_user_individual=Ατομικό concept_code_repository=Αποθετήριο concept_user_organization=Οργανισμός - show_timestamps=Εμφάνιση χρονοσημάνσεων show_log_seconds=Εμφάνιση δευτερολέπτων show_full_screen=Εμφάνιση πλήρους οθόνης - - name=Όνομα value=Τιμή +archived=Αρχειοθετήθηκε [aria] navbar=Γραμμή Πλοήγησης @@ -210,7 +192,6 @@ err_empty_admin_email=Το email του διαχειριστή δεν μπορε err_admin_name_is_reserved=Το Όνομα χρήστη του Διαχειριστή δεν είναι έγκυρο, είναι δεσμευμένο err_admin_name_pattern_not_allowed=Το Όνομα χρήστη του Διαχειριστή δεν είναι έγκυρο, ταιριάζει σε μια δεσμευμένη μορφή err_admin_name_is_invalid=Το Όνομα Χρήστη του Διαχειριστή δεν είναι έγκυρο - general_title=Γενικές Ρυθμίσεις app_name=Τίτλος Ιστοτόπου app_name_helper=Μπορείτε να εισάγετε το όνομα της εταιρείας σας εδώ. @@ -229,7 +210,6 @@ app_url=Βασικό URL του Gitea app_url_helper=Βασική Διεύθυνση για τα URL κλωνοποίησης μέσω HTTP(S) και για τις ειδοποιήσεις μέσω email. log_root_path=Διαδρομή Αρχείων Καταγραφής log_root_path_helper=Τα αρχεία καταγραφής θα γράφονται σε αυτόν τον κατάλογο. - optional_title=Προαιρετικές Ρυθμίσεις email_title=Ρυθμίσεις Email smtp_addr=Διακομιστής SMTP @@ -290,6 +270,7 @@ invalid_password_algorithm=Μη έγκυρος αλγόριθμος κωδικο password_algorithm_helper=Ορίστε τον αλγόριθμο κατακερματισμού για το κωδικό πρόσβασης. Οι αλγόριθμοι διαφέρουν σε απαιτήσεις και αντοχή. Ο αλγόριθμος argon2 είναι αρκετά ασφαλής, αλλά χρησιμοποιεί πολλή μνήμη και μπορεί να είναι ακατάλληλος για μικρά συστήματα. enable_update_checker=Ενεργοποίηση Ελεγκτή Ενημερώσεων enable_update_checker_helper=Ελέγχει περιοδικά για νέες εκδόσεις κάνοντας σύνδεση στο gitea.io. +env_config_keys=Ρυθμίσεις Περιβάλλοντος [home] uname_holder=Όνομα Χρήστη ή Διεύθυνση Email @@ -304,19 +285,16 @@ view_home=Προβολή %s search_repos=Βρείτε ένα αποθετήριο… filter=Άλλα Φίλτρα filter_by_team_repositories=Φιλτράρισμα ανά αποθετήρια ομάδας -feed_of=`Τροφοδοσία του "%s"` - +feed_of=Τροφοδοσία του "%s" show_archived=Αρχειοθετήθηκε archived=Αρχειοθετήθηκε show_both_archived_unarchived=Εμφάνιση και αρχειοθετημένων και μη αρχειοθετημένων show_only_archived=Εμφάνιση μόνο αρχειοθετημένων show_only_unarchived=Εμφάνιση μόνο μη αρχειοθετημένων - show_private=Ιδιωτικό show_both_private_public=Εμφάνιση και δημόσιων και ιδιωτικών show_only_private=Εμφανίζονται μόνο ιδιωτικά show_only_public=Εμφανίζονται μόνο δημόσια - issues.in_your_repos=Στα αποθετήρια σας [explore] @@ -336,12 +314,11 @@ repo_no_results=Δεν βρέθηκαν αποθετήρια που να ται user_no_results=Δεν βρέθηκαν χρήστες που να ταιριάζουν με τα κριτήρια. org_no_results=Δεν βρέθηκαν οργανισμοί που να ταιριάζουν με τα κριτήρια. code_no_results=Δεν βρέθηκε πηγαίος κώδικας που να ταιριάζει με τον όρο αναζήτησης. -code_search_results=`Αποτελέσματα αναζήτησης για "%s"` +code_search_results=Αποτελέσματα αναζήτησης για "%s" code_last_indexed_at=Τελευταίο δημιουργία ευρετηρίου στις %s relevant_repositories_tooltip=Τα αποθετήρια που είναι forks ή που δεν έχουν θέμα, εικονίδιο και περιγραφή είναι κρυμμένα. relevant_repositories=Εμφανίζονται μόνο τα σχετικά αποθετήρια, εμφάνιση χωρίς φίλτρο. - [auth] create_new_account=Εγγραφή Λογαριασμού register_helper_msg=Έχετε ήδη λογαριασμό; Συνδεθείτε τώρα! @@ -410,30 +387,23 @@ view_it_on=Δείτε το στο %s reply=ή απαντήστε απευθείας σε αυτό το email link_not_working_do_paste=Δε λειτουργεί; Δοκιμάστε να το αντιγράψετε στο browser σας. hi_user_x=Γειά σου %s, - activate_account=Παρακαλώ ενεργοποιήστε το λογαριασμό σας activate_account.title=%s, παρακαλώ ενεργοποιήστε το λογαριασμό σας activate_account.text_1=Γεια σας %[1]s, ευχαριστούμε για την εγγραφή στο %[2]s! activate_account.text_2=Παρακαλούμε κάντε κλικ στον παρακάτω σύνδεσμο για να ενεργοποιήσετε το λογαριασμό σας μέσα σε %s: - activate_email=Επιβεβαιώστε τη διεύθυνση email σας activate_email.text=Παρακαλώ κάντε κλικ στον παρακάτω σύνδεσμο για να επαληθεύσετε τη διεύθυνση email σας στο %s: - register_notify=Καλώς ήλθατε στο Gitea register_notify.title=%[1]s, καλώς ήρθατε στο %[2]s register_notify.text_1=αυτό είναι το email επιβεβαίωσης εγγραφής για το %s! register_notify.text_2=Τώρα μπορείτε να συνδεθείτε μέσω του ονόματος χρήστη: %s. register_notify.text_3=Εάν αυτός ο λογαριασμός έχει δημιουργηθεί για εσάς, παρακαλώ ορίστε πρώτα τον κωδικό πρόσβασής σας. - reset_password=Ανάκτηση του λογαριασμού σας reset_password.title=%s, ζητήσατε να ανακτήσετε το λογαριασμό σας reset_password.text=Κάντε κλικ στον παρακάτω σύνδεσμο για να ανακτήσετε το λογαριασμό σας εντός %s: - register_success=Επιτυχής εγγραφή - issue_assigned.pull=@%[1]s σας έχει αναθέσει στο pull request %[2]s στο αποθετήριο %[3]s. issue_assigned.issue=@%[1]s σας ανέθεσε το ζήτημα %[2]s στο αποθετήριο %[3]s. - issue.x_mentioned_you=@%s σας ανέφερε: issue.action.force_push=%[1]s έκανε force-push το %[2]s από %[3]s σε %[4]s. issue.action.push_1=@%[1]s έκανε push την υποβολή %[3]d στο %[2]s @@ -448,7 +418,6 @@ issue.action.review_dismissed=@%[1]s απέρριψε την τελευτ issue.action.ready_for_review=@%[1]s σημείωσε αυτό το pull request σαν έτοιμο για αναθεώρηση. issue.action.new=@%[1]s δημιούργησε το #%[2]d. issue.in_tree_path=Σε %s: - release.new.subject=%s σε %s κυκλοφόρησε release.new.text=@%[1]s κυκλοφόρησε το %[2]s στο %[3]s release.title=Τίτλος: %s @@ -456,15 +425,12 @@ release.note=Σημείωση: release.downloads=Λήψεις: release.download.zip=Πηγαίος Κώδικας (Zip) release.download.targz=Πηγαίος Κώδικας (TAR.GZ) - repo.transfer.subject_to=%s θα ήθελε να μεταφέρει το "%s" σε %s -repo.transfer.subject_to_you=`%s θα ήθελε να σας μεταφέρει το "%s"` +repo.transfer.subject_to_you=%s θα ήθελε να σας μεταφέρει το "%s" repo.transfer.to_you=εσάς repo.transfer.body=Για να το αποδεχτείτε ή να το απορρίψετε, επισκεφθείτε το %s ή απλά αγνοήστε το. - repo.collaborator.added.subject=%s σας πρόσθεσε στο %s repo.collaborator.added.text=Έχετε προστεθεί ως συνεργάτης του αποθετηρίου: - team_invite.subject=%[1]s σας προσκάλεσε να συμμετέχετε στον οργανισμό %[2]s team_invite.text_1=%[1]s σας προσκάλεσε να συμμετέχετε στην ομάδα %[2]s στον οργανισμός %[3]s. team_invite.text_2=Παρακαλώ κάντε κλικ στον παρακάτω σύνδεσμο για να συμμετάσχετε στην ομάδα: @@ -489,36 +455,32 @@ PayloadUrl=Payload URL TeamName=Όνομα ομάδας AuthName=Όνομα εξουσιοδότησης AdminEmail=Email διαχειριστή - NewBranchName=Όνομα νέου κλάδου CommitSummary=Περίληψη υποβολών CommitMessage=Μήνυμα υποβολής CommitChoice=Επιλογή υποβολής TreeName=Διαδρομή αρχείου Content=Περιεχόμενο - SSPISeparatorReplacement=Διαχωριστικό SSPIDefaultLanguage=Προεπιλεγμένη Γλώσσα - -require_error=` δεν μπορεί να είναι κενό.` -alpha_dash_error=` πρέπει να περιέχει μόνο αλφαριθμητικά, παύλες ('-') και κάτω παύλες ('_').` -alpha_dash_dot_error=` πρέπει να περιέχει μόνο αλφαριθμητικά, παύλα ('-'), κάτω παύλα ('_') και τελείες ('.').` -git_ref_name_error=` πρέπει να είναι ένα καλά διαμορφωμένο όνομα αναφοράς Git.` -size_error=`πρέπει να έχει μέγεθος %s.` -min_size_error=` πρέπει να περιέχει τουλάχιστον %s χαρακτήρες.` -max_size_error=` πρέπει να περιέχει το πολύ %s χαρακτήρες.` -email_error=` δεν είναι έγκυρη διεύθυνση email.` -url_error=`Το "%s" δεν είναι ένα έγκυρο URL.` -include_error=` πρέπει να περιέχει το μέρος "%s".` -glob_pattern_error=` το μοτίβο ταιριάσματος (glob) δεν είναι έγκυρο: %s.` -regex_pattern_error=` το μοτίβο regex δεν είναι έγκυρο: %s.` -username_error=` μπορεί να περιέχει μόνο αλφαριθμητικούς χαρακτήρες ('0-9','a-z','A-Z'), παύλα ('-'), κάτω παύλα ('_') και τελεία ('.'). Δεν μπορεί να ξεκινά ή να τελειώνει με μη αλφαριθμητικούς χαρακτήρες, επίσης απαγορεύονται οι διαδοχικοί μη αλφαριθμητικοί χαρακτήρες.` -invalid_group_team_map_error=` η αντιστοίχιση δεν είναι έγκυρη: %s` +require_error=" δεν μπορεί να είναι κενό." +alpha_dash_error=" πρέπει να περιέχει μόνο αλφαριθμητικά, παύλες ('-') και κάτω παύλες ('_')." +alpha_dash_dot_error=" πρέπει να περιέχει μόνο αλφαριθμητικά, παύλα ('-'), κάτω παύλα ('_') και τελείες ('.')." +git_ref_name_error=" πρέπει να είναι ένα καλά διαμορφωμένο όνομα αναφοράς Git." +size_error=πρέπει να έχει μέγεθος %s. +min_size_error=" πρέπει να περιέχει τουλάχιστον %s χαρακτήρες." +max_size_error=" πρέπει να περιέχει το πολύ %s χαρακτήρες." +email_error=" δεν είναι έγκυρη διεύθυνση email." +url_error=Το "%s" δεν είναι ένα έγκυρο URL. +include_error=" πρέπει να περιέχει το μέρος "%s"." +glob_pattern_error=" το μοτίβο ταιριάσματος (glob) δεν είναι έγκυρο: %s." +regex_pattern_error=" το μοτίβο regex δεν είναι έγκυρο: %s." +username_error=" μπορεί να περιέχει μόνο αλφαριθμητικούς χαρακτήρες ('0-9','a-z','A-Z'), παύλα ('-'), κάτω παύλα ('_') και τελεία ('.'). Δεν μπορεί να ξεκινά ή να τελειώνει με μη αλφαριθμητικούς χαρακτήρες, επίσης απαγορεύονται οι διαδοχικοί μη αλφαριθμητικοί χαρακτήρες." +invalid_group_team_map_error=" η αντιστοίχιση δεν είναι έγκυρη: %s" unknown_error=Άγνωστο σφάλμα: captcha_incorrect=Ο κωδικός CAPTCHA είναι λάθος. password_not_match=Οι κωδικοί πρόσβασης δεν ταιριάζουν. lang_select_error=Επιλέξτε μια γλώσσα από τη λίστα. - username_been_taken=Το όνομα χρήστη χρησιμοποιείται ήδη. username_change_not_local_user=Δεν επιτρέπεται στους μη τοπικούς χρήστες να αλλάξουν το όνομα χρήστη τους. username_has_not_been_changed=Το όνομα χρήστη δεν άλλαξε @@ -552,20 +514,17 @@ last_org_owner=Δεν μπορείτε να καταργήσετε τον τελ cannot_add_org_to_team=Ένας οργανισμός δεν μπορεί να προστεθεί ως μέλος ομάδας. duplicate_invite_to_team=Ο χρήστης είχε ήδη προσκληθεί ως μέλος της ομάδας. organization_leave_success=Έχετε εγκαταλείψει με επιτυχία τον οργανισμό %s. - invalid_ssh_key=Δεν είναι δυνατή η επαλήθευση του SSH κλειδιού σας: %s invalid_gpg_key=Δεν είναι δυνατή η επαλήθευση του GPG κλειδιού σας: %s invalid_ssh_principal=Μη έγκυρη αρχή: %s must_use_public_key=Το κλειδί που δώσατε είναι ένα ιδιωτικό κλειδί. Παρακαλώ μην ανεβάζετε το ιδιωτικό σας κλειδί οπουδήποτε. Χρησιμοποιήστε το δημόσιο κλειδί αντί αυτού. unable_verify_ssh_key=Αδυναμία επαλήθευσης του κλειδιού SSH, ελέγξτε το ξανά για λάθη. auth_failed=Αποτυχία ταυτοποίησης: %v - still_own_repo=Ο λογαριασμός σας κατέχει ένα ή περισσότερα αποθετήρια, διαγράψτε ή μεταφέρετε τα πρώτα. still_has_org=Ο λογαριασμός σας είναι μέλος σε ένα ή περισσότερους οργανισμούς, φύγετε από αυτούς πρώτα. still_own_packages=Ο λογαριασμός σας κατέχει ένα ή περισσότερα πακέτα, διαγράψτε τα πρώτα. org_still_own_repo=Αυτός ο οργανισμός κατέχει ακόμα ένα ή περισσότερα αποθετήρια, διαγράψτε τα ή μεταφέρετε τα πρώτα. org_still_own_packages=Αυτός ο οργανισμός κατέχει ακόμα ένα ή περισσότερα πακέτα, διαγράψτε τα πρώτα. - target_branch_not_exist=Ο κλάδος προορισμού δεν υπάρχει. [user] @@ -586,7 +545,6 @@ user_bio=Βιογραφικό disabled_public_activity=Αυτός ο χρήστης έχει απενεργοποιήσει τη δημόσια προβολή της δραστηριότητας. email_visibility.limited=Η διεύθυνση email σας είναι ορατή σε όλους τους ταυτοποιημένους χρήστες email_visibility.private=Η διεύθυνση email σας είναι ορατή μόνο σε εσάς και στους διαχειριστές - form.name_reserved=Το όνομα χρήστη "%s" είναι δεσμευμένο. form.name_pattern_not_allowed=Το μοτίβο "%s" δεν επιτρέπεται μέσα σε ένα όνομα χρήστη. form.name_chars_not_allowed=Το όνομα χρήστη "%s" περιέχει μη έγκυρους χαρακτήρες. @@ -608,7 +566,6 @@ twofa=Έλεγχος Ταυτότητας Δύο Παραγόντων account_link=Συνδεδεμένοι Λογαριασμοί organization=Οργανισμοί webauthn=Κλειδιά Ασφαλείας - public_profile=Δημόσιο Προφίλ password_username_disabled=Οι μη τοπικοί χρήστες δεν επιτρέπεται να αλλάξουν το όνομα χρήστη τους. Επικοινωνήστε με το διαχειριστή σας για περισσότερες λεπτομέρειες. full_name=Πλήρες Όνομα @@ -646,7 +603,6 @@ comment_type_group_issue_ref=Αναφορά ζητήματος saved_successfully=Οι ρυθμίσεις σας αποθηκεύτηκαν επιτυχώς. privacy=Απόρρητο keep_activity_private_popup=Με αυτή την επιλογή η δραστηριότητα σας είναι ορατή μόνο σε εσάς και τους διαχειριστές - lookup_avatar_by_mail=Αναζήτηση ενός Avatar με διεύθυνση email federated_avatar_lookup=Συνενωμένη Αναζήτηση Avatar enable_custom_avatar=Χρήση Προσαρμοσμένης Εικόνας @@ -656,14 +612,12 @@ delete_current_avatar=Διαγραφή Τρέχουσας Εικόνας uploaded_avatar_not_a_image=Το αρχείο που ανεβάσατε δεν είναι εικόνα. update_avatar_success=Η εικόνα σας έχει ενημερωθεί. update_user_avatar_success=Το avatar του χρήστη ενημερώθηκε. - change_password=Ενημέρωση Κωδικού Πρόσβασης old_password=Τρέχων Κωδικός Πρόσβασης new_password=Νέος Κωδικός Πρόσβασης password_incorrect=Ο τρέχων κωδικός πρόσβασης είναι λάθος. change_password_success=Ο κωδικός πρόσβασής σας έχει ενημερωθεί. Από εδώ και τώρα συνδέεστε χρησιμοποιώντας τον νέο κωδικό πρόσβασής σας. password_change_disabled=Οι μη τοπικοί χρήστες δεν μπορούν να ενημερώσουν τον κωδικό πρόσβασής τους μέσω του διεπαφής web του Gitea. - emails=Διευθύνσεις Email manage_emails=Διαχείριση Διευθύνσεων Email manage_themes=Επιλέξτε προεπιλεγμένο θέμα διεπαφής @@ -694,7 +648,6 @@ email_preference_set_success=Οι προτιμήσεις email έχουν ορι add_openid_success=Προστέθηκε η νέα διεύθυνση OpenID. keep_email_private=Απόκρυψη Διεύθυνσης Email openid_desc=Το OpenID σας επιτρέπει να αναθέσετε τον έλεγχο ταυτότητας σε έναν εξωτερικό πάροχο. - manage_ssh_keys=Διαχείριση SSH Κλειδιών manage_ssh_principals=Διαχείριση Των Αρχών Πιστοποιητικού SSH manage_gpg_keys=Διαχείριση Κλειδιών GPG @@ -772,7 +725,6 @@ ssh_signonly=Το SSH είναι απενεργοποιημένο αυτή τη ssh_externally_managed=Αυτό το κλειδί SSH διαχειρίζεται εξωτερικά για αυτόν το χρήστη manage_social=Διαχείριση Συσχετιζόμενων Λογαριασμών Κοινωνικών Δικτύων unbind=Αποσύνδεση - manage_access_token=Διαχείριση Διακριτικών Πρόσβασης generate_new_token=Δημιουργία Νέου Διακριτικού tokens_desc=Αυτά τα διακριτικά (tokens) παρέχουν πρόσβαση στο λογαριασμό σας μέσω του API του Gitea. @@ -794,7 +746,6 @@ permission_no_access=Καμία Πρόσβαση permission_read=Αναγνωσμένες at_least_one_permission=Πρέπει να επιλέξετε τουλάχιστον ένα δικαίωμα για να δημιουργήσετε ένα διακριτικό permissions_list=Δικαιώματα: - manage_oauth2_applications=Διαχείριση Εφαρμογών Oauth2 edit_oauth2_application=Επεξεργασία Εφαρμογής Oauth2 oauth2_applications_desc=Οι εφαρμογές OAuth2 επιτρέπουν στην εξωτερική εφαρμογή σας την ασφαλή ταυτοποίηση των χρηστών σε αυτό το Gitea. @@ -813,12 +764,10 @@ oauth2_regenerate_secret=Αναδημιουργία Μυστικού oauth2_regenerate_secret_hint=Χάσατε το μυστικό σας; oauth2_application_edit=Επεξεργασία oauth2_application_create_description=Οι εφαρμογές OAuth2 δίνει πρόσβαση στην εξωτερική εφαρμογή σας σε λογαριασμούς χρηστών σε αυτή την υπηρεσία. - authorized_oauth2_applications=Εξουσιοδοτημένες Εφαρμογές OAuth2 revoke_key=Ανάκληση revoke_oauth2_grant=Ανάκληση Πρόσβασης revoke_oauth2_grant_description=Η ανάκληση πρόσβασης για αυτή την εξωτερική εφαρμογή θα αποτρέψει αυτή την εφαρμογή από την πρόσβαση στα δεδομένα σας. Σίγουρα; - twofa_desc=Ο έλεγχος ταυτότητας δύο παραγόντων ενισχύει την ασφάλεια του λογαριασμού σας. twofa_is_enrolled=Ο λογαριασμός σας είναι εγγεγραμμένος σε έλεγχο ταυτότητας δύο παραγόντων. twofa_not_enrolled=Ο λογαριασμός σας δεν είναι εγγεγραμμένος σε έλεγχο ταυτότητας δύο παραγόντων. @@ -835,13 +784,11 @@ then_enter_passcode=Και εισάγετε τον κωδικό που εμφα passcode_invalid=Ο κωδικός είναι λάθος. Δοκιμάστε ξανά. twofa_enrolled=Ο λογαριασμός σας έχει εγγραφεί σε ταυτοποίηση δύο παραγόντων. Αποθηκεύστε το διακριτικό μιας χρήσης (%s) σε ασφαλές μέρος καθώς εμφανίζεται μόνο μία φορά! twofa_failed_get_secret=Αποτυχία λήψης μυστικού. - webauthn_desc=Τα κλειδιά ασφαλείας είναι συσκευές που περιέχουν κρυπτογραφικά κλειδιά. Μπορούν να χρησιμοποιηθούν για έλεγχο ταυτότητας δύο παραγόντων. Τα κλειδιά ασφαλείας πρέπει να υποστηρίζουν το πρότυπο WebAuthn Authn Authenticator. webauthn_register_key=Προσθήκη Κλειδιού Ασφαλείας webauthn_nickname=Ψευδώνυμο webauthn_delete_key=Αφαίρεση Κλειδιού Ασφαλείας webauthn_delete_key_desc=Αν αφαιρέσετε ένα κλειδί ασφαλείας δεν μπορείτε πλέον να συνδεθείτε με αυτό. Συνέχεια; - manage_account_links=Διαχείριση Συνδεδεμένων Λογαριασμών manage_account_links_desc=Αυτοί οι εξωτερικοί λογαριασμοί είναι συνδεδεμένοι στον Gitea λογαριασμό σας. account_links_not_available=Προς το παρόν δεν υπάρχουν εξωτερικοί λογαριασμοί συνδεδεμένοι με τον λογαριασμό σας στο Gitea. @@ -849,23 +796,18 @@ link_account=Σύνδεση Λογαριασμού remove_account_link=Αφαίρεση Συνδεδεμένου Λογαριασμού remove_account_link_desc=Η κατάργηση ενός συνδεδεμένου λογαριασμού θα ανακαλέσει την πρόσβασή του στο λογαριασμό σας στο Gitea. Συνέχεια; remove_account_link_success=Ο συνδεδεμένος λογαριασμός έχει αφαιρεθεί. - - orgs_none=Δεν είστε μέλος σε κάποιο οργανισμό. - delete_account=Διαγραφή Του Λογαριασμού Σας delete_prompt=Αυτή η ενέργεια θα διαγράψει μόνιμα το λογαριασμό σας. ΔΕΝ ΘΑ ΜΠΟΡΕΙ να επανέλθει. delete_with_all_comments=Ο λογαριασμός σας είναι νεότερος από %s. Για να αποφύγετε τα σχόλια φαντάσματα, όλα τα σχόλια σε ζητήματα/PR θα διαγραφούν από αυτόν. confirm_delete_account=Επιβεβαίωση Διαγραφής delete_account_title=Διαγραφή Λογαριασμού Χρήστη delete_account_desc=Είστε βέβαιοι ότι θέλετε να διαγράψετε μόνιμα αυτό τον λογαριασμό χρήστη; - email_notifications.enable=Ενεργοποίηση Ειδοποιήσεων Μέσω Email email_notifications.onmention=Email Μόνο κατά την Αναφορά email_notifications.disable=Απενεργοποίηση Ειδοποιήσεων μέσω Email email_notifications.submit=Ορισμός Προτιμότερου Email email_notifications.andyourown=Και Τις Δικές Σας Ειδοποιήσεις - visibility=Ορατότητα χρήστη visibility.public=Δημόσια visibility.public_tooltip=Ορατό σε όλους @@ -954,12 +896,10 @@ delete_preexisting_content=Διαγραφή αρχείων στο %s delete_preexisting_success=Διαγράφηκαν τα μη υιοθετημένα αρχεία στο %s blame_prior=Προβολή ευθύνης πριν από αυτή την αλλαγή author_search_tooltip=Εμφάνιση το πολύ 30 χρηστών - transfer.accept=Αποδοχή Μεταφοράς -transfer.accept_desc=`Μεταφορά στο "%s"` +transfer.accept_desc=Μεταφορά στο "%s" transfer.reject=Απόρριψη Μεταφοράς -transfer.reject_desc=`Ακύρωση μεταφοράς σε "%s"` - +transfer.reject_desc=Ακύρωση μεταφοράς σε "%s" desc.private=Ιδιωτικό desc.public=Δημόσιο desc.private_template=Ιδιωτικό πρότυπο @@ -967,7 +907,6 @@ desc.public_template=Πρότυπο desc.internal=Εσωτερικό desc.internal_template=Εσωτερικό πρότυπο desc.archived=Αρχειοθετημένο - template.items=Αντικείμενα Προτύπου template.git_content=Περιεχόμενο Git (Προεπιλεγμένος Κλάδος) template.git_hooks=Git Hooks @@ -978,15 +917,12 @@ template.avatar=Εικόνα template.issue_labels=Σήματα Ζητήματος template.one_item=Πρέπει να επιλέξετε τουλάχιστον ένα αντικείμενο στο πρότυπο template.invalid=Πρέπει να επιλέξετε ένα πρότυπο αποθετήριο - archive.issue.nocomment=Αυτό το αποθετήριο αρχειοθετήθηκε. Δεν μπορείτε να σχολιάσετε σε ζητήματα. archive.pull.nocomment=Αυτό το repo αρχειοθετήθηκε. Δεν μπορείτε να σχολιάσετε στα pull requests. - form.reach_limit_of_creation_1=Έχετε ήδη συμπληρώσει το όριο του %d αποθετηρίου. form.reach_limit_of_creation_n=Έχετε ήδη συμπληρώσει το όριο των %d αποθετηρίων. form.name_reserved=Το όνομα αποθετηρίου "%s" είναι δεσμευμένο. form.name_pattern_not_allowed=Το μοτίβο "%s" δεν επιτρέπεται στο όνομα του αποθετηρίου. - need_auth=Εξουσιοδότηση migrate_options=Επιλογές Μεταφοράς migrate_service=Υπηρεσία Μεταφοράς @@ -1038,7 +974,6 @@ migrate.migrating_issues=Μετανάστευση Θεμάτων migrate.migrating_pulls=Μεταφορά Pull Requests migrate.cancel_migrating_title=Ακύρωση Μεταφοράς migrate.cancel_migrating_confirm=Θέλετε να ακυρώσετε αυτή τη μεταφορά; - mirror_from=είδωλο του forked_from=forked από generated_from=παράγονται από @@ -1053,7 +988,6 @@ star=Αστέρι fork=Fork download_archive=Λήψη Αποθετηρίου more_operations=Περισσότερες Λειτουργίες - no_desc=Χωρίς Περιγραφή quick_guide=Γρήγορος Οδηγός clone_this_repo=Κλωνοποίηση αυτού του αποθετηρίου @@ -1062,12 +996,11 @@ create_new_repo_command=Δημιουργία νέου αποθετηρίου σ push_exist_repo=Προώθηση ενός υπάρχοντος αποθετηρίου από τη γραμμή εντολών empty_message=Αυτό το αποθετήριο δεν περιέχει τίποτα. broken_message=Τα δεδομένα Git που διέπουν αυτό το αποθετήριο δεν μπορούν να διαβαστούν. Επικοινωνήστε με το διαχειριστή ή διαγράψτε αυτό το αποθετήριο. - code=Κώδικας code.desc=Πρόσβαση στον πηγαίο κώδικα, αρχεία, υποβολές και κλάδους. branch=Κλάδος tree=Δέντρο -clear_ref=`Καθαρισμός τρέχουσας αναφοράς` +clear_ref=Καθαρισμός τρέχουσας αναφοράς filter_branch_and_tag=Φίλτρο κλάδου ή ετικέτας find_tag=Εύρεση ετικέτας branches=Κλάδοι @@ -1080,7 +1013,6 @@ actions=Δράσεις labels=Σήματα org_labels_desc=Τα σήματα στο επίπεδο οργανισμού, που μπορούν να χρησιμοποιηθούν με όλα τα αποθετήρια κάτω από αυτόν τον οργανισμό org_labels_desc_manage=διαχείριση - milestones=Ορόσημα commits=Υποβολές commit=Υποβολή @@ -1097,10 +1029,9 @@ file_view_rendered=Προβολή Απόδοσης file_view_raw=Προβολή Ακατέργαστου file_permalink=Permalink file_too_large=Το αρχείο είναι πολύ μεγάλο για να εμφανιστεί. -invisible_runes_line=`Αυτή η γραμμή έχει αόρατους χαρακτήρες unicode ` -ambiguous_runes_line=`Αυτή η γραμμή έχει ασαφείς χαρακτήρες unicode ` -ambiguous_character=`ο %[1]c [U+%04[1]X] μπορεί να μπερδευτεί με τον %[2]c [U+%04[2]X]` - +invisible_runes_line="Αυτή η γραμμή έχει αόρατους χαρακτήρες unicode " +ambiguous_runes_line="Αυτή η γραμμή έχει ασαφείς χαρακτήρες unicode " +ambiguous_character=ο %[1]c [U+%04[1]X] μπορεί να μπερδευτεί με τον %[2]c [U+%04[2]X] escape_control_characters=Escape unescape_control_characters=Unescape file_copy_permalink=Αντιγραφή Permalink @@ -1120,7 +1051,6 @@ normal_view=Κανονική Προβολή line=γραμμή lines=γραμμές from_comment=(σχόλιο) - editor.add_file=Προσθήκη Αρχείου editor.new_file=Νέο Αρχείο editor.upload_file=Ανέβασμα Αρχείου @@ -1147,7 +1077,7 @@ editor.update=Ενημέρωση %s editor.delete=Διαγραφή %s editor.patch=Εφαρμογή Διόρθωσης editor.patching=Επιδιόρθωση: -editor.fail_to_apply_patch=`Αδυναμία εφαρμογής της επιδιόρθωσης "%s"` +editor.fail_to_apply_patch=Αδυναμία εφαρμογής της επιδιόρθωσης "%s" editor.new_patch=Νέα Διόρθωση editor.commit_message_desc=Προσθήκη προαιρετικής εκτενούς περιγραφής… editor.signoff_desc=Προσθέστε ένα πρόσθετο Signed-off-by στο τέλος του μηνύματος καταγραφής της υποβολής. @@ -1163,7 +1093,7 @@ editor.filename_is_invalid=Το όνομα αρχείου δεν είναι έγ editor.branch_does_not_exist=Ο κλάδος "%s" δεν υπάρχει σε αυτό το αποθετήριο. editor.branch_already_exists=Ο κλάδος "%s" υπάρχει ήδη σε αυτό το αποθετήριο. editor.directory_is_a_file=Το όνομα φακέλου "%s" χρησιμοποιείται ήδη ως όνομα αρχείου σε αυτό το αποθετήριο. -editor.file_is_a_symlink=`Το "%s" είναι συμβολικός σύνδεσμος. Οι συμβολικοί σύνδεσμοι δεν μπορούν να επεξεργαστούν στην ενσωματωμένη εφαρμογή` +editor.file_is_a_symlink=Το "%s" είναι συμβολικός σύνδεσμος. Οι συμβολικοί σύνδεσμοι δεν μπορούν να επεξεργαστούν στην ενσωματωμένη εφαρμογή editor.filename_is_a_directory=Το όνομα αρχείου "%s" χρησιμοποιείται ήδη ως όνομα φακέλου σε αυτό το αποθετήριο. editor.file_editing_no_longer_exists=Το αρχείο "%s" που επεξεργάζεται, δεν υπάρχει πλέον σε αυτό το αποθετήριο. editor.file_deleting_no_longer_exists=Το αρχείο "%s" που διαγράφεται, δεν υπάρχει πλέον σε αυτό το αποθετήριο. @@ -1180,14 +1110,13 @@ editor.push_rejected_summary=Μήνυμα Πλήρους Απόρριψης: editor.add_subdir=Προσθήκη φακέλου… editor.unable_to_upload_files=Αποτυχία αποστολής αρχείων στο "%s" με το σφάλμα: %v editor.upload_file_is_locked=Το αρχείο "%s" είναι κλειδωμένο από %s. -editor.upload_files_to_dir=`Μεταφόρτωση αρχείων στο "%s"` +editor.upload_files_to_dir=Μεταφόρτωση αρχείων στο "%s" editor.cannot_commit_to_protected_branch=Δεν είναι δυνατή η υποβολή στον προστατευόμενο κλάδο "%s". editor.no_commit_to_branch=Δεν είναι δυνατή η απευθείας υποβολή στο κλάδο επειδή: editor.user_no_push_to_branch=Ο χρήστης δεν μπορεί να κάνει push στο κλάδο editor.require_signed_commit=Ο κλάδος απαιτεί υπογεγραμμένη υποβολή editor.cherry_pick=Ανθολόγηση (cherry-pic) του %s στο: editor.revert=Απόσυρση του %s στο: - commits.desc=Δείτε το ιστορικό αλλαγών του πηγαίου κώδικα. commits.commits=Υποβολές commits.no_commits=Δεν υπάρχουν κοινές υποβολές. Τα "%s" και "%s" έχουν εντελώς διαφορετικές ιστορίες. @@ -1206,7 +1135,6 @@ commits.signed_by_untrusted_user=Υπογράφηκε από μη έμπιστο commits.signed_by_untrusted_user_unmatched=Υπογράφηκε από ένα μη έμπιστο χρήστη ο οποίος δεν ταιριάζει με τον υποβολέα commits.gpg_key_id=ID Κλειδιού GPG commits.ssh_key_fingerprint=Αποτύπωμα Κλειδιού SSH - commit.operations=Λειτουργίες commit.revert=Απόσυρση commit.revert-header=Απόσυρση: %s @@ -1214,15 +1142,12 @@ commit.revert-content=Επιλέξτε κλάδο για απόσυρση σε commit.cherry-pick=Cherry-pick commit.cherry-pick-header=Ανθολόγηση: %s commit.cherry-pick-content=Επιλέξτε κλάδο για να κάνετε ανθολόγηση σε αυτό: - commitstatus.error=Σφάλμα commitstatus.failure=Αποτυχία commitstatus.pending=Εκκρεμεί commitstatus.success=Επιτυχές - ext_issues=Πρόσβαση στα Εξωτερικά Ζητήματα ext_issues.desc=Σύνδεση σε εξωτερικό εφαρμογή ζητημάτων. - projects=Έργα projects.desc=Διαχείριση ζητημάτων και pulls στους πίνακες των έργων. projects.description=Περιγραφή (προαιρετικό) @@ -1263,7 +1188,6 @@ projects.column.assigned_to=Ανατέθηκε σε projects.card_type.desc=Προεπισκοπήσεις Καρτών projects.card_type.images_and_text=Εικόνες και Κείμενο projects.card_type.text_only=Μόνο Κείμενο - issues.desc=Οργανώστε αναφορές σφαλμάτων, εργασίες και ορόσημα. issues.filter_assignees=Φίλτρο Αποδέκτη issues.filter_milestones=Φίλτρο Ορόσημου @@ -1313,25 +1237,25 @@ issues.add_labels=πρόσθεσε τα σήματα %s %s issues.remove_label=αφαίρεσε το σήμα %s %s issues.remove_labels=αφαίρεσε τα %s σήματα %s issues.add_remove_labels=πρόσθεσε τα %s και αφαίρεσε τα %s σήματα %s -issues.add_milestone_at=`το πρόσθεσε στο %s ορόσημο %s` -issues.add_project_at=`το πρόσθεσε στο έργο %s %s` -issues.change_milestone_at=`τροποποίησε το ορόσημο από %s σε %s %s` -issues.change_project_at=`τροποποίησε το έργο από %s σε %s %s` -issues.remove_milestone_at=`το αφαίρεσε από το %s ορόσημο %s` -issues.remove_project_at=`το αφαίρεσε από το %s έργο %s` -issues.deleted_milestone=`(διαγράφηκε)` -issues.deleted_project=`(διαγράφηκε)` -issues.self_assign_at=`ανέθεσε στον εαυτό του το %s` -issues.add_assignee_at=`ανατέθηκε από %s %s` -issues.remove_assignee_at=`αφαιρέθηκε η ανάθεση από %s %s` -issues.remove_self_assignment=`αφαίρεσαν την ανάθεση τους %s` -issues.change_title_at=`άλλαξε το τίτλο από %s σε %s %s` -issues.change_ref_at=`άλλαξε την αναφορά από %s σε %s %s` -issues.remove_ref_at=`αφαίρεση την αναφορά %s %s` -issues.add_ref_at=`πρόσθεσε την αναφορά %s %s` -issues.delete_branch_at=`διέγραψε το κλάδο %s %s` +issues.add_milestone_at=το πρόσθεσε στο %s ορόσημο %s +issues.add_project_at=το πρόσθεσε στο έργο %s %s +issues.change_milestone_at=τροποποίησε το ορόσημο από %s σε %s %s +issues.change_project_at=τροποποίησε το έργο από %s σε %s %s +issues.remove_milestone_at=το αφαίρεσε από το %s ορόσημο %s +issues.remove_project_at=το αφαίρεσε από το %s έργο %s +issues.deleted_milestone=(διαγράφηκε) +issues.deleted_project=(διαγράφηκε) +issues.self_assign_at=ανέθεσε στον εαυτό του το %s +issues.add_assignee_at=ανατέθηκε από %s %s +issues.remove_assignee_at=αφαιρέθηκε η ανάθεση από %s %s +issues.remove_self_assignment=αφαίρεσαν την ανάθεση τους %s +issues.change_title_at=άλλαξε το τίτλο από %s σε %s %s +issues.change_ref_at=άλλαξε την αναφορά από %s σε %s %s +issues.remove_ref_at=αφαίρεση την αναφορά %s %s +issues.add_ref_at=πρόσθεσε την αναφορά %s %s +issues.delete_branch_at=διέγραψε το κλάδο %s %s issues.filter_label=Σήμα -issues.filter_label_exclude=`Χρησιμοποιήστε alt + κάντε κλικ/Enter για να εξαιρέσετε τις σημάνσεις` +issues.filter_label_exclude=Χρησιμοποιήστε alt + κάντε κλικ/Enter για να εξαιρέσετε τις σημάνσεις issues.filter_label_no_select=Όλα τα σήματα issues.filter_milestone=Ορόσημο issues.filter_milestone_all=Όλα τα ορόσημα @@ -1387,7 +1311,7 @@ issues.open_title=Ανοιχτά issues.closed_title=Κλειστά issues.draft_title=Προσχέδιο issues.num_comments=%d σχόλια -issues.commented_at=`σχολίασε %s` +issues.commented_at=σχολίασε %s issues.delete_comment_confirm=Θέλετε σίγουρα να διαγράψετε αυτό το σχόλιο; issues.context.copy_link=Αντιγραφή Συνδέσμου issues.context.quote_reply=Παράθεση Απάντησης @@ -1401,16 +1325,16 @@ issues.close_comment_issue=Σχόλιο και κλείσιμο issues.reopen_issue=Ανοίξτε ξανά issues.reopen_comment_issue=Σχόλιο και Άνοιγμα ξανά issues.create_comment=Προσθήκη Σχολίου -issues.closed_at=`αυτό το ζήτημα έκλεισε %[2]s` -issues.reopened_at=`ξανά άνοιξε αυτό το ζήτημα %[2]s` -issues.commit_ref_at=`αναφορά σε αυτό το ζήτημα από την παραπομπή %[2]s` -issues.ref_issue_from=`αναφέρθηκε σε αυτό το ζήτημα %[4]s %[2]s` -issues.ref_pull_from=`αναφέρθηκε σε αυτό το pull request %[4]s %[2]s` -issues.ref_closing_from=`αναφέρθηκε σε ένα pull request %[4]s που θα κλείσει αυτό το ζήτημα %[2]s` -issues.ref_reopening_from=`αναφέρθηκε σε ένα pull request %[4]s που θα ανοίξει ξανά αυτό το ζήτημα %[2]s` -issues.ref_closed_from=`έκλεισε αυτό το ζήτημα %[4]s %[2]s` -issues.ref_reopened_from=`άνοιξε ξανά αυτό το ζήτημα %[4]s %[2]s` -issues.ref_from=`από %[1]s` +issues.closed_at=αυτό το ζήτημα έκλεισε %[2]s +issues.reopened_at=ξανά άνοιξε αυτό το ζήτημα %[2]s +issues.commit_ref_at=αναφορά σε αυτό το ζήτημα από την παραπομπή %[2]s +issues.ref_issue_from=αναφέρθηκε σε αυτό το ζήτημα %[4]s %[2]s +issues.ref_pull_from=αναφέρθηκε σε αυτό το pull request %[4]s %[2]s +issues.ref_closing_from=αναφέρθηκε σε ένα pull request %[4]s που θα κλείσει αυτό το ζήτημα %[2]s +issues.ref_reopening_from=αναφέρθηκε σε ένα pull request %[4]s που θα ανοίξει ξανά αυτό το ζήτημα %[2]s +issues.ref_closed_from=έκλεισε αυτό το ζήτημα %[4]s %[2]s +issues.ref_reopened_from=άνοιξε ξανά αυτό το ζήτημα %[4]s %[2]s +issues.ref_from=από %[1]s issues.author=Συγγραφέας issues.role.owner=Ιδιοκτήτης issues.role.member=Μέλος @@ -1443,8 +1367,8 @@ issues.label.filter_sort.reverse_alphabetically=Αντίστροφα αλφαβ issues.label.filter_sort.by_size=Μικρότερο μέγεθος issues.label.filter_sort.reverse_by_size=Μεγαλύτερο μέγεθος issues.num_participants=%d Συμμετέχοντες -issues.attachment.open_tab=`Κάντε κλικ για να δείτε το "%s" σε μια νέα καρτέλα` -issues.attachment.download=`Κάντε κλικ για να λάβετε το "%s"` +issues.attachment.open_tab=Κάντε κλικ για να δείτε το "%s" σε μια νέα καρτέλα +issues.attachment.download=Κάντε κλικ για να λάβετε το "%s" issues.subscribe=Εγγραφή issues.unsubscribe=Διαγραφή issues.unpin_issue=Άφεση Ζητήματος @@ -1476,30 +1400,30 @@ issues.delete.text=Θέλετε πραγματικά να διαγράψετε issues.tracker=Καταγραφή Χρόνου issues.start_tracking_short=Εκκίνηση Χρονομέτρου issues.start_tracking=Εκκίνηση Καταγραφής Χρόνου -issues.start_tracking_history=`ξεκίνησε να εργάζεται %s` +issues.start_tracking_history=ξεκίνησε να εργάζεται %s issues.tracker_auto_close=Το χρονόμετρο θα σταματήσει αυτόματα όταν κλείσει αυτό το ζήτημα -issues.tracking_already_started=`Έχετε ήδη ξεκινήσει την καταγραφή του χρόνου σε ένα άλλο ζήτημα!` +issues.tracking_already_started=Έχετε ήδη ξεκινήσει την καταγραφή του χρόνου σε ένα άλλο ζήτημα! issues.stop_tracking=Διακοπή Χρονομέτρου -issues.stop_tracking_history=`σταμάτησε να εργάζεται %s` +issues.stop_tracking_history=σταμάτησε να εργάζεται %s issues.cancel_tracking=Απόρριψη issues.add_time=Χειροκίνητη Προσθήκη Ώρας issues.del_time=Διαγραφή αυτού του αρχείου χρόνου issues.add_time_short=Προσθήκη Χρόνου issues.add_time_cancel=Ακύρωση -issues.add_time_history=`πρόσθεσε χρόνο που δαπανήθηκε %s` -issues.del_time_history=`διέγραψε το χρόνο που δαπανήθηκε %s` +issues.add_time_history=πρόσθεσε χρόνο που δαπανήθηκε %s +issues.del_time_history=διέγραψε το χρόνο που δαπανήθηκε %s issues.add_time_hours=Ώρες issues.add_time_minutes=Λεπτά issues.add_time_sum_to_small=Δεν εισήχθη χρόνος. issues.time_spent_total=Συνολική Δαπάνη Χρόνου -issues.time_spent_from_all_authors=`Συνολική Δαπάνη Χρόνου: %s` +issues.time_spent_from_all_authors=Συνολική Δαπάνη Χρόνου: %s issues.due_date=Ημερομηνία Παράδοσης issues.invalid_due_date_format=Η μορφή της ημερομηνίας παράδοσης πρέπει να είναι 'yyyy-mm-dd'. issues.error_modifying_due_date=Αποτυχία τροποποίησης της ημερομηνίας παράδοσης. issues.error_removing_due_date=Αποτυχία κατάργησης της ημερομηνίας παράδοσης. issues.push_commit_1=πρόσθεσε %d υποβολή %s issues.push_commits_n=πρόσθεσε %d υποβολές %s -issues.force_push_codes=`force-pushed %[1]s από το %[2]s στο %[4]s %[6]s` +issues.force_push_codes=force-pushed %[1]s από το %[2]s στο %[4]s %[6]s issues.force_push_compare=Σύγκριση issues.due_date_form=εεεε-μμ-ηη issues.due_date_form_add=Προσθήκη ημερομηνίας παράδοσης @@ -1521,8 +1445,8 @@ issues.dependency.add=Προσθήκη εξάρτησης… issues.dependency.cancel=Ακύρωση issues.dependency.remove=Διαγραφή issues.dependency.remove_info=Αφαίρεση αυτής της εξάρτησης -issues.dependency.added_dependency=`πρόσθεσε μια νέα εξάρτηση %s` -issues.dependency.removed_dependency=`αφαίρεσε μια εξάρτηση %s` +issues.dependency.added_dependency=πρόσθεσε μια νέα εξάρτηση %s +issues.dependency.removed_dependency=αφαίρεσε μια εξάρτηση %s issues.dependency.pr_closing_blockedby=Το κλείσιμο αυτού pull request εμποδίζεται από τα ακόλουθα ζητήματα issues.dependency.issue_closing_blockedby=Το κλείσιμο αυτού του ζητήματος εμποδίζεται από τα ακόλουθα ζητήματα issues.dependency.issue_close_blocks=Αυτό το ζήτημα εμποδίζει το κλείσιμο των ακόλουθων ζητημάτων @@ -1576,10 +1500,8 @@ issues.content_history.delete_from_history=Διαγραφή από το ιστο issues.content_history.delete_from_history_confirm=Διαγραφή από το ιστορικό; issues.content_history.options=Επιλογές issues.reference_link=Αναφορά: %s - compare.compare_base=βάση compare.compare_head=σύγκριση - pulls.desc=Ενεργοποίηση των pull requests και της αξιολόγησης κώδικα. pulls.new=Νέο Pull Request pulls.view=Προβολή Pull Request @@ -1601,11 +1523,11 @@ pulls.filter_branch=Φιλτράρισμα κλάδου pulls.no_results=Δεν βρέθηκαν αποτελέσματα. pulls.nothing_to_compare=Αυτοί οι κλάδοι είναι όμοιοι. Δεν υπάρχει ανάγκη να δημιουργήσετε ένα pull request. pulls.nothing_to_compare_and_allow_empty_pr=Αυτοί οι κλάδοι είναι ίσοι. Αυτό το PR θα είναι κενό. -pulls.has_pull_request=`Υπάρχει ήδη pull request μεταξύ αυτών των κλάδων: %[2]s#%[3]d` +pulls.has_pull_request=Υπάρχει ήδη pull request μεταξύ αυτών των κλάδων: %[2]s#%[3]d pulls.create=Δημιουργία Pull Request pulls.title_desc=θέλει να συγχωνεύσει %[1]d υποβολές από %[2]s σε %[3]s pulls.merged_title_desc=συγχώνευσε %[1]d υποβολές από %[2]s σε %[3]s %[4]s -pulls.change_target_branch_at=`άλλαξε τον κλάδο στόχο από %s σε %s %s` +pulls.change_target_branch_at=άλλαξε τον κλάδο στόχο από %s σε %s %s pulls.tab_conversation=Συζήτηση pulls.tab_commits=Υποβολές pulls.tab_files=Αρχεία Με Αλλαγές @@ -1617,7 +1539,7 @@ pulls.closed=Το pull request έκλεισε pulls.manually_merged=Συγχωνεύτηκαν χειροκίνητα pulls.merged_info_text=Ο κλάδος %s μπορεί τώρα να διαγραφεί. pulls.is_closed=Το pull request έχει κλείσει. -pulls.title_wip_desc=`Ξεκινήστε τον τίτλο με %s για να αποτρέψετε την τυχαία συγχώνευση του pull request.` +pulls.title_wip_desc=Ξεκινήστε τον τίτλο με %s για να αποτρέψετε την τυχαία συγχώνευση του pull request. pulls.cannot_merge_work_in_progress=Αυτό το pull request επισημαίνεται ως μια εργασία σε εξέλιξη. pulls.still_in_progress=Ακόμα είναι σε εξέλιξη; pulls.add_prefix=Προσθήκη %s προθέματος @@ -1642,7 +1564,6 @@ pulls.reject_count_n=%d αιτήματα αλλαγής pulls.waiting_count_1=%d αναμονή αναθεώρησης pulls.waiting_count_n=%d αναμονή αναθεωρήσεων pulls.wrong_commit_id=Το id υποβολής πρέπει να είναι ένα id υποβολής στον κλάδο προορισμού - pulls.no_merge_desc=Αυτό το pull request δεν μπορεί να συγχωνευθεί επειδή όλες οι επιλογές συγχώνευσης αποθετηρίων είναι απενεργοποιημένες. pulls.no_merge_helper=Ενεργοποιήστε τις επιλογές συγχώνευσης στις ρυθμίσεις αποθετηρίου ή συγχωνεύστε το pull request χειροκίνητα. pulls.no_merge_wip=Αυτό το pull request δεν μπορεί να συγχωνευθεί, επειδή έχει επισημανθεί ως μια εργασία σε εξέλιξη. @@ -1655,7 +1576,6 @@ pulls.squash_merge_pull_request=Δημιουργία υποβολής squash pulls.merge_manually=Συγχωνεύτηκαν χειροκίνητα pulls.merge_commit_id=Το ID της υποβολής συγχώνευσης pulls.require_signed_wont_sign=Ο κλάδος απαιτεί υπογεγραμμένες υποβολές αλλά αυτή η συγχώνευση δεν θα υπογραφεί - pulls.invalid_merge_option=Δεν μπορείτε να χρησιμοποιήσετε αυτήν την επιλογή συγχώνευσης για αυτό το pull request. pulls.merge_conflict=Η Συγχώνευση Απέτυχε: Υπήρξε μια διένεξη κατά τη συγχώνευση. Υπόδειξη: Δοκιμάστε μια διαφορετική στρατηγική pulls.merge_conflict_summary=Μήνυμα Σφάλματος @@ -1667,7 +1587,7 @@ pulls.head_out_of_date=Η συγχώνευση απέτυχε: Κατά τη δ pulls.push_rejected=Η συγχώνευση απέτυχε: Η ώθηση απορρίφθηκε. Ελέγξτε τα Άγκιστρα Git για αυτό το αποθετήριο. pulls.push_rejected_summary=Μήνυμα Πλήρους Απόρριψης pulls.push_rejected_no_message=H Συγχώνευση Aπέτυχε: Η ώθηση απορρίφθηκε, αλλά δεν υπήρχε απομακρυσμένο μήνυμα.
Ελέγξτε τα Άγκιστρα Git για αυτό το αποθετήριο -pulls.open_unmerged_pull_exists=`Δεν μπορείτε να ανοίξετε εκ νέου, επειδή υπάρχει ένα εκκρεμές pull request (#%d) με πανομοιότυπες ιδιότητες.` +pulls.open_unmerged_pull_exists=Δεν μπορείτε να ανοίξετε εκ νέου, επειδή υπάρχει ένα εκκρεμές pull request (#%d) με πανομοιότυπες ιδιότητες. pulls.status_checking=Μερικοί έλεγχοι εκκρεμούν pulls.status_checks_success=Όλοι οι έλεγχοι ήταν επιτυχείς pulls.status_checks_warning=Ορισμένοι έλεγχοι ανέφεραν προειδοποιήσεις @@ -1681,31 +1601,24 @@ pulls.update_branch_success=Η ενημέρωση του κλάδου ήταν pulls.update_not_allowed=Δεν επιτρέπεται να ενημερώσετε τον κλάδο pulls.outdated_with_base_branch=Αυτός ο κλάδος δεν είναι ενημερωμένος με τον βασικό κλάδο pulls.close=Κλείσιμο Pull Request -pulls.closed_at=`έκλεισε αυτό το pull request %[2]s` -pulls.reopened_at=`άνοιξε ξανά αυτό το pull request %[2]s` -pulls.merge_instruction_hint=`Μπορείτε επίσης να δείτε τις οδηγίες της γραμμής εντολών.` +pulls.closed_at=έκλεισε αυτό το pull request %[2]s +pulls.reopened_at=άνοιξε ξανά αυτό το pull request %[2]s +pulls.merge_instruction_hint=Μπορείτε επίσης να δείτε τις οδηγίες της γραμμής εντολών. pulls.merge_instruction_step1_desc=Από το αποθετήριο του έργου σας, ελέγξτε έναν νέο κλάδο και τεστάρετε τις αλλαγές. pulls.merge_instruction_step2_desc=Συγχώνευσε τις αλλαγές και ενημέρωσε στο Gitea. pulls.clear_merge_message=Εκκαθάριση μηνύματος συγχώνευσης pulls.clear_merge_message_hint=Η εκκαθάριση του μηνύματος συγχώνευσης θα αφαιρέσει μόνο το περιεχόμενο του μηνύματος υποβολής και θα διατηρήσει τα παραγόμενα git trailers όπως "Co-Authored-By …". - pulls.auto_merge_button_when_succeed=(Όταν οι έλεγχοι πετύχουν) pulls.auto_merge_when_succeed=Αυτόματη συγχώνευση όταν όλοι οι έλεγχοι πετύχουν pulls.auto_merge_newly_scheduled=Το pull request προγραμματίστηκε για συγχώνευση όταν όλοι οι έλεγχοι πετύχουν. pulls.auto_merge_has_pending_schedule=%[1]s προγραμμάτισε αυτό το pull request για αυτόματη συγχώνευση όταν όλοι οι έλεγχοι πετύχουν %[2]s. - pulls.auto_merge_cancel_schedule=Ακύρωση αυτόματης συγχώνευσης pulls.auto_merge_not_scheduled=Αυτό το pull request δεν έχει προγραμματιστεί να συγχωνευτεί αυτόματα. pulls.auto_merge_canceled_schedule=Η αυτόματη συγχώνευση ακυρώθηκε για αυτό το pull request. - -pulls.auto_merge_newly_scheduled_comment=`προγραμμάτισε αυτό το PR να συγχωνευτεί αυτόματα όταν όλοι οι έλεγχοι πετύχουν %[1]s` -pulls.auto_merge_canceled_schedule_comment=`ακύρωσε την αυτόματη συγχώνευση αυτού του pull request όταν όλοι οι έλεγχοι πετύχουν %[1]s` - +pulls.auto_merge_newly_scheduled_comment=προγραμμάτισε αυτό το PR να συγχωνευτεί αυτόματα όταν όλοι οι έλεγχοι πετύχουν %[1]s +pulls.auto_merge_canceled_schedule_comment=ακύρωσε την αυτόματη συγχώνευση αυτού του pull request όταν όλοι οι έλεγχοι πετύχουν %[1]s pulls.delete.title=Διαγραφή αυτού του pull request; pulls.delete.text=Θέλετε πραγματικά να διαγράψετε αυτό το pull request; (Αυτό θα σβήσει οριστικά όλο το περιεχόμενο του. Εξετάστε αν θέλετε να το κλείσετε, αν σκοπεύεται να το αρχειοθετήσετε) - - - milestones.new=Νέο Ορόσημο milestones.closed=Έκλεισε %s milestones.update_ago=Ενημερώθηκε %s @@ -1732,11 +1645,8 @@ milestones.filter_sort.least_complete=Λιγότερο πλήρη milestones.filter_sort.most_complete=Περισσότερο πλήρη milestones.filter_sort.most_issues=Περισσότερα ζητήματα milestones.filter_sort.least_issues=Λιγότερα ζητήματα - - ext_wiki=Πρόσβαση στο Εξωτερικό Wiki ext_wiki.desc=Σύνδεση σε ένα εξωτερικό wiki. - wiki=Wiki wiki.welcome=Καλώς ήρθατε στο Wiki. wiki.welcome_desc=Το wiki σας επιτρέπει να γράψετε και να μοιραστείτε τεκμηρίωση με συνεργάτες. @@ -1763,7 +1673,6 @@ wiki.pages=Σελίδες wiki.last_updated=Τελευταία ενημέρωση %s wiki.page_name_desc=Εισάγετε ένα όνομα για αυτή τη σελίδα Wiki. Μερικά ειδικά ονόματα είναι: 'Home', '_Sidebar' και '_Footer'. wiki.original_git_entry_tooltip=Προβολή του αρχικού αρχείου Git αντί ενός εμφανίσιμου συνδέσμου. - activity=Δραστηριότητα activity.period.filter_label=Περίοδος: activity.period.daily=1 ημέρα @@ -1829,7 +1738,6 @@ activity.git_stats_addition_n=%d προσθήκες activity.git_stats_and_deletions=και activity.git_stats_deletion_1=%d διαγραφή activity.git_stats_deletion_n=%d διαγραφές - search=Αναζήτηση search.search_repo=Αναζήτηση αποθετηρίου search.type.tooltip=Τύπος αναζήτησης @@ -1840,7 +1748,6 @@ search.match.tooltip=Συμπερίληψη μόνο των αποτελεσμά search.results=Αποτελέσματα αναζήτησης για "%s" σε %s search.code_no_results=Δεν βρέθηκε πηγαίος κώδικας που να ταιριάζει με τον όρο αναζήτησης. search.code_search_unavailable=Η αναζήτηση κώδικα δεν είναι διαθέσιμη αυτή τη στιγμή. Παρακαλώ επικοινωνήστε με το διαχειριστή. - settings=Ρυθμίσεις settings.desc=Στις Ρυθμίσεις μπορείτε να διαχειριστείτε τις ρυθμίσεις για το αποθετήριο settings.options=Αποθετήριο @@ -1872,7 +1779,6 @@ settings.mirror_settings.last_update=Τελευταία ενημέρωση settings.mirror_settings.push_mirror.none=Δεν έχουν ρυθμιστεί είδωλα ώθησης settings.mirror_settings.push_mirror.remote_url=URL Απομακρυσμένου Αποθετηρίου Git settings.mirror_settings.push_mirror.add=Προσθήκη Είδωλου Push - settings.sync_mirror=Συγχρονισμός Τώρα settings.mirror_sync_in_progress=Ο συγχρονισμός ειδώλου είναι σε εξέλιξη. Ελέγξτε ξανά σε λίγο. settings.site=Ιστοσελίδα @@ -1949,7 +1855,7 @@ settings.transfer_notices_2=- Θα διατηρήσετε την πρόσβασ settings.transfer_notices_3=- Εάν το αποθετήριο είναι ιδιωτικό και μεταβιβάζεται σε μεμονωμένο χρήστη, αυτή η ενέργεια εξασφαλίζει ότι ο χρήστης έχει τουλάχιστον άδεια ανάγνωσης (και αλλάζει τα δικαιώματα εάν είναι απαραίτητο). settings.transfer_owner=Νέος Ιδιοκτήτης settings.transfer_perform=Εκτέλεση Μεταφοράς -settings.transfer_started=`Αυτό το αποθετήριο έχει επισημανθεί για μεταφορά και αναμένει επιβεβαίωση από το "%s"` +settings.transfer_started=Αυτό το αποθετήριο έχει επισημανθεί για μεταφορά και αναμένει επιβεβαίωση από το "%s" settings.transfer_succeed=Το αποθετήριο έχει μεταφερθεί. settings.signing_settings=Ρυθμίσεις Επαλήθευσης Υπογραφής settings.trust_model=Μοντέλο Εμπιστοσύνης Υπογραφής @@ -2252,7 +2158,6 @@ settings.rename_branch_success=Ο κλάδος %s μετονομάστηκε μ settings.rename_branch_from=παλιό όνομα κλάδου settings.rename_branch_to=νέο όνομα κλάδου settings.rename_branch=Μετονομασία κλάδου - diff.browse_source=Περιήγηση Στο Πηγαίο diff.parent=γονέας diff.commit=υποβολή @@ -2308,7 +2213,6 @@ diff.image.overlay=Επικάλυψη diff.has_escaped=Αυτή η γραμμή έχει κρυφούς χαρακτήρες Unicode diff.show_file_tree=Εμφάνιση δέντρου αρχείων diff.hide_file_tree=Απόκρυψη δέντρου αρχείων - releases.desc=Παρακολούθηση εκδόσεων έργου και λήψεων. release.releases=Κυκλοφορίες release.detail=Λεπτομέρειες κυκλοφορίας @@ -2355,18 +2259,17 @@ release.add_tag_msg=Χρησιμοποιήστε τον τίτλο και το release.add_tag=Δημιουργία Ετικέτας Μόνο release.releases_for=Κυκλοφορίες για %s release.tags_for=Ετικέτες για %s - branch.name=Όνομα Κλάδου branch.search=Αναζήτηση κλάδων branch.already_exists=Ήδη υπάρχει ένας κλάδος με το όνομα "%s". branch.delete_head=Διαγραφή -branch.delete=`Διαγραφή του Κλάδου "%s"` +branch.delete=Διαγραφή του Κλάδου "%s" branch.delete_html=Διαγραφή Κλάδου branch.deletion_success=Ο κλάδος "%s" διαγράφηκε. branch.deletion_failed=Αποτυχία διαγραφής του κλάδου "%s". branch.delete_branch_has_new_commits=Ο κλάδος "%s" δεν μπορεί να διαγραφεί επειδή προστέθηκαν νέες υποβολές μετά τη συγχώνευση. branch.create_branch=Δημιουργία κλάδου %s -branch.create_from=`από το "%s"` +branch.create_from=από το "%s" branch.create_success=Ο κλάδος "%s" δημιουργήθηκε. branch.branch_already_exists=Ο κλάδος "%s" υπάρχει ήδη σε αυτό το αποθετήριο. branch.branch_name_conflict=Το όνομα του κλάδου "%s" συγκρούεται με το ήδη υπάρχον κλάδο "%s". @@ -2376,9 +2279,9 @@ branch.restore_success=Ο κλάδος "%s" επαναφέρθηκε. branch.restore_failed=Αποτυχία επαναφοράς του κλάδου "%s". branch.protected_deletion_failed=Ο κλάδος "%s" προστατεύεται. Δεν μπορεί να διαγραφεί. branch.default_deletion_failed=Ο κλάδος "%s" είναι ο προεπιλεγμένος κλάδος. Δεν μπορεί να διαγραφεί. -branch.restore=`Επαναφορά του Κλάδου "%s"` -branch.download=`Λήψη του Κλάδου "%s"` -branch.rename=`Μετονομασία Κλάδου "%s"` +branch.restore=Επαναφορά του Κλάδου "%s" +branch.download=Λήψη του Κλάδου "%s" +branch.rename=Μετονομασία Κλάδου "%s" branch.included_desc=Αυτός ο κλάδος είναι μέρος του προεπιλεγμένου κλάδου branch.included=Περιλαμβάνεται branch.create_new_branch=Δημιουργία κλάδου από κλάδο: @@ -2388,23 +2291,18 @@ branch.rename_branch_to=Μετονομασία του "%s" σε: branch.confirm_rename_branch=Μετονομασία κλάδου branch.create_branch_operation=Δημιουργία κλάδου branch.new_branch=Δημιουργία νέου κλάδου -branch.new_branch_from=`Δημιουργία νέου κλάδου από το "%s"` +branch.new_branch_from=Δημιουργία νέου κλάδου από το "%s" branch.renamed=Ο κλάδος %s μετονομάστηκε σε %s. - tag.create_tag=Δημιουργία ετικέτας %s tag.create_tag_operation=Δημιουργία ετικέτας tag.confirm_create_tag=Δημιουργία ετικέτας -tag.create_tag_from=`Δημιουργία νέας ετικέτας από το "%s"` - +tag.create_tag_from=Δημιουργία νέας ετικέτας από το "%s" tag.create_success=Η ετικέτα "%s" δημιουργήθηκε. - topic.manage_topics=Διαχείριση Θεμάτων topic.done=Ολοκληρώθηκε topic.count_prompt=Δεν μπορείτε να επιλέξετε περισσότερα από 25 θέματα - find_file.go_to_file=Αναζήτηση αρχείου find_file.no_matching=Δεν ταιριάζει κανένα αρχείο - error.csv.too_large=Δεν είναι δυνατή η απόδοση αυτού του αρχείου επειδή είναι πολύ μεγάλο. error.csv.unexpected=Δεν είναι δυνατή η απόδοση αυτού του αρχείου, επειδή περιέχει έναν μη αναμενόμενο χαρακτήρα στη γραμμή %d και στη στήλη %d. error.csv.invalid_field_count=Δεν είναι δυνατή η απόδοση αυτού του αρχείου, επειδή έχει λάθος αριθμό πεδίων στη γραμμή %d. @@ -2431,11 +2329,9 @@ team_access_desc=Πρόσβαση αποθετηρίου team_permission_desc=Δικαίωμα team_unit_desc=Να επιτρέπεται η Πρόσβαση σε Τμήματα του Αποθετηρίου team_unit_disabled=(Απενεργοποιημένο) - form.name_reserved=Το όνομα οργανισμού "%s" είναι δεσμευμένο. form.name_pattern_not_allowed=Το μοτίβο "%s" δεν επιτρέπεται μέσα σε ένα όνομα οργανισμού. form.create_org_not_allowed=Δεν επιτρέπεται να δημιουργήσετε έναν οργανισμό. - settings=Ρυθμίσεις settings.options=Οργανισμός settings.full_name=Πλήρες Όνομα @@ -2449,7 +2345,6 @@ settings.visibility.limited=Περιορισμένο (ορατό μόνο σε settings.visibility.limited_shortname=Περιορισμένος settings.visibility.private=Ιδιωτικός (ορατός μόνο στα μέλη του οργανισμού) settings.visibility.private_shortname=Ιδιωτικός - settings.update_settings=Ενημέρωση Ρυθμίσεων settings.update_setting_success=Οι ρυθμίσεις του οργανισμού έχουν ενημερωθεί. settings.change_orgname_redirect_prompt=Το παλιό όνομα θα ανακατευθύνει μέχρι να διεκδικηθεί. @@ -2461,9 +2356,7 @@ settings.confirm_delete_account=Επιβεβαίωση Διαγραφής settings.delete_org_title=Διαγραφή Οργανισμού settings.delete_org_desc=Αυτός ο οργανισμός θα διαγραφεί οριστικά. Συνέχεια; settings.hooks_desc=Προσθήκη webhooks που θα ενεργοποιούνται για όλα τα αποθετήρια κάτω από αυτό τον οργανισμό. - settings.labels_desc=Προσθήκη σημάτων που μπορούν να χρησιμοποιηθούν σε ζητήματα για όλα τα αποθετήρια κάτω από αυτό τον οργανισμό. - members.membership_visibility=Ορατότητα Μέλους: members.public=Ορατό members.public_helper=κάνε το κρυφό @@ -2478,7 +2371,6 @@ members.leave=Αποχώρηση members.leave.detail=Αποχώρηση από %s; members.invite_desc=Προσθέστε ένα νέο μέλος στο %s: members.invite_now=Πρόσκληση Τώρα - teams.join=Συμμετοχή teams.leave=Αποχώρηση teams.leave.detail=Αποχώρηση από %s; @@ -2544,7 +2436,6 @@ monitor=Παρακολούθηση first_page=Πρώτο last_page=Τελευταίο total=Σύνολο: %d - dashboard.new_version_hint=Το Gitea %s είναι διαθέσιμο, τώρα εκτελείτε το %s. Ανατρέξτε στο blog για περισσότερες λεπτομέρειες. dashboard.statistic=Περίληψη dashboard.operations=Λειτουργίες Συντήρησης @@ -2623,7 +2514,6 @@ dashboard.gc_lfs=Συλλογή απορριμάτων στα μετα-αντι dashboard.stop_zombie_tasks=Διακοπή εργασιών ζόμπι dashboard.stop_endless_tasks=Διακοπή ατελείωτων εργασιών dashboard.cancel_abandoned_jobs=Ακύρωση εγκαταλελειμμένων εργασιών - users.user_manage_panel=Διαχείριση Λογαριασμών Χρηστών users.new_account=Δημιουργία Λογαριασμού Χρήστη users.name=Όνομα Χρήστη @@ -2677,7 +2567,6 @@ users.list_status_filter.is_prohibit_login=Απαγόρευση Σύνδεσης users.list_status_filter.not_prohibit_login=Επιτρέπεται η Σύνδεση users.list_status_filter.is_2fa_enabled=2FA Ενεργοποιημένο users.list_status_filter.not_2fa_enabled=2FA Απενεργοποιημένο - emails.email_manage_panel=Διαχείριση Email Χρήστη emails.primary=Κύριο emails.activated=Ενεργοποιήθηκε @@ -2690,13 +2579,11 @@ emails.not_updated=Αποτυχία ενημέρωσης της ζητούμεν emails.duplicate_active=Αυτή η διεύθυνση email είναι ήδη ενεργή σε διαφορετικό χρήστη. emails.change_email_header=Ενημέρωση Ιδιοτήτων Email emails.change_email_text=Είστε βέβαιοι ότι θέλετε να ενημερώσετε αυτή τη διεύθυνση email; - orgs.org_manage_panel=Διαχείριση Οργανισμού orgs.name=Όνομα orgs.teams=Ομάδες orgs.members=Μέλη orgs.new_orga=Νέος Οργανισμός - repos.repo_manage_panel=Διαχείριση Αποθετηρίου repos.unadopted=Μη Υιοθετημένα Αποθετήρια repos.unadopted.no_more=Δεν βρέθηκαν μη υιοθετημένα αποθετήρια @@ -2708,7 +2595,6 @@ repos.stars=Αστέρια repos.forks=Forks repos.issues=Ζητήματα repos.size=Μέγεθος - packages.package_manage_panel=Διαχείριση Πακέτων packages.total_size=Συνολικό Μέγεθος: %s packages.unreferenced_size=Μέγεθος Χωρίς Αναφορά: %s @@ -2720,15 +2606,12 @@ packages.type=Τύπος packages.repository=Αποθετήριο packages.size=Μέγεθος packages.published=Δημοσιευμένα - defaulthooks=Προεπιλεγμένα Webhooks defaulthooks.add_webhook=Προσθήκη Προεπιλεγμένου Webhook defaulthooks.update_webhook=Ενημέρωση Προεπιλεγμένου Webhook - systemhooks=Webhooks Συστήματος systemhooks.add_webhook=Προσθήκη Webhook Συστήματος systemhooks.update_webhook=Ενημέρωση Webhook Συστήματος - auths.auth_manage_panel=Διαχείριση ΠηγήςΤαυτοποίησης auths.new=Προσθήκη Πηγής Ταυτοποίησης auths.name=Όνομα @@ -2820,16 +2703,16 @@ auths.tips=Συμβουλές auths.tips.oauth2.general=Ταυτοποίηση OAuth2 auths.tip.oauth2_provider=Πάροχος OAuth2 auths.tip.bitbucket=Καταχωρήστε ένα νέο καταναλωτή OAuth στο https://bitbucket.org/account/user//oauth-consumers/new και προσθέστε το δικαίωμα 'Account' - 'Read' -auths.tip.nextcloud=`Καταχωρήστε ένα νέο καταναλωτή OAuth στην υπηρεσία σας χρησιμοποιώντας το παρακάτω μενού "Settings -> Security -> OAuth 2.0 client"` +auths.tip.nextcloud=Καταχωρήστε ένα νέο καταναλωτή OAuth στην υπηρεσία σας χρησιμοποιώντας το παρακάτω μενού "Settings -> Security -> OAuth 2.0 client" auths.tip.dropbox=Δημιουργήστε μια νέα εφαρμογή στο https://www.dropbox.com/developers/apps -auths.tip.facebook=`Καταχωρήστε μια νέα εφαρμογή στο https://developers.facebook.com/apps και προσθέστε το προϊόν "Facebook Login"` +auths.tip.facebook=Καταχωρήστε μια νέα εφαρμογή στο https://developers.facebook.com/apps και προσθέστε το προϊόν "Facebook Login" auths.tip.github=Καταχωρήστε μια νέα εφαρμογή OAuth στο https://github.com/settings/applications/new auths.tip.gitlab=Καταχωρήστε μια νέα εφαρμογή στο https://gitlab.com/profile/applications auths.tip.google_plus=Αποκτήστε τα διαπιστευτήρια πελάτη OAuth2 από την κονσόλα API της Google στο https://console.developers.google.com/ auths.tip.openid_connect=Χρησιμοποιήστε το OpenID Connect Discovery URL (/.well known/openid-configuration) για να καθορίσετε τα τελικά σημεία auths.tip.twitter=Πηγαίνετε στο https://dev.twitter.com/apps, δημιουργήστε μια εφαρμογή και βεβαιωθείτε ότι η επιλογή “Allow this application to be used to Sign in with Twitter” είναι ενεργοποιημένη auths.tip.discord=Καταχωρήστε μια νέα εφαρμογή στο https://discordapp.com/developers/applications/me -auths.tip.yandex=`Δημιουργήστε μια νέα εφαρμογή στο https://oauth.yandex.com/client/new. Επιλέξτε τα ακόλουθα δικαιώματα από την ενότητα "Yandex.Passport API": "Access to email address", "Access to user avatar" και "Access to username, first name and surname, gender"` +auths.tip.yandex=Δημιουργήστε μια νέα εφαρμογή στο https://oauth.yandex.com/client/new. Επιλέξτε τα ακόλουθα δικαιώματα από την ενότητα "Yandex.Passport API": "Access to email address", "Access to user avatar" και "Access to username, first name and surname, gender" auths.tip.mastodon=Εισαγάγετε ένα προσαρμομένο URL για την υπηρεσία mastodon με την οποία θέλετε να πιστοποιήσετε (ή να χρησιμοποιήσετε την προεπιλεγμένη) auths.edit=Επεξεργασία Πηγής Ταυτοποίησης auths.activated=Αυτή η Πηγή Ταυτοποίησης είναι Ενεργοποιημένη @@ -2845,7 +2728,6 @@ auths.login_source_exist=Υπάρχει ήδη η πηγή ταυτοποίησ auths.login_source_of_type_exist=Υπάρχει ήδη πηγή ταυτοποίησης αυτού του τύπου. auths.unable_to_initialize_openid=Αδυναμία εκκίνησης του παρόχου OpenID Connect: %s auths.invalid_openIdConnectAutoDiscoveryURL=Μη έγκυρο Auto Discovery URL (πρέπει να είναι ένα έγκυρο URL που ξεκινά με http:// ή https://) - config.server_config=Ρυθμίσεις Διακομιστή config.app_name=Τίτλος Ιστοτόπου config.app_ver=Έκδοση Gitea @@ -2863,7 +2745,6 @@ config.lfs_root_path=Ριζική Διαδρομή LFS config.log_file_root_path=Διαδρομή Καταγραφών config.script_type=Τύπος Σεναρίου config.reverse_auth_user=Χρήστης ΑντίστροφηςΠιστοποίησης - config.ssh_config=Ρύθμιση SSH config.ssh_enabled=Ενεργοποιημένο config.ssh_start_builtin_server=Χρήση Ενσωματωμένου Διακομιστή @@ -2875,12 +2756,10 @@ config.ssh_key_test_path=Διαδρομή Δοκιμής Κλειδιού config.ssh_keygen_path=Διαδρομή Keygen ('ssh-keygen') config.ssh_minimum_key_size_check=Έλεγχος Ελάχιστου Μεγέθους Κλειδιού config.ssh_minimum_key_sizes=Ελάχιστα Μεγέθη Κλειδιών - config.lfs_config=Ρύθμιση LFS config.lfs_enabled=Ενεργοποιημένο config.lfs_content_path=Διαδρομή Περιεχομένου LFS config.lfs_http_auth_expiry=LFS Λήξη Ταυτοποίησης HTTP - config.db_config=Ρύθμιση Βάσης Δεδομένων config.db_type=Τύπος config.db_host=Διακομιστής @@ -2889,7 +2768,6 @@ config.db_user=Όνομα Χρήστη config.db_schema=Σχήμα config.db_ssl_mode=SSL config.db_path=Διαδρομή - config.service_config=Ρυθμίσεις Υπηρεσίας config.register_email_confirm=Απαιτείται Επιβεβαίωση του Email για Εγγραφή config.disable_register=Απενεργοποίηση Αυτοεγγραφής @@ -2912,12 +2790,10 @@ config.default_allow_only_contributors_to_track_time=Επιτρέπονται Μ config.no_reply_address=Κρυφό Email Domain config.default_visibility_organization=Προεπιλεγμένη ορατότητα για νέους οργανισμούς config.default_enable_dependencies=Ενεργοποίηση Εξαρτήσεων Ζητημάτων από Προεπιλογή - config.webhook_config=Ρύθμιση Webhook config.queue_length=Μέγεθος Ουράς config.deliver_timeout=Χρονικό Όριο Παράδοσης config.skip_tls_verify=Παράλειψη Επαλήθευσης TLS - config.mailer_config=Ρυθμίσεις Αλληλογραφίας config.mailer_enabled=Ενεργοποιημένο config.mailer_enable_helo=Ενεργοποίηση HELO @@ -2936,16 +2812,13 @@ config.send_test_mail=Αποστολή Δοκιμαστικού Email config.send_test_mail_submit=Αποστολή config.test_mail_failed=Αποτυχία αποστολής ενός δοκιμαστικού email στο"%s": %v config.test_mail_sent=Στάλθηκε ένα δοκιμαστικό email στο "%s". - config.oauth_config=Ρύθμιση Oauth config.oauth_enabled=Ενεργό - config.cache_config=Ρύθμιση Προσωρινής Αποθήκευσης config.cache_adapter=Προσαρμογέας Προσωρινής Αποθήκευσης config.cache_interval=Διάστημα Προσωρινής Αποθήκευσης config.cache_conn=Σύνδεση Προσωρινής Αποθήκευσης config.cache_item_ttl=TTL Στοιχείων Προσωρινής Αποθήκευσης - config.session_config=Ρύθμιση Συνεδρίας config.session_provider=Πάροχος Συνεδρίας config.provider_config=Ρυθμίσεις Πάροχου @@ -2954,12 +2827,10 @@ config.gc_interval_time=Χρόνος Διαστήματος GC config.session_life_time=Χρόνος Ζωής Συνεδρίας config.https_only=Μόνο HTTPS config.cookie_life_time=Χρόνος Ζωής Cookie - config.picture_config=Ρύθμιση Εικόνας και Avatar config.picture_service=Υπηρεσία Εικόνας config.disable_gravatar=Απενεργοποίηση Gravatar config.enable_federated_avatar=Ενεργοποίηση Ομόσπονδων Avatars - config.git_config=Ρύθμιση Git config.git_disable_diff_highlight=Απενεργοποίηση Επισήμανσης Σύνταξης Diff config.git_max_diff_lines=Μέγιστες γραμμές Diff (για ένα μόνο αρχείο) @@ -2971,19 +2842,15 @@ config.git_mirror_timeout=Χρονικό Όριο Ενημέρωσης Ειδώ config.git_clone_timeout=Χρονικό Όριο Κλωνοποίησης config.git_pull_timeout=Χρονικό Όριο Pull config.git_gc_timeout=Χρονικό Όριο Λειτουργίας GC - config.log_config=Ρύθμιση Καταγραφών config.logger_name_fmt=Καταγραφέας: %s config.disabled_logger=Απενεργοποιημένο config.access_log_mode=Λειτουργία Καταγραφών Πρόσβασης config.access_log_template=Πρότυπο Καταγραφής Προσβάσεων config.xorm_log_sql=Καταγραφή SQL - config.get_setting_failed=Αποτυχία λήψης ρύθμισης %s config.set_setting_failed=Αποτυχία ορισμού της ρύθμισης %s - monitor.stats=Στατιστικά - monitor.cron=Προγραμματισμένες Εργασίες monitor.name=Όνομα monitor.schedule=Πρόγραμμα @@ -3002,7 +2869,6 @@ monitor.process.cancel=Ακύρωση διαδικασίας monitor.process.cancel_desc=Η ακύρωση μιας διαδικασίας μπορεί να προκαλέσει απώλεια δεδομένων monitor.process.cancel_notices=Ακύρωση: %s; monitor.process.children=Θυγατρικές - monitor.queues=Ουρές monitor.queue=Ουρά: %s monitor.queue.name=Όνομα @@ -3020,7 +2886,6 @@ monitor.queue.settings.submit=Ενημέρωση Ρυθμίσεων monitor.queue.settings.changed=Οι Ρυθμίσεις Ενημερώθηκαν monitor.queue.settings.remove_all_items=Αφαίρεση όλων monitor.queue.settings.remove_all_items_done=Όλα τα αντικείμενα στην ουρά αφαιρέθηκαν. - notices.system_notice_list=Ειδοποιήσεις Συστήματος notices.view_detail_header=Προβολή Λεπτομερειών Ειδοποίησης notices.operations=Λειτουργίες @@ -3040,16 +2905,16 @@ notices.delete_success=Οι ειδοποιήσεις του συστήματος create_repo=δημιούργησε το αποθετήριο %s rename_repo=μετονόμασε το αποθετήριο από %[1]s σε %[3]s commit_repo=έκανε push στο %[3]s σε %[4]s -create_issue=`άνοιξε το ζήτημα %[3]s#%[2]s` -close_issue=`έκλεισε το ζήτημα %[3]s#%[2]s` -reopen_issue=`άνοιξε ξανά το ζήτημα %[3]s#%[2]s` -create_pull_request=`δημιούργησε το pull request %[3]s#%[2]s` -close_pull_request=`έκλεισε pull request %[3]s#%[2]s` -reopen_pull_request=`άνοιξε ξανά το pull request %[3]s#%[2]s` -comment_issue=`σχολίασε το ζήτημα %[3]s#%[2]s` -comment_pull=`σχολίασε στο pull request %[3]s#%[2]s` -merge_pull_request=`συγχώνευσε το pull request %[3]s#%[2]s` -auto_merge_pull_request=`αυτόματη συγχώνευση του pull request %[3]s#%[2]s` +create_issue=άνοιξε το ζήτημα %[3]s#%[2]s +close_issue=έκλεισε το ζήτημα %[3]s#%[2]s +reopen_issue=άνοιξε ξανά το ζήτημα %[3]s#%[2]s +create_pull_request=δημιούργησε το pull request %[3]s#%[2]s +close_pull_request=έκλεισε pull request %[3]s#%[2]s +reopen_pull_request=άνοιξε ξανά το pull request %[3]s#%[2]s +comment_issue=σχολίασε το ζήτημα %[3]s#%[2]s +comment_pull=σχολίασε στο pull request %[3]s#%[2]s +merge_pull_request=συγχώνευσε το pull request %[3]s#%[2]s +auto_merge_pull_request=αυτόματη συγχώνευση του pull request %[3]s#%[2]s transfer_repo=μετέφερε το αποθετήριο %s σε %s push_tag=ώθησε την ετικέτα %[3]s σε %[4]s delete_tag=διέγραψε την ετικέτα %[2]s από %[3]s @@ -3060,10 +2925,10 @@ compare_commits_general=Σύγκριση υποβολών mirror_sync_push=συγχρονιστήκαν οι υποβολές του %[3]s στο %[4]s από το είδωλο mirror_sync_create=συγχρονίστηκε η νέα αναφορά %[3]s στο %[4]s από το είδωλο mirror_sync_delete=συγχρόνισε και διάγραψε την αναφορά %[2]s σε %[3]s από το είδωλο -approve_pull_request=`ενέκρινε το %[3]s#%[2]s` -reject_pull_request=`πρότεινε αλλαγές για το %[3]s#%[2]s` -publish_release=`έκδωσε τη "%[4]s" στο %[3]s` -review_dismissed=`ακύρωσε την εξέταση από %[4]s for %[3]s#%[2]s` +approve_pull_request=ενέκρινε το %[3]s#%[2]s +reject_pull_request=πρότεινε αλλαγές για το %[3]s#%[2]s +publish_release=έκδωσε τη "%[4]s" στο %[3]s +review_dismissed=ακύρωσε την εξέταση από %[4]s for %[3]s#%[2]s review_dismissed_reason=Αιτία: create_branch=δημιούργησε το κλαδο %[3]s στο %[4]s starred_repo=έδωσε αστέρι στο %[2]s @@ -3288,9 +3153,7 @@ management=Διαχείριση Μυστικών [actions] actions=Δράσεις - unit.desc=Διαχείριση δράσεων - status.unknown=Άγνωστη status.waiting=Αναμονή status.running=Εκτελείται @@ -3298,7 +3161,6 @@ status.success=Επιτυχές status.failure=Αποτυχία status.skipped=Παρακάμφθηκε status.blocked=Αποκλείστηκε - runners=Εκτελεστές runners.runner_manage_panel=Διαχείριση Εκτελεστών runners.new=Δημιουργία νέου Εκτελεστή @@ -3333,17 +3195,13 @@ runners.status.active=Ενεργό runners.status.offline=Χωρίς Σύνδεση runners.version=Έκδοση runners.reset_registration_token_success=Επιτυχής επανέκδοση διακριτικού εγγραφής του εκτελεστή - runs.all_workflows=Όλες Οι Ροές Εργασίας runs.commit=Υποβολή runs.invalid_workflow_helper=Το αρχείο ροής εργασίας δεν είναι έγκυρο. Ελέγξτε το αρχείο σας: %s runs.no_matching_runner_helper=Δε ταιριάζει εκτελεστής: %s runs.status=Κατάσταση - - need_approval_desc=Πρέπει να εγκριθεί η εκτέλεση ροών εργασίας για pull request από fork. - [projects] type-1.display_name=Ατομικό Έργο type-2.display_name=Έργο Αποθετηρίου @@ -3351,4 +3209,3 @@ type-3.display_name=Έργο Οργανισμού [git.filemode] symbolic_link=Symbolic link - diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 74b8931de8..1dad000249 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -112,6 +112,7 @@ step2 = Step 2: error = Error error404 = The page you are trying to reach either does not exist or you are not authorized to view it. +go_back = Go Back never = Never unknown = Unknown @@ -245,6 +246,7 @@ email_title = Email Settings smtp_addr = SMTP Host smtp_port = SMTP Port smtp_from = Send Email As +smtp_from_invalid = The "Send Email As" address is invalid smtp_from_helper = Email address Gitea will use. Enter a plain email address or use the "Name" format. mailer_user = SMTP Username mailer_password = SMTP Password @@ -352,7 +354,6 @@ code_last_indexed_at = Last indexed %s relevant_repositories_tooltip = Repositories that are forks or that have no topic, no icon, and no description are hidden. relevant_repositories = Only relevant repositories are being shown, show unfiltered results. - [auth] create_new_account = Register Account register_helper_msg = Already have an account? Sign in now! @@ -422,6 +423,7 @@ authorization_failed_desc = The authorization failed because we detected an inva sspi_auth_failed = SSPI authentication failed password_pwned = The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password and consider changing this password elsewhere too. password_pwned_err = Could not complete request to HaveIBeenPwned +last_admin = You cannot remove the last admin. There must be at least one admin. [mail] view_it_on = View it on %s @@ -565,6 +567,8 @@ enterred_invalid_repo_name = The repository name you entered is incorrect. enterred_invalid_org_name = The organization name you entered is incorrect. enterred_invalid_owner_name = The new owner name is not valid. enterred_invalid_password = The password you entered is incorrect. +unset_password = The login user has not set the password. +unsupported_login_type = The login type is not supported to delete account. user_not_exist = The user does not exist. team_not_exist = The team does not exist. last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner for an organization. @@ -587,6 +591,8 @@ org_still_own_packages = "This organization still owns one or more packages, del target_branch_not_exist = Target branch does not exist. +admin_cannot_delete_self = You cannot delete yourself when you are an admin. Please remove your admin privileges first. + [user] change_avatar = Change your avatar… joined_on = Joined on %s @@ -854,7 +860,7 @@ oauth2_client_secret_hint = The secret will not be shown again after you leave o oauth2_application_edit = Edit oauth2_application_create_description = OAuth2 applications gives your third-party application access to user accounts on this instance. oauth2_application_remove_description = Removing an OAuth2 application will prevent it from accessing authorized user accounts on this instance. Continue? -oauth2_application_locked = Gitea pre-registers some OAuth2 applications on startup if enabled in config. To prevent unexpected bahavior, these can neither be edited nor removed. Please refer to the OAuth2 documentation for more information. +oauth2_application_locked = Gitea pre-registers some OAuth2 applications on startup if enabled in config. To prevent unexpected behavior, these can neither be edited nor removed. Please refer to the OAuth2 documentation for more information. authorized_oauth2_applications = Authorized OAuth2 Applications authorized_oauth2_applications_description = You have granted access to your personal Gitea account to these third party applications. Please revoke access for applications you no longer need. @@ -922,7 +928,7 @@ visibility.private = Private visibility.private_tooltip = Visible only to members of organizations you have joined [repo] -new_repo_helper = A repository contains all project files, including revision history. Already hosting one elsewhere? Migrate repository. +new_repo_helper = A repository contains all project files, including revision history. Already hosting one elsewhere? Migrate repository. owner = Owner owner_helper = Some organizations may not show up in the dropdown due to a maximum repository count limit. repo_name = Repository Name @@ -1012,19 +1018,21 @@ blame.ignore_revs = Ignoring revisions in .git-blame-ignore-revs.git-blame-ignore-revs. author_search_tooltip = Shows a maximum of 30 users +tree_path_not_found_commit = Path %[1]s doesn't exist in commit %[2]s +tree_path_not_found_branch = Path %[1]s doesn't exist in branch %[2]s +tree_path_not_found_tag = Path %[1]s doesn't exist in tag %[2]s + transfer.accept = Accept Transfer -transfer.accept_desc = Transfer to "%s" +transfer.accept_desc = Transfer to "%s" transfer.reject = Reject Transfer -transfer.reject_desc = Cancel transfer to "%s" +transfer.reject_desc = Cancel transfer to "%s" transfer.no_permission_to_accept = You do not have permission to accept this transfer. transfer.no_permission_to_reject = You do not have permission to reject this transfer. desc.private = Private desc.public = Public -desc.private_template = Private template -desc.public_template = Template +desc.template = Template desc.internal = Internal -desc.internal_template = Internal template desc.archived = Archived template.items = Template Items @@ -1151,7 +1159,7 @@ releases = Releases tag = Tag released_this = released this tagged_this = tagged this -file.title = %s at %s +file.title = %s at %s file_raw = Raw file_history = History file_view_source = View Source @@ -1239,6 +1247,8 @@ editor.file_editing_no_longer_exists = The file being edited, "%s", no longer ex editor.file_deleting_no_longer_exists = The file being deleted, "%s", no longer exists in this repository. editor.file_changed_while_editing = The file contents have changed since you started editing. Click here to see them or Commit Changes again to overwrite them. editor.file_already_exists = A file named "%s" already exists in this repository. +editor.commit_id_not_matching = The Commit ID does not match the ID when you began editing. Commit into a patch branch and then merge. +editor.push_out_of_date = The push appears to be out of date. editor.commit_empty_file_header = Commit an empty file editor.commit_empty_file_text = The file you're about to commit is empty. Proceed? editor.no_changes_to_show = There are no changes to show. @@ -1512,7 +1522,8 @@ issues.label_description = Description issues.label_color = Color issues.label_exclusive = Exclusive issues.label_archive = Archive Label -issues.label_archive_tooltip= Archived labels are excluded from the label search when applying labels to an issue. Existing labels on issues remain unaffected, allowing you to retire obsolete labels without losing information. +issues.label_archived_filter = Show archived labels +issues.label_archive_tooltip = Archived labels are excluded by default from the suggestions when searching by label. issues.label_exclusive_desc = Name the label scope/item to make it mutually exclusive with other scope/ labels. issues.label_exclusive_warning = Any conflicting scoped labels will be removed when editing the labels of an issue or pull request. issues.label_count = %d labels @@ -1768,9 +1779,9 @@ pulls.unrelated_histories = Merge Failed: The merge head and base do not share a pulls.merge_out_of_date = Merge Failed: Whilst generating the merge, the base was updated. Hint: Try again. pulls.head_out_of_date = Merge Failed: Whilst generating the merge, the head was updated. Hint: Try again. pulls.has_merged = Failed: The pull request has been merged, you cannot merge again or change the target branch. -pulls.push_rejected = Merge Failed: The push was rejected. Review the Git Hooks for this repository. +pulls.push_rejected = Push Failed: The push was rejected. Review the Git Hooks for this repository. pulls.push_rejected_summary = Full Rejection Message -pulls.push_rejected_no_message = Merge Failed: The push was rejected but there was no remote message.
Review the Git Hooks for this repository +pulls.push_rejected_no_message = Push Failed: The push was rejected but there was no remote message. Review the Git Hooks for this repository pulls.open_unmerged_pull_exists = `You cannot perform a reopen operation because there is a pending pull request (#%d) with identical properties.` pulls.status_checking = Some checks are pending pulls.status_checks_success = All checks were successful @@ -2133,12 +2144,14 @@ settings.webhook_deletion_desc = Removing a webhook deletes its settings and del settings.webhook_deletion_success = The webhook has been removed. settings.webhook.test_delivery = Test Delivery settings.webhook.test_delivery_desc = Test this webhook with a fake event. +settings.webhook.test_delivery_desc_disabled = To test this webhook with a fake event, activate it. settings.webhook.request = Request settings.webhook.response = Response settings.webhook.headers = Headers settings.webhook.payload = Content settings.webhook.body = Body settings.webhook.replay.description = Replay this webhook. +settings.webhook.replay.description_disabled = To replay this webhook, activate it. settings.webhook.delivery.success = An event has been added to the delivery queue. It may take few seconds before it shows up in the delivery history. settings.githooks_desc = "Git Hooks are powered by Git itself. You can edit hook files below to set up custom operations." settings.githook_edit_desc = If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook. @@ -2298,6 +2311,7 @@ settings.dismiss_stale_approvals_desc = When new commits that change the content settings.require_signed_commits = Require Signed Commits settings.require_signed_commits_desc = Reject pushes to this branch if they are unsigned or unverifiable. settings.protect_branch_name_pattern = Protected Branch Name Pattern +settings.protect_branch_name_pattern_desc = "Protected branch name patterns. See the documentation for pattern syntax. Examples: main, release/**" settings.protect_patterns = Patterns settings.protect_protected_file_patterns = "Protected file patterns (separated using semicolon ';'):" settings.protect_protected_file_patterns_desc = "Protected files are not allowed to be changed directly even if user has rights to add, edit, or delete files in this branch. Multiple patterns can be separated using semicolon (';'). See github.com/gobwas/glob documentation for pattern syntax. Examples: .drone.yml, /docs/**/*.txt." @@ -2493,7 +2507,6 @@ release.releases_for = Releases for %s release.tags_for = Tags for %s branch.name = Branch Name -branch.search = Search branches branch.already_exists = A branch named "%s" already exists. branch.delete_head = Delete branch.delete = Delete Branch "%s" @@ -2871,6 +2884,7 @@ packages.package_manage_panel = Package Management packages.total_size = Total Size: %s packages.unreferenced_size = Unreferenced Size: %s packages.cleanup = Clean up expired data +packages.cleanup.success = Cleaned up expired data successfully packages.owner = Owner packages.creator = Creator packages.name = Name @@ -3143,7 +3157,6 @@ config.access_log_mode = Access Log Mode config.access_log_template = Access Log Template config.xorm_log_sql = Log SQL -config.get_setting_failed = Get setting %s failed config.set_setting_failed = Set setting %s failed monitor.stats = Stats @@ -3163,8 +3176,8 @@ monitor.start = Start Time monitor.execute_time = Execution Time monitor.last_execution_result = Result monitor.process.cancel = Cancel process -monitor.process.cancel_desc = Cancelling a process may cause data loss -monitor.process.cancel_notices = Cancel: %s? +monitor.process.cancel_desc = Cancelling a process may cause data loss +monitor.process.cancel_notices = Cancel: %s? monitor.process.children = Children monitor.queues = Queues @@ -3228,7 +3241,7 @@ mirror_sync_create = synced new reference %[3]s to %[3]s from mirror approve_pull_request = `approved %[3]s#%[2]s` reject_pull_request = `suggested changes for %[3]s#%[2]s` -publish_release = `released "%[4]s" at %[3]s` +publish_release = `released "%[4]s" at %[3]s` review_dismissed = `dismissed review from %[4]s for %[3]s#%[2]s` review_dismissed_reason = Reason: create_branch = created branch %[3]s in %[4]s @@ -3507,6 +3520,7 @@ runners.status.idle = Idle runners.status.active = Active runners.status.offline = Offline runners.version = Version +runners.reset_registration_token = Reset registration token runners.reset_registration_token_success = Runner registration token reset successfully runs.all_workflows = All Workflows @@ -3514,13 +3528,18 @@ runs.commit = Commit runs.scheduled = Scheduled runs.pushed_by = pushed by runs.invalid_workflow_helper = Workflow config file is invalid. Please check your config file: %s -runs.no_matching_runner_helper = No matching runner: %s +runs.no_matching_online_runner_helper = No matching online runner with label: %s +runs.no_job_without_needs = The workflow must contain at least one job without dependencies. runs.actor = Actor runs.status = Status runs.actors_no_select = All actors runs.status_no_select = All status runs.no_results = No results matched. +runs.no_workflows = There are no workflows yet. +runs.no_workflows.quick_start = Don't know how to start with Gitea Action? See the quick start guide. +runs.no_workflows.documentation = For more information on the Gitea Action, see the documentation. runs.no_runs = The workflow has no runs yet. +runs.empty_commit_message = (empty commit message) workflow.disable = Disable Workflow workflow.disable_success = Workflow '%s' disabled successfully. @@ -3553,7 +3572,7 @@ type-3.display_name = Organization Project [git.filemode] changed_filemode = %[1]s → %[2]s -# Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", … +; Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", … directory = Directory normal_file = Normal file executable_file = Executable file diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index c8ff04c157..270befc3d2 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -22,7 +22,6 @@ signed_in_as=Identificado como toc=Tabla de contenidos licenses=Licencias return_to_gitea=Volver a Gitea - username=Nombre de usuario email=Correo electrónico password=Contraseña @@ -32,7 +31,6 @@ captcha=CAPTCHA twofa=Autenticación de doble factor twofa_scratch=Código de respaldo passcode=Código de acceso - webauthn_insert_key=Introduzca su clave de seguridad webauthn_sign_in=Presione el botón en su clave de seguridad. Si su clave de seguridad no tiene ningún botón, vuelva a insertarla. webauthn_press_button=Por favor, presione el botón de su llave de seguridad… @@ -40,13 +38,12 @@ webauthn_use_twofa=Utilice un código de doble factor desde su teléfono móvil webauthn_error=No se pudo leer su llave de seguridad. webauthn_unsupported_browser=Su navegador no soporta actualmente WebAuthn. webauthn_error_unknown=Ha ocurrido un error desconocido. Por favor, inténtelo de nuevo. -webauthn_error_insecure=`WebAuthn sólo soporta conexiones seguras. Para probar sobre HTTP, puede utilizar el origen "localhost" o "127.0.0.1"` +webauthn_error_insecure=WebAuthn sólo soporta conexiones seguras. Para probar sobre HTTP, puede utilizar el origen "localhost" o "127.0.0.1" webauthn_error_unable_to_process=El servidor no pudo procesar su solicitud. webauthn_error_duplicated=La clave de seguridad no está permitida para esta solicitud. Por favor, asegúrese de que la clave no está ya registrada. webauthn_error_empty=Debe establecer un nombre para esta clave. webauthn_error_timeout=Tiempo de espera máximo alcanzado antes de que su clave pudiese ser leída. Por favor, cargue la página y vuelva a intentarlo. webauthn_reload=Recargar - repository=Repositorio organization=Organización mirror=Réplica @@ -63,18 +60,15 @@ settings=Configuración your_profile=Perfil your_starred=Destacado your_settings=Configuración - all=Todos sources=Propios mirrors=Réplica collaborative=Colaborativo forks=Forks - activities=Actividades pull_requests=Pull Requests issues=Incidencias milestones=Hitos - ok=OK cancel=Cancelar save=Guardar @@ -83,45 +77,77 @@ add_all=Añadir todo remove=Eliminar remove_all=Eliminar todos edit=Editar - enabled=Activo disabled=Desactivado - copy=Copiar copy_url=Copiar URL copy_branch=Copiar nombre de rama copy_success=¡Copiado! copy_error=Copiar falló - write=Escribir preview=Vista previa loading=Cargando… - step1=Paso 1: step2=Paso 2: - error=Error error404=La página a la que está intentando acceder o no existe o no está autorizado para verla. - never=Nunca - rss_feed=Fuentes RSS - - - concept_code_repository=Repositorio concept_user_organization=Organización - - - name=Nombre value=Valor +sign_in_with_provider=Iniciar sesión con %s +enable_javascript=Este sitio web requiere JavaScript. +new_project_column=Columna nueva +retry=Reintentar +rerun=Re-ejecutar +rerun_all=Volver a ejecutar todos los trabajos +remove_label_str=Eliminar elemento "%s" +locked=Bloqueado +copy_content=Copiar contenido +copy_type_unsupported=No se puede copiar este tipo de archivo +go_back=Volver +unknown=Desconocido +pin=Anclar +unpin=Desanclar +artifacts=Artefactos +archived=Archivado +concept_system_global=Global +concept_user_individual=Individual +show_timestamps=Mostrar marcas de tiempo +show_log_seconds=Mostrar segundos +show_full_screen=Mostrar pantalla completa +download_logs=Descargar registros +confirm_delete_selected=¿Borrar todos los elementos seleccionados? [aria] +navbar=Barra de navegación +footer=Pie +footer.software=Acerca del Software +footer.links=Enlaces [heatmap] +number_of_contributions_in_the_last_12_months=%s contribuciones en los últimos 12 meses +no_contributions=No hay contribuciones +less=Menos +more=Más [editor] +buttons.heading.tooltip=Añadir encabezado +buttons.bold.tooltip=Añadir texto en negrita +buttons.italic.tooltip=Añadir texto en cursiva +buttons.quote.tooltip=Citar texto +buttons.code.tooltip=Añadir código +buttons.link.tooltip=Añadir un enlace +buttons.list.unordered.tooltip=Añadir una lista +buttons.list.ordered.tooltip=Añadir una lista numerada +buttons.list.task.tooltip=Añadir una lista de tareas +buttons.mention.tooltip=Mencionar un usuario o equipo +buttons.ref.tooltip=Referir a una incidencia o pull request +buttons.switch_to_legacy.tooltip=Utilizar el editor antiguo en su lugar +buttons.enable_monospace_font=Activar fuente monoespaciada +buttons.disable_monospace_font=Desactivar fuente monoespaciada [filter] string.asc=A - Z @@ -133,6 +159,7 @@ missing_csrf=Solicitud incorrecta: sin token CSRF invalid_csrf=Solicitud incorrecta: el token CSRF no es válido not_found=El objetivo no pudo ser encontrado. network_error=Error de red +report_message=Si crees que este es un error de Gitea, por favor busca incidencias en GitHub o abre una nueva incidencia si es necesario. [startpage] app_desc=Un servicio de Git autoalojado y sin complicaciones @@ -143,6 +170,7 @@ lightweight=Ligero lightweight_desc=Gitea tiene pocos requisitos y puede funcionar en una Raspberry Pi barata. ¡Ahorra energía! license=Código abierto license_desc=¡Está todo en < code.gitea.io/gitea! Únase contribuyendo a hacer este proyecto todavía mejor. ¡No sea tímido y colabore! +install_desc=Simplemente ejecuta el binario para tu plataforma, lánzalo con Dockero consíguelo empaquetado. [install] install=Instalación @@ -172,7 +200,6 @@ err_empty_admin_email=El correo electrónico del administrador no puede estar va err_admin_name_is_reserved=Nombre de usuario del administrador no es válido, el nombre de usuario está reservado err_admin_name_pattern_not_allowed=El nombre de usuario del administrador no es válido, el nombre de usuario coincide con un patrón reservado err_admin_name_is_invalid=Nombre de usuario del administrador no es válido - general_title=Configuración general app_name=Título del sitio app_name_helper=Puede colocar aquí el nombre de su empresa. @@ -191,7 +218,6 @@ app_url=URL base de Gitea app_url_helper=Dirección base para URLs de clonación HTTP(S) y notificaciones de correo electrónico. log_root_path=Ruta del registro log_root_path_helper=Archivos de registro se escribirán en este directorio. - optional_title=Configuración opcional email_title=Configuración de Correo smtp_addr=Servidor SMTP @@ -246,6 +272,15 @@ default_enable_timetracking_popup=Activar el seguimiento de tiempo para nuevos r no_reply_address=Dominio de correos electrónicos ocultos no_reply_address_helper=Nombre de dominio para usuarios con dirección de correo electrónico oculta. Por ejemplo, el usuario 'joe' quedará registrado en Git como 'joe@noreply.example.org' si el dominio de correo electrónico oculto se establece a 'noreply.example.org'. password_algorithm=Algoritmo Hash de Contraseña +run_user_helper=El nombre de usuario del sistema operativo que ejecuta Gitea. Tenga en cuenta que este usuario debe tener acceso a la ruta raíz del repositorio. +require_sign_in_view_popup=Limitar el acceso a los usuarios conectados. Los visitantes sólo verán las páginas de inicio de sesión y de registro. +invalid_db_table=La tabla "%s" de la base de datos no es válida: %v +invalid_password_algorithm=Algoritmo hash de contraseña no válido +password_algorithm_helper=Establece el algoritmo de hashing de contraseña. Los algoritmos tienen diferentes requisitos y fuerza. El algoritmo argon2 es bastante seguro, pero usa mucha memoria y puede ser inapropiado para sistemas pequeños. +enable_update_checker=Activar comprobador de actualizaciones +enable_update_checker_helper=Comprueba el lanzamiento de nuevas versiones periódicamente en gitea.io. +env_config_keys=Configuración del entorno +env_config_keys_prompt=Las siguientes variables de entorno también se aplicarán a su archivo de configuración: [home] uname_holder=Nombre de usuario o correo electrónico @@ -260,19 +295,16 @@ view_home=Ver %s search_repos=Buscar un repositorio… filter=Otros filtros filter_by_team_repositories=Filtrar por repositorios de equipo -feed_of=`Suministro de noticias de "%s"` - +feed_of=Suministro de noticias de "%s" show_archived=Archivado archived=Archivado show_both_archived_unarchived=Mostrar respositorios archivados y desarchivados show_only_archived=Mostrar sólo repositorios archivados show_only_unarchived=Mostrar sólo repositorios desarchivados - show_private=Privado show_both_private_public=Mostrar repositorios públicos y privados show_only_private=Mostrar sólo repositorios privados show_only_public=Mostrar sólo repositorios públicos - issues.in_your_repos=En tus repositorios [explore] @@ -294,7 +326,8 @@ code_no_results=No se ha encontrado código de fuente que coincida con su térmi code_last_indexed_at=Indexado por última vez %s relevant_repositories_tooltip=Repositorios que son bifurcaciones o que no tienen ningún tema, ningún icono, y ninguna descripción están ocultos. relevant_repositories=Solo se muestran repositorios relevantes, mostrar resultados sin filtrar. - +go_to=Ir a +code_search_results=Resultados de búsqueda para «%s» [auth] create_new_account=Registrar una cuenta @@ -357,35 +390,36 @@ authorize_title=¿Autorizar a "%s" a acceder a su cuenta? authorization_failed=Autorización fallida sspi_auth_failed=Fallo en la autenticación SSPI password_pwned_err=No se pudo completar la solicitud a HaveIBeenPwned +sign_up_successful=La cuenta se ha creado correctamente. ¡Bienvenido! +prohibit_login_desc=Su cuenta no puede iniciar sesión, póngase en contacto con el administrador de su sitio. +invalid_code_forgot_password=Su código de confirmación no es válido o ha caducado. Haga clic aquí para iniciar una nueva sesión. +invalid_password=Su contraseña no coincide con la contraseña utilizada para crear la cuenta. +reset_password_wrong_user=Has iniciado sesión como %s, pero el enlace de recuperación de la cuenta está destinado a %s +openid_signin_desc=Introduzca su URI de OpenID. Por ejemplo: alice.openid.example.org o https://openid.example.org/alice. +authorization_failed_desc=La autorización ha fallado porque hemos detectado una solicitud no válida. Por favor, póngase en contacto con el responsable de la aplicación que ha intentado autorizar. +password_pwned=La contraseña que eligió está en una lista de contraseñas robadas previamente expuestas en violaciones de datos públicos. Por favor, inténtelo de nuevo con una contraseña diferente y considere cambiar esta contraseña también en otros lugares. [mail] view_it_on=Ver en %s link_not_working_do_paste=¿No funciona? Intenta copiarlo y pegarlo en tu navegador. hi_user_x=Hola %s, - activate_account=Por favor, active su cuenta activate_account.title=%s, por favor activa tu cuenta activate_account.text_1=¡Hola %[1]s, gracias por registrarse en %[2]s! activate_account.text_2=Por favor, haga clic en el siguiente enlace para activar su cuenta dentro de %s: - activate_email=Verifique su correo electrónico activate_email.text=Por favor, haga clic en el siguiente enlace para verificar su dirección de correo electrónico dentro de %s: - register_notify=¡Bienvenido a Gitea register_notify.title=%[1]s, bienvenido a %[2]s register_notify.text_1=este es tu correo de confirmación de registro para %s! register_notify.text_2=Ahora puede iniciar sesión vía nombre de usuario: %s. register_notify.text_3=Si esta cuenta ha sido creada para usted, por favor establezca su contraseña primero. - reset_password=Recupere su cuenta reset_password.title=%s, has solicitado recuperar tu cuenta reset_password.text=Haga clic en el siguiente enlace para recuperar su cuenta dentro de %s: - register_success=Registro completado - issue_assigned.pull=@%[1]s le asignó al pull request %[2]s en el repositorio %[3]s. issue_assigned.issue=@%[1]s le asignó a la incidencia %[2]s en el repositorio %[3]s. - issue.x_mentioned_you=@%s te mencionó: issue.action.force_push=%[1]s empujó a la fuerza el %[2]s de %[3]s a %[4]s. issue.action.push_1=@%[1]s hizo %[3]d commit al %[2]s @@ -400,7 +434,6 @@ issue.action.review_dismissed=@%[1]s descartó la última revisión de %[ issue.action.ready_for_review=@%[1]s marcó este pull request listo para ser revisado. issue.action.new=@%[1]s creó #%[2]d. issue.in_tree_path=En %s: - release.new.subject=%s en %s publicado release.new.text=@%[1]s lanzó %[2]s en %[3]s release.title=Título: %s @@ -408,25 +441,25 @@ release.note=Nota: release.downloads=Descargas: release.download.zip=Código fuente (ZIP) release.download.targz=Código fuente (TAR.GZ) - repo.transfer.subject_to=%s desea transferir "%s" a %s repo.transfer.subject_to_you=%s desea transferir "%s" a usted repo.transfer.to_you=usted repo.transfer.body=Para aceptarlo o rechazarlo, visita %s o simplemente ignórelo. - repo.collaborator.added.subject=%s le añadió en %s repo.collaborator.added.text=Has sido añadido como colaborador del repositorio: - team_invite.subject=%[1]s le ha invitado a unirse a la organización de %[2]s team_invite.text_1=%[1]s le ha invitado a unirse al equipo %[2]s en la organización %[3]s. team_invite.text_2=Por favor, haz clic en el siguiente enlace para unirte al equipo: team_invite.text_3=Nota: Esta invitación estaba destinada a %[1]s. Si no esperabas esta invitación, puedes ignorar este correo electrónico. +reply=o responde directamente a este correo electrónico +activate_email.title=%s, por favor verifique su dirección de correo electrónico [modal] yes=Sí no=No cancel=Cancelar modify=Actualizar +confirm=Confirmar [form] UserName=Nombre de usuario @@ -440,32 +473,28 @@ PayloadUrl=URL de carga TeamName=Nombre del equipo AuthName=Nombre de autorización AdminEmail=Correo electrónico del administrador - NewBranchName=Nuevo nombre de rama CommitSummary=Resumen del commit CommitMessage=Mensaje de commit CommitChoice=Hacer commit de la elección TreeName=Ruta del archivo Content=Contenido - SSPISeparatorReplacement=Separador SSPIDefaultLanguage=Idioma predeterminado - -require_error=` no puede estar vacío.` -alpha_dash_error=` solo debe contener caracteres alfanuméricos, guiones medios ('-') y guiones bajos ('_').` -alpha_dash_dot_error=` solo debe contener caracteres alfanuméricos, guiones, ('-'), subrayados ('_'), y puntos ('.').` -git_ref_name_error=` debe ser un nombre de referencia de Git bien formado.` -size_error=` debe ser de tamaño %s.` -min_size_error=` debe contener al menos %s caracteres.` -max_size_error=` debe contener como máximo %s caracteres.` -email_error=` no es una dirección de correo válida.` -glob_pattern_error=` el patrón globo no es válido: %s.` -regex_pattern_error=` el patrón de regex no es válido: %s.` +require_error=" no puede estar vacío." +alpha_dash_error=" solo debe contener caracteres alfanuméricos, guiones medios ('-') y guiones bajos ('_')." +alpha_dash_dot_error=" solo debe contener caracteres alfanuméricos, guiones, ('-'), subrayados ('_'), y puntos ('.')." +git_ref_name_error=" debe ser un nombre de referencia de Git bien formado." +size_error=" debe ser de tamaño %s." +min_size_error=" debe contener al menos %s caracteres." +max_size_error=" debe contener como máximo %s caracteres." +email_error=" no es una dirección de correo válida." +glob_pattern_error=" el patrón globo no es válido: %s." +regex_pattern_error=" el patrón de regex no es válido: %s." unknown_error=Error desconocido: captcha_incorrect=El código CAPTCHA no es correcto. password_not_match=Las contraseñas no coinciden. lang_select_error=Seleccione un idioma de la lista. - username_been_taken=El nombre de usuario ya está en uso. username_change_not_local_user=Los usuarios que no son locales no tienen permitido cambiar su nombre de usuario. repo_name_been_taken=El nombre del repositorio ya está usado. @@ -496,14 +525,25 @@ team_not_exist=Este equipo no existe. last_org_owner=No puedes eliminar al último usuario del equipo de 'propietarios'. Todas las organizaciones deben tener al menos un propietario. cannot_add_org_to_team=Una organización no puede ser añadida como miembro de un equipo. duplicate_invite_to_team=El usuario ya fue invitado como miembro del equipo. - invalid_ssh_key=No se puede verificar su clave SSH: %s invalid_gpg_key=No se puede verificar su clave GPG: %s invalid_ssh_principal=Principal no válido: %s auth_failed=Autenticación fallo: %v - - target_branch_not_exist=La rama de destino no existe +url_error="%s" no es una URL válida. +include_error=" debe contener la subcadena "%s"." +username_error=" sólo puede contener caracteres alfanuméricos ('0-9','a-z','A-Z'), guión ('-'), guión bajo ('_') y punto ('.'). No puede comenzar o terminar con caracteres no alfanuméricos, y los caracteres no alfanuméricos consecutivos también están prohibidos." +invalid_group_team_map_error=" la asignación no es válida: %s" +username_has_not_been_changed=El nombre de usuario no ha sido cambiado +openid_been_used=La dirección de OpenID "%s" ya está en uso. +organization_leave_success=Ha abandonado correctamente la organización %s. +must_use_public_key=La clave que proporcionó es una clave privada. No cargue su clave privada en ningún sitio. Utilice su clave pública en su lugar. +unable_verify_ssh_key=No se puede verificar la clave SSH, comprueba si hay errores. +still_own_repo=Su cuenta posee uno o más repositorios, elimínalos o transfiérelos primero. +still_has_org=Tu cuenta es miembro de una o más organizaciones, déjalas primero. +still_own_packages=Su cuenta posee uno o más paquetes, elimínalos primero. +org_still_own_repo=Esta organización todavía posee uno o más repositorios, elimínalos o transfiérelos primero. +org_still_own_packages=Esta organización todavía posee uno o más paquetes, elimínalos primero. [user] change_avatar=Cambiar su avatar… @@ -520,7 +560,14 @@ follow=Seguir unfollow=Dejar de seguir user_bio=Biografía disabled_public_activity=Este usuario ha desactivado la visibilidad pública de la actividad. - +joined_on=Se unió el %s +email_visibility.limited=Tu dirección de correo electrónico es visible para todos los usuarios autenticados +email_visibility.private=Tu dirección de correo electrónico solo es visible para ti y los administradores +show_on_map=Mostrar este lugar en un mapa +settings=Ajustes del usuario +form.name_reserved=El nombre de usuario "%s" está reservado. +form.name_pattern_not_allowed=El patrón "%s" no está permitido en un nombre de usuario. +form.name_chars_not_allowed=El nombre de usuario "%s" contiene caracteres no válidos. [settings] profile=Perfil @@ -539,7 +586,6 @@ twofa=Autenticación de doble factor account_link=Cuentas vinculadas organization=Organizaciones webauthn=Llaves de Seguridad - public_profile=Perfil público password_username_disabled=Usuarios no locales no tienen permitido cambiar su nombre de usuario. Por favor, contacta con el administrador del sistema para más detalles. full_name=Nombre completo @@ -573,7 +619,6 @@ comment_type_group_issue_ref=Referencia del incidente saved_successfully=Tus ajustes se han guardado correctamente. privacy=Privacidad keep_activity_private_popup=Hace la actividad visible sólo para ti y los administradores - lookup_avatar_by_mail=Buscar avatar por dirección de correo electrónico federated_avatar_lookup=Búsqueda de Avatar Federado enable_custom_avatar=Activar avatar personalizado @@ -583,14 +628,12 @@ delete_current_avatar=Eliminar avatar uploaded_avatar_not_a_image=El archivo subido no es una imagen. update_avatar_success=Su avatar ha sido actualizado. update_user_avatar_success=El avatar del usuario se ha actualizado. - change_password=Actualizar contraseña old_password=Contraseña actual new_password=Nueva contraseña password_incorrect=Contraseña actual incorrecta. change_password_success=Su contraseña ha sido modificada. Utilice su nueva contraseña la próxima vez que acceda a la cuenta. password_change_disabled=Los usuarios no locales no pueden actualizar su contraseña a través de la interfaz web de Gitea. - emails=Direcciones de correo electrónico manage_emails=Administrar direcciones de correo electrónico manage_themes=Selecciona el tema por defecto @@ -620,7 +663,6 @@ email_preference_set_success=La preferencia de correo electrónico se ha estable add_openid_success=La nueva dirección OpenID ha sido añadida. keep_email_private=Ocultar dirección de correo electrónico openid_desc=OpenID le permite delegar la autenticación a un proveedor externo. - manage_ssh_keys=Gestionar Claves SSH manage_ssh_principals=Administrar Principales de Certificado SSH manage_gpg_keys=Administrar claves GPG @@ -690,7 +732,6 @@ ssh_disabled=SSH deshabilitado ssh_externally_managed=Esta clave SSH está administrada externamente para este usuario manage_social=Gestionar Redes Sociales asociadas unbind=Desvincular - manage_access_token=Administrar Tokens de Acceso generate_new_token=Generar nuevo Token tokens_desc=Estos tokens otorgan acceso a su cuenta usando la API de Gitea. @@ -706,7 +747,6 @@ access_token_deletion_desc=Eliminar un token revocará el acceso a su cuenta par delete_token_success=El token ha sido eliminado. Las aplicaciones que lo usen ya no tienen acceso a su cuenta. permission_no_access=Sin acceso permission_read=Leídas - manage_oauth2_applications=Administrar aplicaciones OAuth2 edit_oauth2_application=Modificar aplicaciones OAuth2 oauth2_applications_desc=Las aplicaciones OAuth2 permiten a su aplicación de terceros autenticar de forma segura a los usuarios en esta instancia de Gitea. @@ -723,12 +763,10 @@ oauth2_regenerate_secret=Regenerar secreto oauth2_regenerate_secret_hint=¿Ha perdido su secreto? oauth2_application_edit=Editar oauth2_application_create_description=Las aplicaciones OAuth2 le dan acceso a su aplicación de terceros a cuentas de usuario en esta instancia. - authorized_oauth2_applications=Aplicaciones OAuth2 autorizadas revoke_key=Revocar revoke_oauth2_grant=Revocar acceso revoke_oauth2_grant_description=Revocar el acceso a esta aplicación impedirá que esta aplicación acceda a sus datos. ¿Está seguro? - twofa_desc=La autenticación de doble factor mejora la seguridad de su cuenta. twofa_is_enrolled=Su cuenta actualmente está registrada en la autenticación de doble factor. twofa_not_enrolled=Tu cuenta no está actualmente inscrita en la autenticación de doble factor. @@ -745,13 +783,11 @@ then_enter_passcode=E introduzca el código de acceso mostrado en la aplicación passcode_invalid=El código de acceso es incorrecto. Vuelva a intentarlo. twofa_enrolled=Su cuenta ha sido inscrita en la autenticación de doble factor. ¡Guarde su código de respaldo (%s) en un lugar seguro, ya que sólo se muestra una vez! twofa_failed_get_secret=No se pudo obtener el secreto. - webauthn_desc=Las claves de seguridad son dispositivos hardware que contienen claves criptográficas. Pueden ser usados para la autenticación de doble factor. Las claves de seguridad deben soportar el estándar WebAuthn Authenticator. webauthn_register_key=Añadir clave de seguridad webauthn_nickname=Apodo webauthn_delete_key=Eliminar clave de seguridad webauthn_delete_key_desc=Si elimina una llave de seguridad ya no podrá utilizarla para iniciar sesión con ella. ¿Continuar? - manage_account_links=Administrar cuentas vinculadas manage_account_links_desc=Estas cuentas externas están vinculadas a su cuenta de Gitea. account_links_not_available=Actualmente no hay cuentas externas vinculadas a su cuenta de Gitea. @@ -759,27 +795,72 @@ link_account=Enlazar cuenta remove_account_link=Eliminar cuenta vinculada remove_account_link_desc=Eliminar una cuenta vinculada revocará su acceso a su cuenta de Gitea. ¿Continuar? remove_account_link_success=La cuenta vinculada ha sido eliminada. - - orgs_none=No eres miembro de ninguna organización. - delete_account=Elimina tu cuenta delete_prompt=Esta operación eliminará permanentemente su cuenta de usuario. NO podrá deshacerse. delete_with_all_comments=Tu cuenta es menor de %s. Para evitar comentarios fantasma, todos los comentarios/PR serán eliminados con ella. confirm_delete_account=Confirmar Eliminación delete_account_title=Eliminar cuenta de usuario delete_account_desc=¿Está seguro que desea eliminar permanentemente esta cuenta de usuario? - email_notifications.enable=Habilitar notificaciones por correo electrónico email_notifications.onmention=Enviar correo sólo al ser mencionado email_notifications.disable=Deshabilitar las notificaciones por correo electrónico email_notifications.submit=Establecer preferencias de correo electrónico email_notifications.andyourown=Y sus propias notificaciones - visibility=Visibilidad del usuario visibility.public=Público visibility.limited=Limitado visibility.private=Privado +uid=UID +biography_placeholder=¡Cuéntanos un poco sobre ti mismo! (Puedes usar Markdown) +location_placeholder=Comparte tu ubicación aproximada con otros +profile_desc=Controla cómo se muestra su perfil a otros usuarios. Tu dirección de correo electrónico principal se utilizará para notificaciones, recuperación de contraseña y operaciones de Git basadas en la web. +update_language_not_found=Idioma "%s" no está disponible. +change_username_prompt=Nota: Cambiar su nombre de usuario también cambia la URL de su cuenta. +change_username_redirect_prompt=El nombre de usuario antiguo redirigirá hasta que alguien lo reclame. +hidden_comment_types_description=Los tipos de comentarios marcados aquí no se mostrarán dentro de las páginas de incidencia. Marcar "Etiqueta" por ejemplo elimina todos los comentarios " añadidos/eliminados