diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4f82a5d8c6..f8e5972af1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -20,7 +20,6 @@ "customizations": { "vscode": { "settings": {}, - // same extensions as Gitpod, should match /.gitpod.yml "extensions": [ "editorconfig.editorconfig", "dbaeumer.vscode-eslint", diff --git a/.github/labeler.yml b/.github/labeler.yml index 68a0f30fd6..0f3c508041 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -43,7 +43,6 @@ modifies/internal: - ".editorconfig" - ".eslintrc.cjs" - ".golangci.yml" - - ".gitpod.yml" - ".markdownlint.yaml" - ".spectral.yaml" - "stylelint.config.*" diff --git a/.github/workflows/cron-flake-updater.yml b/.github/workflows/cron-flake-updater.yml index 105802e558..c9a1f22a2a 100644 --- a/.github/workflows/cron-flake-updater.yml +++ b/.github/workflows/cron-flake-updater.yml @@ -13,7 +13,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: DeterminateSystems/determinate-nix-action@v3 - uses: DeterminateSystems/update-flake-lock@main with: diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml index 332e9e0d6f..55d206bb0f 100644 --- a/.github/workflows/files-changed.yml +++ b/.github/workflows/files-changed.yml @@ -40,7 +40,7 @@ jobs: json: ${{ steps.changes.outputs.json }} steps: - uses: actions/checkout@v6 - - uses: dorny/paths-filter@v3 + - uses: dorny/paths-filter@v4 id: changes with: filters: | diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index fb81622bd6..c93aed05f4 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -38,9 +38,9 @@ jobs: contents: read steps: - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@v8.0.0 - run: uv python install 3.14 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v6 with: node-version: 24 @@ -58,7 +58,7 @@ jobs: contents: read steps: - uses: actions/checkout@v6 - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@v8.0.0 - run: uv python install 3.14 - run: make deps-py - run: make lint-yaml @@ -71,7 +71,7 @@ jobs: contents: read steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v5 with: node-version: 24 @@ -86,7 +86,7 @@ jobs: contents: read steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v6 with: node-version: 24 @@ -168,7 +168,7 @@ jobs: contents: read steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v6 with: node-version: 24 @@ -222,7 +222,7 @@ jobs: contents: read steps: - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v6 with: node-version: 24 diff --git a/.github/workflows/pull-docker-dryrun.yml b/.github/workflows/pull-docker-dryrun.yml index 405521c354..201825ccba 100644 --- a/.github/workflows/pull-docker-dryrun.yml +++ b/.github/workflows/pull-docker-dryrun.yml @@ -21,18 +21,20 @@ jobs: contents: read steps: - uses: actions/checkout@v6 - - uses: docker/setup-qemu-action@v3 - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-qemu-action@v4 + - uses: docker/setup-buildx-action@v4 - name: Build regular container image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 push: false + cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful - name: Build rootless container image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . push: false platforms: linux/amd64,linux/arm64,linux/riscv64 file: Dockerfile.rootless + cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless diff --git a/.github/workflows/pull-e2e-tests.yml b/.github/workflows/pull-e2e-tests.yml index c77f7af3f0..3472d517c1 100644 --- a/.github/workflows/pull-e2e-tests.yml +++ b/.github/workflows/pull-e2e-tests.yml @@ -25,7 +25,7 @@ jobs: with: go-version-file: go.mod check-latest: true - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v6 with: node-version: 24 diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index f073df05d3..eaebccd7fb 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -22,7 +22,7 @@ jobs: with: go-version-file: go.mod check-latest: true - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v6 with: node-version: 24 @@ -35,7 +35,7 @@ jobs: TAGS: bindata sqlite sqlite_unlock_notify - name: import gpg key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v6 + uses: crazy-max/ghaction-import-gpg@v7 with: gpg_private_key: ${{ secrets.GPGSIGN_KEY }} passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} @@ -52,7 +52,7 @@ jobs: echo "Cleaned name is ${REF_NAME}" echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT" - name: configure aws - uses: aws-actions/configure-aws-credentials@v5 + uses: aws-actions/configure-aws-credentials@v6 with: aws-region: ${{ secrets.AWS_REGION }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -71,14 +71,14 @@ jobs: # 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@v3 - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-qemu-action@v4 + - uses: docker/setup-buildx-action@v4 - name: Get cleaned branch name id: clean_name run: | REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//') echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT" - - uses: docker/metadata-action@v5 + - uses: docker/metadata-action@v6 id: meta with: images: |- @@ -88,7 +88,7 @@ jobs: type=raw,value=${{ steps.clean_name.outputs.branch }} annotations: | org.opencontainers.image.authors="maintainers@gitea.io" - - uses: docker/metadata-action@v5 + - uses: docker/metadata-action@v6 id: meta_rootless with: images: |- @@ -102,26 +102,28 @@ jobs: annotations: | org.opencontainers.image.authors="maintainers@gitea.io" - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR using PAT - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: build regular docker image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 push: true tags: ${{ steps.meta.outputs.tags }} annotations: ${{ steps.meta.outputs.annotations }} + cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful + cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful,mode=max - name: build rootless docker image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 @@ -129,3 +131,5 @@ jobs: file: Dockerfile.rootless tags: ${{ steps.meta_rootless.outputs.tags }} annotations: ${{ steps.meta_rootless.outputs.annotations }} + cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless + cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless,mode=max diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml index fab468c9b4..248fa532ee 100644 --- a/.github/workflows/release-tag-rc.yml +++ b/.github/workflows/release-tag-rc.yml @@ -23,7 +23,7 @@ jobs: with: go-version-file: go.mod check-latest: true - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v6 with: node-version: 24 @@ -36,7 +36,7 @@ jobs: TAGS: bindata sqlite sqlite_unlock_notify - name: import gpg key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v6 + uses: crazy-max/ghaction-import-gpg@v7 with: gpg_private_key: ${{ secrets.GPGSIGN_KEY }} passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} @@ -53,7 +53,7 @@ jobs: echo "Cleaned name is ${REF_NAME}" echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" - name: configure aws - uses: aws-actions/configure-aws-credentials@v5 + uses: aws-actions/configure-aws-credentials@v6 with: aws-region: ${{ secrets.AWS_REGION }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -81,9 +81,9 @@ jobs: # 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@v3 - - uses: docker/setup-buildx-action@v3 - - uses: docker/metadata-action@v5 + - uses: docker/setup-qemu-action@v4 + - uses: docker/setup-buildx-action@v4 + - uses: docker/metadata-action@v6 id: meta with: images: |- @@ -96,7 +96,7 @@ jobs: type=semver,pattern={{version}} annotations: | org.opencontainers.image.authors="maintainers@gitea.io" - - uses: docker/metadata-action@v5 + - uses: docker/metadata-action@v6 id: meta_rootless with: images: |- @@ -112,18 +112,18 @@ jobs: annotations: | org.opencontainers.image.authors="maintainers@gitea.io" - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR using PAT - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: build regular container image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 @@ -131,7 +131,7 @@ jobs: tags: ${{ steps.meta.outputs.tags }} annotations: ${{ steps.meta.outputs.annotations }} - name: build rootless container image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index 113a33c3c7..1e84ae1739 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -26,7 +26,7 @@ jobs: with: go-version-file: go.mod check-latest: true - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v6 with: node-version: 24 @@ -39,7 +39,7 @@ jobs: TAGS: bindata sqlite sqlite_unlock_notify - name: import gpg key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v6 + uses: crazy-max/ghaction-import-gpg@v7 with: gpg_private_key: ${{ secrets.GPGSIGN_KEY }} passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }} @@ -56,7 +56,7 @@ jobs: echo "Cleaned name is ${REF_NAME}" echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT" - name: configure aws - uses: aws-actions/configure-aws-credentials@v5 + uses: aws-actions/configure-aws-credentials@v6 with: aws-region: ${{ secrets.AWS_REGION }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -84,9 +84,9 @@ jobs: # 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@v3 - - uses: docker/setup-buildx-action@v3 - - uses: docker/metadata-action@v5 + - uses: docker/setup-qemu-action@v4 + - uses: docker/setup-buildx-action@v4 + - uses: docker/metadata-action@v6 id: meta with: images: |- @@ -103,7 +103,7 @@ jobs: type=semver,pattern={{major}}.{{minor}} annotations: | org.opencontainers.image.authors="maintainers@gitea.io" - - uses: docker/metadata-action@v5 + - uses: docker/metadata-action@v6 id: meta_rootless with: images: |- @@ -124,18 +124,18 @@ jobs: annotations: | org.opencontainers.image.authors="maintainers@gitea.io" - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR using PAT - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: build regular container image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 @@ -143,7 +143,7 @@ jobs: tags: ${{ steps.meta.outputs.tags }} annotations: ${{ steps.meta.outputs.annotations }} - name: build rootless container image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 diff --git a/.gitignore b/.gitignore index 45e8e9295f..019ee94c7a 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,7 @@ cpu.out /yarn-error.log /npm-debug.log* /.pnpm-store +/public/assets/.vite /public/assets/js /public/assets/css /public/assets/fonts @@ -87,8 +88,6 @@ cpu.out /VERSION /.air -# Files and folders that were previously generated -/public/assets/img/webpack # Snapcraft /gitea_a*.txt diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 8671edc47c..0000000000 --- a/.gitpod.yml +++ /dev/null @@ -1,51 +0,0 @@ -tasks: - - name: Setup - init: | - cp -r contrib/ide/vscode .vscode - make deps - make build - command: | - gp sync-done setup - exit 0 - - name: Run backend - command: | - gp sync-await setup - - # Get the URL and extract the domain - url=$(gp url 3000) - domain=$(echo $url | awk -F[/:] '{print $4}') - - if [ -f custom/conf/app.ini ]; then - sed -i "s|^ROOT_URL =.*|ROOT_URL = ${url}/|" custom/conf/app.ini - sed -i "s|^DOMAIN =.*|DOMAIN = ${domain}|" custom/conf/app.ini - sed -i "s|^SSH_DOMAIN =.*|SSH_DOMAIN = ${domain}|" custom/conf/app.ini - sed -i "s|^NO_REPLY_ADDRESS =.*|SSH_DOMAIN = noreply.${domain}|" custom/conf/app.ini - else - mkdir -p custom/conf/ - echo -e "[server]\nROOT_URL = ${url}/" > custom/conf/app.ini - echo -e "\n[database]\nDB_TYPE = sqlite3\nPATH = $GITPOD_REPO_ROOT/data/gitea.db" >> custom/conf/app.ini - fi - export TAGS="sqlite sqlite_unlock_notify" - make watch-backend - - name: Run frontend - command: | - gp sync-await setup - make watch-frontend - openMode: split-right - -vscode: - extensions: - - editorconfig.editorconfig - - dbaeumer.vscode-eslint - - golang.go - - stylelint.vscode-stylelint - - DavidAnson.vscode-markdownlint - - Vue.volar - - ms-azuretools.vscode-docker - - vitest.explorer - - cweijan.vscode-database-client2 - - GitHub.vscode-pull-request-github - -ports: - - name: Gitea - port: 3000 diff --git a/.golangci.yml b/.golangci.yml index 2b85c89fdc..afd91d65e5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -51,8 +51,6 @@ linters: desc: do not use the go-chi cache package, use gitea's cache system - pkg: github.com/pkg/errors desc: use builtin errors package instead - - pkg: github.com/go-ap/errors - desc: use builtin errors package instead nolintlint: allow-unused: false require-explanation: true @@ -141,9 +139,6 @@ linters: - linters: - unused text: (?i)swagger - - linters: - - staticcheck - text: (?i)argument x is overwritten before first use - linters: - gocritic text: '(?i)commentFormatting: put a space between `//` and comment text' diff --git a/AGENTS.md b/AGENTS.md index 402a9d6945..589dea7865 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,10 +1,11 @@ -# Instructions for agents - - Use `make help` to find available development targets -- Before committing `.go` changes, run `make fmt` to format, and run `make lint-go` to lint -- Before committing `.ts` changes, run `make lint-js` to lint -- Before committing `go.mod` changes, run `make tidy` -- Before committing new `.go` files, add the current year into the copyright header -- Before committing any files, remove all trailing whitespace from source code lines -- Never force-push to pull request branches -- Always start issue and pull request comments with an authorship attribution +- Run `make fmt` to format `.go` files, and run `make lint-go` to lint them +- Run `make lint-js` to lint `.ts` files +- Run `make tidy` after any `go.mod` changes +- Add the current year into the copyright header of new `.go` files +- Ensure no trailing whitespace in edited files +- Never force-push, amend, or squash unless asked. Use new commits and normal push for pull request updates +- Preserve existing code comments, do not remove or rewrite comments that are still relevant +- In TypeScript, use `!` (non-null assertion) instead of `?.`/`??` when a value is known to always exist +- Include authorship attribution in issue and pull request comments +- Add `Co-Authored-By` lines to all commits, indicating name and model used diff --git a/CHANGELOG.md b/CHANGELOG.md index f0d93452ae..0fbdf6d9f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,432 @@ This changelog goes through 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.26.0-rc0](https://github.com/go-gitea/gitea/releases/tag/v1.26.0-rc0) - 2026-04-07 + +* BREAKING + * Correct swagger annotations for enums, status codes, and notification state (#37030) + * Remove GET API registration-token (#36801) + * Support Actions `concurrency` syntax (#32751) + * Make PUBLIC_URL_DETECTION default to "auto" (#36955) +* SECURITY + * Bound PageSize in `ListUnadoptedRepositories` (#36884) +* FEATURES + * Support Actions `concurrency` syntax (#32751) + * Add terraform state registry (#36710) + * Instance-wide (global) info banner and maintenance mode (#36571) + * Support rendering OpenAPI spec (#36449) + * Add keyboard shortcuts for repository file and code search (#36416) + * Add support for archive-upload rpc (#36391) + * Add ability to download subpath archive (#36371) + * Add workflow dependencies visualization (#26062) (#36248) & Restyle Workflow Graph (#36912) + * Automatic generation of release notes (#35977) + * Add "Go to file", "Delete Directory" to repo file list page (#35911) + * Introduce "config edit-ini" sub command to help maintaining INI config file (#35735) + * Add button to re-run failed jobs in Actions (#36924) + * Support actions and reusable workflows from private repos (#32562) + * Add summary to action runs view (#36883) + * Add user badges (#36752) + * Add configurable permissions for Actions automatic tokens (#36173) + * Add per-runner “Disable/Pause” (#36776) +* PERFORMANCE + * WorkflowDispatch API optionally return runid (#36706) + * Add render cache for SVG icons (#36863) + * Load `mentionValues` asynchronously (#36739) + * Lazy-load some Vue components, fix heatmap chunk loading on every page (#36719) + * Load heatmap data asynchronously (#36622) + * Use prev/next pagination for user profile activities page to speed up (#36642) + * Refactor cat-file batch operations and support `--batch-command` approach (#35775) + * Use merge tree to detect conflicts when possible (#36400) +* ENHANCEMENTS + * Adds option to force update new branch in contents routes (#35592) + * Add viewer controller for mermaid (zoom, drag) (#36557) + * Add code editor setting dropdowns (#36534) + * Add `elk` layout support to mermaid (#36486) + * Add resolve/unresolve review comment API endpoints (#36441) + * Allow configuring default PR base branch (fixes #36412) (#36425) + * Add support for RPM Errata (updateinfo.xml) (#37125) + * Require additional user confirmation for making repo private (#36959) + * Feature non-zipped actions artifacts (action v7 / nodejs / npm v6.2.0) (#36786) + * Add `actions.WORKFLOW_DIRS` setting (#36619) + * Avoid opening new tab when downloading actions logs (#36740) + * Implements OIDC RP-Initiated Logout (#36724) + * Show workflow link (#37070) + * Desaturate dark theme background colors (#37056) + * Refactor "org teams" page and help new users to "add member" to an org (#37051) + * Add webhook name field to improve webhook identification (#37025) (#37040) + * Make task list checkboxes clickable in the preview tab (#37010) + * Improve severity labels in Actions logs and tweak colors (#36993) + * Linkify URLs in Actions workflow logs (#36986) + * Allow text selection on checkbox labels (#36970) + * Support dark/light theme images in markdown (#36922) + * Enable native dark mode for swagger-ui (#36899) + * Rework checkbox styling, remove `input` border hover effect (#36870) + * Refactor storage content-type handling of ServeDirectURL (#36804) + * Use "Enable Gravatar" but not "Disable" (#36771) + * Use case-insensitive matching for Git error "Not a valid object name" (#36728) + * Add “Copy Source” to markup comment menu (#36726) + * Change image transparency grid to CSS (#36711) + * Add "Run" prefix for unnamed action steps (#36624) + * Persist actions log time display settings in `localStorage` (#36623) + * Use first commit title for multi-commit PRs and fix auto-focus title field (#36606) + * Improve BuildCaseInsensitiveLike with lowercase (#36598) + * Improve diff highlighting (#36583) + * Exclude cancelled runs from failure-only email notifications (#36569) + * Use full-file highlighting for diff sections (#36561) + * Color command/error logs in Actions log (#36538) + * Add paging headers (#36521) + * Improve timeline entries for WIP prefix changes in pull requests (#36518) + * Add FOLDER_ICON_THEME configuration option (#36496) + * Normalize guessed languages for code highlighting (#36450) + * Add chunked transfer encoding support for LFS uploads (#36380) + * Indicate when only optional checks failed (#36367) + * Add 'allow_maintainer_edit' API option for creating a pull request (#36283) + * Support closing keywords with URL references (#36221) + * Improve diff file headers (#36215) + * Fix and enhance comment editor monospace toggle (#36181) + * Add git.DIFF_RENAME_SIMILARITY_THRESHOLD option (#36164) + * Add matching pair insertion to markdown textarea (#36121) + * Add sorting/filtering to admin user search API endpoint (#36112) + * Allow action user have read permission in public repo like other user (#36095) + * Disable matchBrackets in monaco (#36089) + * Use GitHub-style commit message for squash merge (#35987) + * Make composer registry support tar.gz and tar.bz2 and fix bugs (#35958) + * Add GITEA_PR_INDEX env variable to githooks (#35938) + * Add proper error message if session provider can not be created (#35520) + * Add button to copy file name in PR files (#35509) + * Move `X_FRAME_OPTIONS` setting from `cors` to `security` section (#30256) + * Add placeholder content for empty content page (#37114) + * Add `DEFAULT_DELETE_BRANCH_AFTER_MERGE` setting (#36917) + * Redirect to the only OAuth2 provider when no other login methods and fix various problems (#36901) + * Add admin badge to navbar avatar (#36790) + * Add `never` option to `PUBLIC_URL_DETECTION` configuration (#36785) + * Add background and run count to actions list page (#36707) + * Add icon to buttons "Close with Comment", "Close Pull Request", "Close Issue" (#36654) + * Add support for in_progress event in workflow_run webhook (#36979) + * Report commit status for pull_request_review events (#36589) + * Render merged pull request title as such in dashboard feed (#36479) + * Feature to be able to filter project boards by milestones (#36321) + * Use user id in noreply emails (#36550) + * Enable pagination on GiteaDownloader.getIssueReactions() (#36549) + * Remove striped tables in UI (#36509) + * Improve control char rendering and escape button styling (#37094) + * Support legacy run/job index-based URLs and refactor migration 326 (#37008) + * Add date to "No Contributions" tooltip (#36190) + * Show edit page confirmation dialog on tree view file change (#36130) + * Mention proc-receive in text for dashboard.resync_all_hooks func (#35991) + * Reuse selectable style for wiki (#35990) + * Support blue yellow colorblind theme (#35910) + * Support selecting theme on the footer (#35741) + * Improve online runner check (#35722) + * Add quick approve button on PR page (#35678) + * Enable commenting on expanded lines in PR diffs (#35662) + * Print PR-Title into tooltip for actions (#35579) + * Use explicit, stronger defaults for newly generated repo signing keys for Debian (#36236) + * Improve the compare page (#36261) + * Unify repo names in system notices (#36491) + * Move package settings to package instead of being tied to version (#37026) + * Add Actions API rerun endpoints for runs and jobs (#36768) + * Add branch_count to repository API (#35351) (#36743) + * Add created_by filter to SearchIssues (#36670) + * Allow admins to rename non-local users (#35970) + * Support updating branch via API (#35951) + * Add an option to automatically verify SSH keys from LDAP (#35927) + * Make "update file" API can create a new file when SHA is not set (#35738) + * Update issue.go with labels documentation (labels content, not ids) (#35522) + * Expose content_version for optimistic locking on issue and PR edits (#37035) + * Pass ServeHeaderOptions by value instead of pointer, fine tune httplib tests (#36982) +* BUGFIXES + * Fix API not persisting pull request unit config when has_pull_requests is not set (#36718) + * Rename CSS variables and improve colorblind themes (#36353) + * Hide `add-matcher` and `remove-matcher` from actions job logs (#36520) + * Prevent navigation keys from triggering actions during IME composition (#36540) + * Fix vertical alignment of `.commit-sign-badge` children (#36570) + * Fix duplicate startup warnings in admin panel (#36641) + * Fix CODEOWNERS review request attribution using comment metadata (#36348) + * Fix HTML tags appearing in wiki table of contents (#36284) + * Fix various bugs (#37096) + * Fix various legacy problems (#37092) + * Fix RPM Registry 404 when package name contains 'package' (#37087) + * Merge some standalone Vite entries into index.js (#37085) + * Fix various problems (#37077) + * Fix issue label deletion with Actions tokens (#37013) + * Hide delete branch or tag buttons in mirror or archived repositories. (#37006) + * Fix org contact email not clearable once set (#36975) + * Fix a bug when forking a repository in an organization (#36950) + * Preserve sort order of exclusive labels from template repo (#36931) + * Make container registry support Apple Container (basic auth) (#36920) + * Fix the wrong push commits in the pull request when force push (#36914) + * Add class "list-header-filters" to the div for projects (#36889) + * Fix dbfs error handling (#36844) + * Fix incorrect viewed files counter if reverted change was viewed (#36819) + * Refactor avatar package, support default avatar fallback (#36788) + * Fix README symlink resolution in subdirectories like .github (#36775) + * Fix CSS stacking context issue in actions log (#36749) + * Add gpg signing for merge rebase and update by rebase (#36701) + * Delete non-exist branch should return 404 (#36694) + * Fix `TestActionsCollaborativeOwner` (#36657) + * Fix multi-arch Docker build SIGILL by splitting frontend stage (#36646) + * Fix linguist-detectable attribute being ignored for configuration files (#36640) + * Fix state desync in ComboMarkdownEditor (#36625) + * Unify DEFAULT_SHOW_FULL_NAME output in templates and dropdown (#36597) + * Pull Request Pusher should be the author of the merge (#36581) + * Fix various version parsing problems (#36553) + * Fix highlight diff result (#36539) + * Fix mirror sync parser and fix mirror messages (#36504) + * Fix bug when list pull request commits (#36485) + * Fix various bugs (#36446) + * Fix issue filter menu layout (#36426) + * Restrict branch naming when new change matches with protection rules (#36405) + * Fix link/origin referrer and login redirect (#36279) + * Generate IDs for HTML headings without id attribute (#36233) + * Use a migration test instead of a wrong test which populated the meta test repositories and fix a migration bug (#36160) + * Fix issue close timeline icon (#36138) + * Fix diff blob excerpt expansion (#35922) + * Fix external render (#35727) + * Fix review request webhook bug (#35339) (#35723) + * Fix shutdown waitgroup panic (#35676) + * Cleanup ActionRun creation (#35624) + * Fix possible bug when migrating issues/pull requests (#33487) + * Various fixes (#36697) + * Apply notify/register mail flags during install load (#37120) + * Repair duration display for bad stopped timestamps (#37121) + * Fix(upgrade.sh): use HTTPS for GPG key import and restore SELinux context after upgrade (#36930) + * Fix various trivial problems (#36921) + * Fix various trivial problems (#36953) + * Fix NuGet package upload error handling (#37074) + * Fix CodeQL code scanning alerts (#36858) + * Refactor issue sidebar and fix various problems (#37045) + * Fix various problems (#37029) + * Fix relative-time RangeError (#37021) + * Fix chroma lexer mapping (#36629) + * Fix typos and grammar in English locale (#36751) + * Fix milestone/project text overflow in issue sidebar (#36741) + * Fix `no-content` message not rendering after comment edit (#36733) + * Fix theme loading in development (#36605) + * Fix workflow run jobs API returning null steps (#36603) + * Fix timeline event layout overflow with long content (#36595) + * Fix minor UI issues in runner edit page (#36590) + * Fix incorrect vendored detections (#36508) + * Fix editorconfig not respected in PR Conversation view (#36492) + * Don't create self-references in merged PRs (#36490) + * Fix potential incorrect runID in run status update (#36437) + * Fix file-tree ui error when adding files to repo without commits (#36312) + * Improve image captcha contrast for dark mode (#36265) + * Fix panic in blame view when a file has only a single commit (#36230) + * Fix spelling error in migrate-storage cmd utility (#36226) + * Fix code highlighting on blame page (#36157) + * Fix nilnil in onedev downloader (#36154) + * Fix actions lint (#36029) + * Fix oauth2 session gob register (#36017) + * Fix Arch repo pacman.conf snippet (#35825) + * Fix a number of `strictNullChecks`-related issues (#35795) + * Fix URLJoin, markup render link reoslving, sign-in/up/linkaccount page common data (#36861) + * Hide delete directory button for mirror or archive repository and disable the menu item if user have no permission (#36384) + * Update message severity colors, fix navbar double border (#37019) + * Inline and lazy-load EasyMDE CSS, fix border colors (#36714) + * Closed milestones with no issues now show as 100% completed (#36220) + * Add test for ExtendCommentTreePathLength migration and fix bugs (#35791) + * Only turn links to current instance into hash links (#36237) + * Fix typos in code comments: doesnt, dont, wont (#36890) +* REFACTOR + * Replace Monaco with CodeMirror (#36764) + * Replace CSRF cookie with `CrossOriginProtection` (#36183) + * Replace index with id in actions routes (#36842) + * Remove unnecessary function parameter (#35765) + * Move jobparser from act repository to Gitea (#36699) + * Refactor compare router param parse (#36105) + * Optimize 'refreshAccesses' to perform update without removing then adding (#35702) + * Clean up checkbox cursor styles (#37016) + * Remove undocumented support of signing key in the repository git configuration file (#36143) + * Switch `cmd/` to use constructor functions. (#36962) + * Use `relative-time` to render absolute dates (#36238) + * Some refactors about GetMergeBase (#36186) + * Some small refactors (#36163) + * Use gitRepo as parameter instead of repopath when invoking sign functions (#36162) + * Move blame to gitrepo (#36161) + * Move some functions to gitrepo package to reduce RepoPath reference directly (#36126) + * Use gitrepo's clone and push when possible (#36093) + * Remove mermaid margin workaround (#35732) + * Move some functions to gitrepo package (#35543) + * Move GetDiverging functions to gitrepo (#35524) + * Use global lock instead of status pool for cron lock (#35507) + * Use explicit mux instead of DefaultServeMux (#36276) + * Use gitrepo's push function (#36245) + * Pass request context to generateAdditionalHeadersForIssue (#36274) + * Move assign project when creating pull request to the same database transaction (#36244) + * Move catfile batch to a sub package of git module (#36232) + * Use gitrepo.Repository instead of wikipath (#35398) + * Use experimental go json v2 library (#35392) + * Refactor template render (#36438) + * Refactor GetRepoRawDiffForFile to avoid unnecessary pipe or goroutine (#36434) + * Refactor text utility classes to Tailwind CSS (#36703) + * Refactor git command stdio pipe (#36422) + * Refactor git command context & pipeline (#36406) + * Refactor git command stdio pipe (#36393) + * Remove unused functions (#36672) + * Refactor Actions Token Access (#35688) + * Move commit related functions to gitrepo package (#35600) + * Move archive function to repo_model and gitrepo (#35514) + * Move some functions to gitrepo package (#35503) + * Use git model to detect whether branch exist instead of gitrepo method (#35459) + * Some refactor for repo path (#36251) + * Extract helper functions from SearchIssues (#36158) + * Refactor merge conan and container auth preserve actions taskID (#36560) + * Refactor Nuget Auth to reuse Basic Auth Token Validation (#36558) + * Refactor ActionsTaskID (#36503) + * Refactor auth middleware (#36848) + * Refactor code render and render control chars (#37078) + * Clean up AppURL, remove legacy origin-url webcomponent (#37090) + * Remove `util.URLJoin` and replace all callers with direct path concatenation (#36867) + * Replace legacy tw-flex utility classes with flex-text-block/inline (#36778) + * Mark unused&immature activitypub as "not implemented" (#36789) +* TESTING + * Add e2e tests for server push events (#36879) + * Rework e2e tests (#36634) + * Add e2e reaction test, improve accessibility, enable parallel testing (#37081) + * Increase e2e test timeouts on CI to fix flaky tests (#37053) +* BUILD + * Convert locale files from ini to json format (#35489) + * Bump golangci-lint to 2.7.2, enable modernize stringsbuilder (#36180) + * Port away from `flake-utils` (#35675) + * Remove nolint (#36252) + * Update the Unlicense copy to latest version (#36636) + * Update to go 1.26.0 and golangci-lint 2.9.0 (#36588) + * Replace `google/go-licenses` with custom generation (#36575) + * Update go dependencies (#36548) + * Bump appleboy/git-push-action from 1.0.0 to 1.2.0 (#36306) + * Remove fomantic form module (#36222) + * Bump setup-node to v6, re-enable cache (#36207) + * Bump crowdin/github-action from 1 to 2 (#36204) + * Revert "Bump alpine to 3.23 (#36185)" (#36202) + * Update chroma to v2.21.1 (#36201) + * Bump astral-sh/setup-uv from 6 to 7 (#36198) + * Bump docker/build-push-action from 5 to 6 (#36197) + * Bump aws-actions/configure-aws-credentials from 4 to 5 (#36196) + * Bump dev-hanz-ops/install-gh-cli-action from 0.1.0 to 0.2.1 (#36195) + * Add JSON linting (#36192) + * Enable dependabot for actions (#36191) + * Bump alpine to 3.23 (#36185) + * Update chroma to v2.21.0 (#36171) + * Update JS deps and eslint enhancements (#36147) + * Update JS deps (#36091) + * update golangci-lint to v2.7.0 (#36079) + * Update JS deps, fix deprecations (#36040) + * Update JS deps (#35978) + * Add toolchain directive to go.mod (#35901) + * Move `gitea-vet` to use `go tool` (#35878) + * Update to go 1.25.4 (#35877) + * Enable TypeScript `strictNullChecks` (#35843) + * Enable `vue/require-typed-ref` eslint rule (#35764) + * Update JS dependencies (#35759) + * Move `codeformat` folder to tools (#35758) + * Update dependencies (#35733) + * Bump happy-dom from 20.0.0 to 20.0.2 (#35677) + * Bump setup-go to v6 (#35660) + * Update JS deps, misc tweaks (#35643) + * Bump happy-dom from 19.0.2 to 20.0.0 (#35625) + * Use bundled version of spectral (#35573) + * Update JS and PY deps (#35565) + * Bump github.com/wneessen/go-mail from 0.6.2 to 0.7.1 (#35557) + * Migrate from webpack to vite (#37002) + * Update JS dependencies and misc tweaks (#37064) + * Update to eslint 10 (#36925) + * Optimize Docker build with dependency layer caching (#36864) + * Update JS deps (#36850) + * Update tool dependencies and fix new lint issues (#36702) + * Remove redundant linter rules (#36658) + * Move Fomantic dropdown CSS to custom module (#36530) + * Remove and forbid `@ts-expect-error` (#36513) + * Refactor git command stderr handling (#36402) + * Enable gocheckcompilerdirectives linter (#36156) + * Replace `lint-go-gopls` with additional `govet` linters (#36028) + * Update golangci-lint to v2.6.0 (#35801) + * Misc tool tweaks (#35734) + * Add cache to container build (#35697) + * Upgrade vite (#37126) + * Update `setup-uv` to v8.0.0 (#37101) + * Upgrade `go-git` to v5.17.2 and related dependencies (#37060) + * Raise minimum Node.js version to 22.18.0 (#37058) + * Upgrade `golang.org/x/image` to v0.38.0 (#37054) + * Update minimum go version to 1.26.1, golangci-lint to 2.11.2, fix test style (#36876) + * Enable eslint concurrency (#36878) + * Vendor relative-time-element as local web component (#36853) + * Update material-icon-theme v5.32.0 (#36832) + * Update Go dependencies (#36781) + * Upgrade minimatch (#36760) + * Remove i18n backport tool at the moment because of translation format changed (#36643) + * Update emoji data for Unicode 16 (#36596) + * Update JS dependencies, adjust webpack config, misc fixes (#36431) + * Update material-icon-theme to v5.31.0 (#36427) + * Update JS and PY deps (#36383) + * Bump alpine to 3.23, add platforms to `docker-dryrun` (#36379) + * Update JS deps (#36354) + * Update goldmark to v1.7.16 (#36343) + * Update chroma to v2.22.0 (#36342) +* DOCS + * Update AI Contribution Policy (#37022) + * Update AGENTS.md with additional guidelines (#37018) + * Add missing cron tasks to example ini (#37012) + * Add AI Contribution Policy to CONTRIBUTING.md (#36651) + * Minor punctuation improvement in CONTRIBUTING.md (#36291) + * Add documentation for markdown anchor post-processing (#36443) +* MISC + * Correct spelling (#36783) + * Update Nix flake (#37110) + * Update Nix flake (#37024) + * Add valid github scopes (#36977) + * Update Nix flake (#36943) + * Update Nix flake (#36902) + * Update Nix flake (#36857) + * Update Nix flake (#36787) + +## [1.25.5](https://github.com/go-gitea/gitea/releases/tag/v1.25.5) - 2026-03-10 + +* SECURITY + * Toolchain Update to Go 1.25.6 (#36480) (#36487) + * Adjust the toolchain version (#36537) (#36542) + * Update toolchain to 1.25.8 for v1.25 (#36888) + * Prevent redirect bypasses via backslash-encoded paths (#36660) (#36716) + * Fix get release draft permission check (#36659) (#36715) + * Fix a bug user could change another user's primary email (#36586) (#36607) + * Fix OAuth2 authorization code expiry and reuse handling (#36797) (#36851) + * Add validation constraints for repository creation fields (#36671) (#36757) + * Fix bug to check whether user can update pull request branch or rebase branch (#36465) (#36838) + * Add migration http transport for push/sync mirror lfs (#36665) (#36691) + * Fix track time list permission check (#36662) (#36744) + * Fix track time issue id (#36664) (#36689) + * Fix path resolving (#36734) (#36746) + * Fix dump release asset bug (#36799) (#36839) + * Fix org permission API visibility checks for hidden members and private orgs (#36798) (#36841) + * Fix forwarded proto handling for public URL detection (#36810) (#36836) + * Add a git grep search timeout (#36809) (#36835) + * Fix oauth2 s256 (#36462) (#36477) +* ENHANCEMENTS + * Make `security-check` informational only (#36681) (#36852) + * Upgrade to github.com/cloudflare/circl 1.6.3, svgo 4.0.1, markdownlint-cli 0.48.0 (#36840) + * Add some validation on values provided to USER_DISABLED_FEATURES and EXTERNAL_USER_DISABLED_FEATURES (#36688) (#36692) + * Upgrade gogit to 5.16.5 (#36687) + * Add wrap to runner label list (#36565) (#36574) + * Add dnf5 command for Fedora in RPM package instructions (#36527) (#36572) + * Allow scroll propagation outside code editor (#36502) (#36510) +* BUGFIXES + * Fix non-admins unable to automerge PRs from forks (#36833) (#36843) + * Fix bug when pushing mirror with wiki (#36795) (#36807) + * Fix artifacts v4 backend upload problems (#36805) (#36834) + * Fix CRAN package version validation to allow more than 4 version components (#36813) (#36821) + * Fix force push time-line commit comments of pull request (#36653) (#36717) + * Fix SVG height calculation in diff viewer (#36748) (#36750) + * Fix push time bug (#36693) (#36713) + * Fix bug the protected branch rule name is conflicted with renamed branch name (#36650) (#36661) + * Fix bug when do LFS GC (#36500) (#36608) + * Fix focus lost bugs in the Monaco editor (#36609) + * Reprocess htmx content after loading more files (#36568) (#36577) + * Fix assignee sidebar links and empty placeholder (#36559) (#36563) + * Fix issues filter dropdown showing empty label scope section (#36535) (#36544) + * Fix various mermaid bugs (#36547) (#36552) + * Fix data race when uploading container blobs concurrently (#36524) (#36526) + * Correct spacing between username and bot label (#36473) (#36484) + ## [1.25.4](https://github.com/go-gitea/gitea/releases/tag/v1.25.4) - 2026-01-15 * SECURITY diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33b329182c..4b32631d6a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,16 +70,18 @@ For configuring IDEs for Gitea development, see the [contributed IDE configurati ## AI Contribution Policy -Contributions made with the assistance of AI tools are welcome, but contributors must use them responsibly. +Contributions made with the assistance of AI tools are welcome, but contributors must use them responsibly and disclose that use clearly. -1. Include related issues or pull requests in the prompt so that the AI has ideal context. -2. Review AI-generated code closely before submitting a pull request. -3. Manually test the changes and add appropriate automated tests where feasible. -4. Only use AI to assist in contributions that you understand well enough to respond to feedback without relying on AI. -5. Indicate AI-generated content in issue and pull requests descriptions and comments. Specify which model was used. -6. Do not use AI to reply to questions about your issue or pull request. The questions are for you, not an AI model. +1. Review AI-generated code closely before marking a pull request ready for review. +2. Manually test the changes and add appropriate automated tests where feasible. +3. Only use AI to assist in contributions that you understand well enough to explain, defend, and revise yourself during review. +4. Disclose AI-assisted content clearly. +5. Do not use AI to reply to questions about your issue or pull request. The questions are for you, not an AI model. +6. AI may be used to help draft issues and pull requests, but contributors remain responsible for the accuracy, completeness, and intent of what they submit. -Maintainers reserve the right to close pull requests and issues that appear to be low-quality AI-generated content. We welcome new contributors, but cannot sustain the effort of supporting contributors who primarily defer to AI rather than engaging substantively with the review process. +Maintainers reserve the right to close pull requests and issues that do not disclose AI assistance, that appear to be low-quality AI-generated content, or where the contributor cannot explain or defend the proposed changes themselves. + +We welcome new contributors, but cannot sustain the effort of supporting contributors who primarily defer to AI rather than engaging substantively with the review process. ## Issues @@ -196,10 +198,11 @@ Here's how to run the test suite: - E2E test environment variables -| Variable | Description | -| :------------------------ | :---------------------------------------------------------------- | -| ``GITEA_TEST_E2E_DEBUG`` | When set, show Gitea server output | -| ``GITEA_TEST_E2E_FLAGS`` | Additional flags passed to Playwright, for example ``--ui`` | +| Variable | Description | +| :-------------------------------- | :---------------------------------------------------------- | +| ``GITEA_TEST_E2E_DEBUG`` | When set, show Gitea server output | +| ``GITEA_TEST_E2E_FLAGS`` | Additional flags passed to Playwright, for example ``--ui`` | +| ``GITEA_TEST_E2E_TIMEOUT_FACTOR`` | Timeout multiplier (default: 3 on CI, 1 locally) | ## Translation diff --git a/Dockerfile b/Dockerfile index f71b13e8f3..323f06125f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,16 @@ # syntax=docker/dockerfile:1 -# Build frontend on the native platform to avoid QEMU-related issues with esbuild/webpack +# Build frontend on the native platform to avoid QEMU-related issues with nodejs ecosystem FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26-alpine3.23 AS frontend-build RUN apk --no-cache add build-base git nodejs pnpm WORKDIR /src +COPY package.json pnpm-lock.yaml .npmrc ./ +RUN --mount=type=cache,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile COPY --exclude=.git/ . . -RUN --mount=type=cache,target=/root/.local/share/pnpm/store make frontend +RUN make frontend # Build backend for each target platform FROM docker.io/library/golang:1.26-alpine3.23 AS build-env -ARG GOPROXY=direct - ARG GITEA_VERSION ARG TAGS="sqlite sqlite_unlock_notify" ENV TAGS="bindata timetzdata $TAGS" @@ -22,14 +22,15 @@ RUN apk --no-cache add \ git WORKDIR ${GOPATH}/src/code.gitea.io/gitea +COPY go.mod go.sum ./ +RUN go mod download # Use COPY instead of bind mount as read-only one breaks makefile state tracking and read-write one needs binary to be moved as it's discarded. # ".git" directory is mounted separately later only for version data extraction. COPY --exclude=.git/ . . COPY --from=frontend-build /src/public/assets public/assets # Build gitea, .git mount is required for version data -RUN --mount=type=cache,target=/go/pkg/mod \ - --mount=type=cache,target="/root/.cache/go-build" \ +RUN --mount=type=cache,target="/root/.cache/go-build" \ --mount=type=bind,source=".git/",target=".git/" \ make backend diff --git a/Dockerfile.rootless b/Dockerfile.rootless index bc210132c5..83c69cbd51 100644 --- a/Dockerfile.rootless +++ b/Dockerfile.rootless @@ -1,16 +1,16 @@ # syntax=docker/dockerfile:1 -# Build frontend on the native platform to avoid QEMU-related issues with esbuild/webpack +# Build frontend on the native platform to avoid QEMU-related issues with nodejs ecosystem FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26-alpine3.23 AS frontend-build RUN apk --no-cache add build-base git nodejs pnpm WORKDIR /src +COPY package.json pnpm-lock.yaml .npmrc ./ +RUN --mount=type=cache,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile COPY --exclude=.git/ . . -RUN --mount=type=cache,target=/root/.local/share/pnpm/store make frontend +RUN make frontend # Build backend for each target platform FROM docker.io/library/golang:1.26-alpine3.23 AS build-env -ARG GOPROXY=direct - ARG GITEA_VERSION ARG TAGS="sqlite sqlite_unlock_notify" ENV TAGS="bindata timetzdata $TAGS" @@ -22,13 +22,14 @@ RUN apk --no-cache add \ git WORKDIR ${GOPATH}/src/code.gitea.io/gitea +COPY go.mod go.sum ./ +RUN go mod download # See the comments in Dockerfile COPY --exclude=.git/ . . COPY --from=frontend-build /src/public/assets public/assets # Build gitea, .git mount is required for version data -RUN --mount=type=cache,target=/go/pkg/mod \ - --mount=type=cache,target="/root/.cache/go-build" \ +RUN --mount=type=cache,target="/root/.cache/go-build" \ --mount=type=bind,source=".git/",target=".git/" \ make backend diff --git a/MAINTAINERS b/MAINTAINERS index 1c7afc6f6c..26192eff44 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -64,3 +64,4 @@ metiftikci (@metiftikci) Christopher Homberger (@ChristopherHX) Tobias Balle-Petersen (@tobiasbp) TheFox (@TheFox0x7) +Nicolas (@bircni) \ No newline at end of file diff --git a/Makefile b/Makefile index cb7742c5c7..8c73dc350d 100644 --- a/Makefile +++ b/Makefile @@ -15,13 +15,13 @@ XGO_VERSION := go-1.25.x AIR_PACKAGE ?= github.com/air-verse/air@v1 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2 -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.9.0 +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 -MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0 +MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1 XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 -ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.10 +ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.11 DOCKER_IMAGE ?= gitea/gitea DOCKER_TAG ?= latest @@ -81,15 +81,6 @@ STORED_VERSION_FILE := VERSION GITHUB_REF_TYPE ?= branch GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) -# Enable typescript support in Node.js before 22.18 -# TODO: Remove this once we can raise the minimum Node.js version to 22.18 (alpine >= 3.23) -NODE_VERSION := $(shell printf "%03d%03d%03d" $(shell node -v 2>/dev/null | cut -c2- | sed 's/-.*//' | tr '.' ' ')) -ifeq ($(shell test "$(NODE_VERSION)" -lt "022018000"; echo $$?),0) - NODE_VARS := NODE_OPTIONS="--experimental-strip-types" -else - NODE_VARS := -endif - ifneq ($(GITHUB_REF_TYPE),branch) VERSION ?= $(subst v,,$(GITHUB_REF_NAME)) GITEA_VERSION ?= $(VERSION) @@ -120,10 +111,11 @@ LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/r 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,$(shell $(GO) list ./... | grep -v /vendor/)) MIGRATE_TEST_PACKAGES ?= $(shell $(GO) list code.gitea.io/gitea/models/migrations/...) -WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f) -WEBPACK_CONFIGS := webpack.config.ts tailwind.config.ts -WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css -WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts +FRONTEND_SOURCES := $(shell find web_src/js web_src/css -type f) +FRONTEND_CONFIGS := vite.config.ts tailwind.config.ts +FRONTEND_DEST := public/assets/.vite/manifest.json +FRONTEND_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/.vite +FRONTEND_DEV_LOG_LEVEL ?= warn BINDATA_DEST_WILDCARD := modules/migration/bindata.* modules/public/bindata.* modules/options/bindata.* modules/templates/bindata.* @@ -155,6 +147,7 @@ GO_SOURCES := $(wildcard *.go) GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go") GO_SOURCES += $(GENERATED_GO_DEST) +ESLINT_CONCURRENCY ?= 2 SWAGGER_SPEC := templates/swagger/v1_json.tmpl SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json @@ -198,7 +191,7 @@ git-check: .PHONY: clean-all clean-all: clean ## delete backend, frontend and integration files - rm -rf $(WEBPACK_DEST_ENTRIES) node_modules + rm -rf $(FRONTEND_DEST_ENTRIES) node_modules .PHONY: clean clean: ## delete backend and integration files @@ -292,41 +285,41 @@ lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backen .PHONY: lint-js lint-js: node_modules ## lint js and ts files - $(NODE_VARS) pnpm exec eslint --color --max-warnings=0 $(ESLINT_FILES) - $(NODE_VARS) pnpm exec vue-tsc + pnpm exec eslint --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) $(ESLINT_FILES) + pnpm exec vue-tsc .PHONY: lint-js-fix lint-js-fix: node_modules ## lint js and ts files and fix issues - $(NODE_VARS) pnpm exec eslint --color --max-warnings=0 $(ESLINT_FILES) --fix - $(NODE_VARS) pnpm exec vue-tsc + pnpm exec eslint --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) $(ESLINT_FILES) --fix + pnpm exec vue-tsc .PHONY: lint-css lint-css: node_modules ## lint css files - $(NODE_VARS) pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES) + pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES) .PHONY: lint-css-fix lint-css-fix: node_modules ## lint css files and fix issues - $(NODE_VARS) pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix + pnpm exec stylelint --color --max-warnings=0 $(STYLELINT_FILES) --fix .PHONY: lint-swagger lint-swagger: node_modules ## lint swagger files - $(NODE_VARS) pnpm exec spectral lint -q -F hint $(SWAGGER_SPEC) + pnpm exec spectral lint -q -F hint $(SWAGGER_SPEC) .PHONY: lint-md lint-md: node_modules ## lint markdown files - $(NODE_VARS) pnpm exec markdownlint *.md + pnpm exec markdownlint *.md .PHONY: lint-md-fix lint-md-fix: node_modules ## lint markdown files and fix issues - $(NODE_VARS) pnpm exec markdownlint --fix *.md + pnpm exec markdownlint --fix *.md .PHONY: lint-spell lint-spell: ## lint spelling - @go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -error $(SPELLCHECK_FILES) + @git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -error .PHONY: lint-spell-fix lint-spell-fix: ## lint spelling and fix issues - @go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -w $(SPELLCHECK_FILES) + @git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -w .PHONY: lint-go lint-go: ## lint go files @@ -368,20 +361,19 @@ lint-yaml: .venv ## lint yaml files .PHONY: lint-json lint-json: node_modules ## lint json files - $(NODE_VARS) pnpm exec eslint -c eslint.json.config.ts --color --max-warnings=0 + pnpm exec eslint -c eslint.json.config.ts --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) .PHONY: lint-json-fix lint-json-fix: node_modules ## lint and fix json files - $(NODE_VARS) pnpm exec eslint -c eslint.json.config.ts --color --max-warnings=0 --fix + pnpm exec eslint -c eslint.json.config.ts --color --max-warnings=0 --concurrency $(ESLINT_CONCURRENCY) --fix .PHONY: watch watch: ## watch everything and continuously rebuild @bash tools/watch.sh .PHONY: watch-frontend -watch-frontend: node_modules ## watch frontend files and continuously rebuild - @rm -rf $(WEBPACK_DEST_ENTRIES) - NODE_ENV=development $(NODE_VARS) pnpm exec webpack --watch --progress --disable-interpret +watch-frontend: node_modules ## start vite dev server for frontend + NODE_ENV=development pnpm exec vite --logLevel $(FRONTEND_DEV_LOG_LEVEL) .PHONY: watch-backend watch-backend: ## watch backend files and continuously rebuild @@ -397,7 +389,7 @@ test-backend: ## test backend files .PHONY: test-frontend test-frontend: node_modules ## test frontend files - $(NODE_VARS) pnpm exec vitest + pnpm exec vitest .PHONY: test-check test-check: @@ -533,7 +525,7 @@ test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test .PHONY: playwright playwright: deps-frontend @# on GitHub Actions VMs, playwright's system deps are pre-installed - @$(NODE_VARS) pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium $(if $(CI),firefox) $(PLAYWRIGHT_FLAGS) + @pnpm exec playwright install $(if $(GITHUB_ACTIONS),,--with-deps) chromium firefox $(PLAYWRIGHT_FLAGS) .PHONY: test-e2e test-e2e: playwright $(EXECUTABLE_E2E) @@ -644,7 +636,7 @@ install: $(wildcard *.go) build: frontend backend ## build everything .PHONY: frontend -frontend: $(WEBPACK_DEST) ## build frontend files +frontend: $(FRONTEND_DEST) ## build frontend files .PHONY: backend backend: generate-backend $(EXECUTABLE) ## build backend files @@ -671,7 +663,7 @@ ifneq ($(and $(STATIC),$(findstring pam,$(TAGS))),) endif CGO_ENABLED="$(CGO_ENABLED)" CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@ -$(EXECUTABLE_E2E): $(GO_SOURCES) +$(EXECUTABLE_E2E): $(GO_SOURCES) $(FRONTEND_DEST) CGO_ENABLED=1 $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TEST_TAGS)' -ldflags '-s -w $(EXTLDFLAGS) $(LDFLAGS)' -o $@ .PHONY: release @@ -749,7 +741,7 @@ deps-tools: ## install tool dependencies wait node_modules: pnpm-lock.yaml - $(NODE_VARS) pnpm install --frozen-lockfile + pnpm install --frozen-lockfile @touch node_modules .venv: uv.lock @@ -757,33 +749,38 @@ node_modules: pnpm-lock.yaml @touch .venv .PHONY: update -update: update-js update-py ## update js and py dependencies +update: update-go update-js update-py ## update dependencies + +.PHONY: update-go +update-go: ## update go dependencies + $(GO) get -u ./... + $(MAKE) tidy .PHONY: update-js update-js: node_modules ## update js dependencies - $(NODE_VARS) pnpm exec updates -u -f package.json + pnpm exec updates -u -f package.json rm -rf node_modules pnpm-lock.yaml - $(NODE_VARS) pnpm install - $(NODE_VARS) pnpm exec nolyfill install - $(NODE_VARS) pnpm install + pnpm install + pnpm exec nolyfill install + pnpm install @touch node_modules .PHONY: update-py update-py: node_modules ## update py dependencies - $(NODE_VARS) pnpm exec updates -u -f pyproject.toml + pnpm exec updates -u -f pyproject.toml rm -rf .venv uv.lock uv sync @touch .venv -.PHONY: webpack -webpack: $(WEBPACK_DEST) ## build webpack files +.PHONY: vite +vite: $(FRONTEND_DEST) ## build vite files -$(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) pnpm-lock.yaml +$(FRONTEND_DEST): $(FRONTEND_SOURCES) $(FRONTEND_CONFIGS) pnpm-lock.yaml @$(MAKE) -s node_modules - @rm -rf $(WEBPACK_DEST_ENTRIES) - @echo "Running webpack..." - @BROWSERSLIST_IGNORE_OLD_DATA=true $(NODE_VARS) pnpm exec webpack --disable-interpret - @touch $(WEBPACK_DEST) + @rm -rf $(FRONTEND_DEST_ENTRIES) + @echo "Running vite build..." + @pnpm exec vite build + @touch $(FRONTEND_DEST) .PHONY: svg svg: node_modules ## build svg files @@ -802,7 +799,7 @@ svg-check: svg .PHONY: lockfile-check lockfile-check: - $(NODE_VARS) pnpm install --frozen-lockfile + pnpm install --frozen-lockfile @diff=$$(git diff --color=always pnpm-lock.yaml); \ if [ -n "$$diff" ]; then \ echo "pnpm-lock.yaml is inconsistent with package.json"; \ diff --git a/README.md b/README.md index ed000971a7..7ebeac97be 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ [![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source") [![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea") [![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT") -[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea) [![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin") [繁體中文](./README.zh-tw.md) | [简体中文](./README.zh-cn.md) diff --git a/README.zh-cn.md b/README.zh-cn.md index 8d9531e8e4..8ccacc0fea 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -8,7 +8,6 @@ [![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source") [![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea") [![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT") -[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea) [![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin") [English](./README.md) | [繁體中文](./README.zh-tw.md) diff --git a/README.zh-tw.md b/README.zh-tw.md index 875d31e28a..4160fd0bd9 100644 --- a/README.zh-tw.md +++ b/README.zh-tw.md @@ -8,7 +8,6 @@ [![](https://www.codetriage.com/go-gitea/gitea/badges/users.svg)](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source") [![](https://opencollective.com/gitea/tiers/backers/badge.svg?label=backers&color=brightgreen)](https://opencollective.com/gitea "Become a backer/sponsor of gitea") [![](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT "License: MIT") -[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green)](https://gitpod.io/#https://github.com/go-gitea/gitea) [![](https://badges.crowdin.net/gitea/localized.svg)](https://translate.gitea.com "Crowdin") [English](./README.md) | [简体中文](./README.zh-cn.md) diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 03fdbcc0b2..61bf76702c 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -39,11 +39,6 @@ "path": "filippo.io/edwards25519/LICENSE", "licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, - { - "name": "git.sr.ht/~mariusor/go-xsd-duration", - "path": "git.sr.ht/~mariusor/go-xsd-duration/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2019 Go xsd:duration\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" - }, { "name": "gitea.com/go-chi/binding", "path": "gitea.com/go-chi/binding/LICENSE", @@ -144,6 +139,11 @@ "path": "github.com/andybalholm/brotli/LICENSE", "licenseText": "Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" }, + { + "name": "github.com/andybalholm/brotli/flate", + "path": "github.com/andybalholm/brotli/flate/LICENSE", + "licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + }, { "name": "github.com/andybalholm/cascadia", "path": "github.com/andybalholm/cascadia/LICENSE", @@ -374,6 +374,16 @@ "path": "github.com/chi-middleware/proxy/LICENSE", "licenseText": "Copyright (c) 2020 Lauris BH\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" }, + { + "name": "github.com/clipperhouse/displaywidth", + "path": "github.com/clipperhouse/displaywidth/LICENSE", + "licenseText": "MIT License\n\nCopyright (c) 2025 Matt Sherman\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/clipperhouse/uax29/v2", + "path": "github.com/clipperhouse/uax29/v2/LICENSE", + "licenseText": "MIT License\n\nCopyright (c) 2020 Matt Sherman\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/cloudflare/circl", "path": "github.com/cloudflare/circl/LICENSE", @@ -391,8 +401,8 @@ }, { "name": "github.com/couchbase/goutils", - "path": "github.com/couchbase/goutils/LICENSE.md", - "licenseText": "Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n" + "path": "github.com/couchbase/goutils/LICENSE.txt", + "licenseText": "Source code in this repository is licensed under various licenses. The\nBusiness Source License 1.1 (BSL) is one such license. Each file indicates in\na section at the beginning of the file the name of the license that applies to\nit. All licenses used in this repository can be found in the top-level\nlicenses directory.\n" }, { "name": "github.com/cpuguy83/go-md2man/v2", @@ -401,8 +411,8 @@ }, { "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-2024 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" + "path": "github.com/cyphar/filepath-securejoin/COPYING.md", + "licenseText": "## COPYING ##\n\n`SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0`\n\nThis project is made up of code licensed under different licenses. Which code\nyou use will have an impact on whether only one or both licenses apply to your\nusage of this library.\n\nNote that **each file** in this project individually has a code comment at the\nstart describing the license of that particular file -- this is the most\naccurate license information of this project; in case there is any conflict\nbetween this document and the comment at the start of a file, the comment shall\ntake precedence. The only purpose of this document is to work around [a known\ntechnical limitation of pkg.go.dev's license checking tool when dealing with\nnon-trivial project licenses][go75067].\n\n[go75067]: https://go.dev/issue/75067\n\n### `BSD-3-Clause` ###\n\nAt time of writing, the following files and directories are licensed under the\nBSD-3-Clause license:\n\n * `doc.go`\n * `join*.go`\n * `vfs.go`\n * `internal/consts/*.go`\n * `pathrs-lite/internal/gocompat/*.go`\n * `pathrs-lite/internal/kernelversion/*.go`\n\nThe text of the BSD-3-Clause license used by this project is the following (the\ntext is also available from the [`LICENSE.BSD`](./LICENSE.BSD) file):\n\n```\nCopyright (C) 2014-2015 Docker Inc \u0026 Go Authors. All rights reserved.\nCopyright (C) 2017-2024 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```\n\n### `MPL-2.0` ###\n\nAll other files (unless otherwise marked) are licensed under the Mozilla Public\nLicense (version 2.0).\n\nThe text of the Mozilla Public License (version 2.0) is the following (the text\nis also available from the [`LICENSE.MPL-2.0`](./LICENSE.MPL-2.0) file):\n\n```\nMozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n means each individual or legal entity that creates, contributes to\n the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n means the combination of the Contributions of others (if any) used\n by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n means Source Code Form to which the initial Contributor has attached\n the notice in Exhibit A, the Executable Form of such Source Code\n Form, and Modifications of such Source Code Form, in each case\n including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n means\n\n (a) that the initial Contributor has attached the notice described\n in 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\n terms of a Secondary License.\n\n1.6. \"Executable Form\"\n means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n means a work that combines Covered Software with other material, in\n a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n means this document.\n\n1.9. \"Licensable\"\n means having the right to grant, to the maximum extent possible,\n whether at the time of the initial grant or subsequently, any and\n all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\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\n Software; or\n\n (b) any new file in Source Code Form that contains any Covered\n Software.\n\n1.11. \"Patent Claims\" of a Contributor\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\n License, by the making, using, selling, offering for sale, having\n made, import, or transfer of either its Contributions or its\n Contributor Version.\n\n1.12. \"Secondary License\"\n means either the GNU General Public License, Version 2.0, the GNU\n Lesser General Public License, Version 2.1, the GNU Affero General\n Public License, Version 3.0, or any later versions of those\n licenses.\n\n1.13. \"Source Code Form\"\n means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n means an individual or a legal entity exercising rights under this\n License. For legal entities, \"You\" includes any entity that\n controls, is controlled by, or is under common control with You. For\n purposes of this definition, \"control\" means (a) the power, direct\n or indirect, to cause the direction or management of such entity,\n whether by contract or otherwise, or (b) ownership of more than\n fifty percent (50%) of the outstanding shares or beneficial\n ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-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\n for sale, have made, import, and otherwise transfer either its\n Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n 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\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n Form, as described in Section 3.1, and You must inform recipients of\n the Executable Form how they can obtain a copy of such Source Code\n Form by reasonable means in a timely manner, at a charge no more\n than the cost 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\n the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n* *\n* 6. Disclaimer of Warranty *\n* ------------------------- *\n* *\n* Covered Software is provided under this License on an \"as is\" *\n* basis, without warranty of any kind, either expressed, implied, or *\n* statutory, including, without limitation, warranties that the *\n* Covered Software is free of defects, merchantable, fit for a *\n* particular purpose or non-infringing. The entire risk as to the *\n* quality and performance of the Covered Software is with You. *\n* Should any Covered Software prove defective in any respect, You *\n* (not any Contributor) assume the cost of any necessary servicing, *\n* repair, or correction. This disclaimer of warranty constitutes an *\n* essential part of this License. No use of any Covered Software is *\n* authorized under this License except under this disclaimer. *\n* *\n************************************************************************\n\n************************************************************************\n* *\n* 7. Limitation of Liability *\n* -------------------------- *\n* *\n* Under no circumstances and under no legal theory, whether tort *\n* (including negligence), contract, or otherwise, shall any *\n* Contributor, or anyone who distributes Covered Software as *\n* permitted above, be liable to You for any direct, indirect, *\n* special, incidental, or consequential damages of any character *\n* including, without limitation, damages for lost profits, loss of *\n* goodwill, work stoppage, computer failure or malfunction, or any *\n* and all other commercial damages or losses, even if such party *\n* shall have been informed of the possibility of such damages. This *\n* limitation of liability shall not apply to liability for death or *\n* personal injury resulting from such party's negligence to the *\n* extent applicable law prohibits such limitation. Some *\n* jurisdictions do not allow the exclusion or limitation of *\n* incidental or consequential damages, so this exclusion and *\n* limitation may not apply to You. *\n* *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n This Source Code Form is subject to the terms of the Mozilla Public\n License, v. 2.0. If a copy of the MPL was not distributed with this\n file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n This Source Code Form is \"Incompatible With Secondary Licenses\", as\n defined by the Mozilla Public License, v. 2.0.\n```\n" }, { "name": "github.com/davecgh/go-spew", @@ -489,21 +499,6 @@ "path": "github.com/gliderlabs/ssh/LICENSE", "licenseText": "Copyright (c) 2016 Glider Labs. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Glider Labs nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, - { - "name": "github.com/go-ap/activitypub", - "path": "github.com/go-ap/activitypub/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2017 Golang ActitvityPub\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" - }, - { - "name": "github.com/go-ap/errors", - "path": "github.com/go-ap/errors/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2019 Golang ActitvityPub\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" - }, - { - "name": "github.com/go-ap/jsonld", - "path": "github.com/go-ap/jsonld/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2017 Marius Orcsik\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" - }, { "name": "github.com/go-asn1-ber/asn1-ber", "path": "github.com/go-asn1-ber/asn1-ber/LICENSE", @@ -520,8 +515,8 @@ "licenseText": "Copyright (c) 2014 Olivier Poitrey \u003crs@dailymotion.com\u003e\nCopyright (c) 2016-Present https://github.com/go-chi authors\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/go-co-op/gocron", - "path": "github.com/go-co-op/gocron/LICENSE", + "name": "github.com/go-co-op/gocron/v2", + "path": "github.com/go-co-op/gocron/v2/LICENSE", "licenseText": "MIT License\n\nCopyright (c) 2014, 辣椒面\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" }, { @@ -569,10 +564,15 @@ "path": "github.com/go-sql-driver/mysql/LICENSE", "licenseText": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n means each individual or legal entity that creates, contributes to\n the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n means the combination of the Contributions of others (if any) used\n by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n means Source Code Form to which the initial Contributor has attached\n the notice in Exhibit A, the Executable Form of such Source Code\n Form, and Modifications of such Source Code Form, in each case\n including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n means\n\n (a) that the initial Contributor has attached the notice described\n in 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\n terms of a Secondary License.\n\n1.6. \"Executable Form\"\n means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n means a work that combines Covered Software with other material, in \n a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n means this document.\n\n1.9. \"Licensable\"\n means having the right to grant, to the maximum extent possible,\n whether at the time of the initial grant or subsequently, any and\n all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\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\n Software; or\n\n (b) any new file in Source Code Form that contains any Covered\n Software.\n\n1.11. \"Patent Claims\" of a Contributor\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\n License, by the making, using, selling, offering for sale, having\n made, import, or transfer of either its Contributions or its\n Contributor Version.\n\n1.12. \"Secondary License\"\n means either the GNU General Public License, Version 2.0, the GNU\n Lesser General Public License, Version 2.1, the GNU Affero General\n Public License, Version 3.0, or any later versions of those\n licenses.\n\n1.13. \"Source Code Form\"\n means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n means an individual or a legal entity exercising rights under this\n License. For legal entities, \"You\" includes any entity that\n controls, is controlled by, or is under common control with You. For\n purposes of this definition, \"control\" means (a) the power, direct\n or indirect, to cause the direction or management of such entity,\n whether by contract or otherwise, or (b) ownership of more than\n fifty percent (50%) of the outstanding shares or beneficial\n ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-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\n for sale, have made, import, and otherwise transfer either its\n Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n 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\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n Form, as described in Section 3.1, and You must inform recipients of\n the Executable Form how they can obtain a copy of such Source Code\n Form by reasonable means in a timely manner, at a charge no more\n than the cost 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\n the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n* *\n* 6. Disclaimer of Warranty *\n* ------------------------- *\n* *\n* Covered Software is provided under this License on an \"as is\" *\n* basis, without warranty of any kind, either expressed, implied, or *\n* statutory, including, without limitation, warranties that the *\n* Covered Software is free of defects, merchantable, fit for a *\n* particular purpose or non-infringing. The entire risk as to the *\n* quality and performance of the Covered Software is with You. *\n* Should any Covered Software prove defective in any respect, You *\n* (not any Contributor) assume the cost of any necessary servicing, *\n* repair, or correction. This disclaimer of warranty constitutes an *\n* essential part of this License. No use of any Covered Software is *\n* authorized under this License except under this disclaimer. *\n* *\n************************************************************************\n\n************************************************************************\n* *\n* 7. Limitation of Liability *\n* -------------------------- *\n* *\n* Under no circumstances and under no legal theory, whether tort *\n* (including negligence), contract, or otherwise, shall any *\n* Contributor, or anyone who distributes Covered Software as *\n* permitted above, be liable to You for any direct, indirect, *\n* special, incidental, or consequential damages of any character *\n* including, without limitation, damages for lost profits, loss of *\n* goodwill, work stoppage, computer failure or malfunction, or any *\n* and all other commercial damages or losses, even if such party *\n* shall have been informed of the possibility of such damages. This *\n* limitation of liability shall not apply to liability for death or *\n* personal injury resulting from such party's negligence to the *\n* extent applicable law prohibits such limitation. Some *\n* jurisdictions do not allow the exclusion or limitation of *\n* incidental or consequential damages, so this exclusion and *\n* limitation may not apply to You. *\n* *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n This Source Code Form is subject to the terms of the Mozilla Public\n License, v. 2.0. If a copy of the MPL was not distributed with this\n file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n This Source Code Form is \"Incompatible With Secondary Licenses\", as\n defined by the Mozilla Public License, v. 2.0.\n" }, + { + "name": "github.com/go-viper/mapstructure/v2", + "path": "github.com/go-viper/mapstructure/v2/LICENSE", + "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2013 Mitchell Hashimoto\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" + }, { "name": "github.com/go-webauthn/webauthn", "path": "github.com/go-webauthn/webauthn/LICENSE", - "licenseText": "Copyright (c) 2017 Duo Security, Inc. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n1. Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the distribution.\n3. Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\nIS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\nCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\nEXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\nPROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\nCopyright (c) 2021-2022 github.com/go-webauthn/webauthn authors.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the\nfollowing conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following\n disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following\n disclaimer in the documentation and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products\n derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\nINCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\nWHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\nTHIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + "licenseText": "Copyright (c) 2025 github.com/go-webauthn/webauthn authors.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n1. Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the distribution.\n3. Neither the name of the copyright holder nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\nIS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\nCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\nEXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\nPROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." }, { "name": "github.com/go-webauthn/x", @@ -635,8 +635,8 @@ "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, { - "name": "github.com/google/go-github/v74", - "path": "github.com/google/go-github/v74/LICENSE", + "name": "github.com/google/go-github/v84", + "path": "github.com/google/go-github/v84/LICENSE", "licenseText": "Copyright (c) 2013 The go-github AUTHORS. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, { @@ -729,6 +729,11 @@ "path": "github.com/huandu/xstrings/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Huan Du\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n" }, + { + "name": "github.com/inbucket/html2text", + "path": "github.com/inbucket/html2text/LICENSE", + "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Jay Taylor\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n" + }, { "name": "github.com/jaytaylor/html2text", "path": "github.com/jaytaylor/html2text/LICENSE", @@ -740,10 +745,15 @@ "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014 Juan Batiz-Benet\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" }, { - "name": "github.com/jhillyerd/enmime", - "path": "github.com/jhillyerd/enmime/LICENSE", + "name": "github.com/jhillyerd/enmime/v2", + "path": "github.com/jhillyerd/enmime/v2/LICENSE", "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2012-2016 James Hillyerd, All Rights Reserved\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/jonboulle/clockwork", + "path": "github.com/jonboulle/clockwork/LICENSE", + "licenseText": "Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" + }, { "name": "github.com/josharian/intern", "path": "github.com/josharian/intern/license.md", @@ -909,11 +919,6 @@ "path": "github.com/minio/minlz/LICENSE", "licenseText": "\r\n Apache License\r\n Version 2.0, January 2004\r\n http://www.apache.org/licenses/\r\n\r\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\r\n\r\n 1. Definitions.\r\n\r\n \"License\" shall mean the terms and conditions for use, reproduction,\r\n and distribution as defined by Sections 1 through 9 of this document.\r\n\r\n \"Licensor\" shall mean the copyright owner or entity authorized by\r\n the copyright owner that is granting the License.\r\n\r\n \"Legal Entity\" shall mean the union of the acting entity and all\r\n other entities that control, are controlled by, or are under common\r\n control with that entity. For the purposes of this definition,\r\n \"control\" means (i) the power, direct or indirect, to cause the\r\n direction or management of such entity, whether by contract or\r\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\r\n outstanding shares, or (iii) beneficial ownership of such entity.\r\n\r\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\r\n exercising permissions granted by this License.\r\n\r\n \"Source\" form shall mean the preferred form for making modifications,\r\n including but not limited to software source code, documentation\r\n source, and configuration files.\r\n\r\n \"Object\" form shall mean any form resulting from mechanical\r\n transformation or translation of a Source form, including but\r\n not limited to compiled object code, generated documentation,\r\n and conversions to other media types.\r\n\r\n \"Work\" shall mean the work of authorship, whether in Source or\r\n Object form, made available under the License, as indicated by a\r\n copyright notice that is included in or attached to the work\r\n (an example is provided in the Appendix below).\r\n\r\n \"Derivative Works\" shall mean any work, whether in Source or Object\r\n form, that is based on (or derived from) the Work and for which the\r\n editorial revisions, annotations, elaborations, or other modifications\r\n represent, as a whole, an original work of authorship. For the purposes\r\n of this License, Derivative Works shall not include works that remain\r\n separable from, or merely link (or bind by name) to the interfaces of,\r\n the Work and Derivative Works thereof.\r\n\r\n \"Contribution\" shall mean any work of authorship, including\r\n the original version of the Work and any modifications or additions\r\n to that Work or Derivative Works thereof, that is intentionally\r\n submitted to Licensor for inclusion in the Work by the copyright owner\r\n or by an individual or Legal Entity authorized to submit on behalf of\r\n the copyright owner. For the purposes of this definition, \"submitted\"\r\n means any form of electronic, verbal, or written communication sent\r\n to the Licensor or its representatives, including but not limited to\r\n communication on electronic mailing lists, source code control systems,\r\n and issue tracking systems that are managed by, or on behalf of, the\r\n Licensor for the purpose of discussing and improving the Work, but\r\n excluding communication that is conspicuously marked or otherwise\r\n designated in writing by the copyright owner as \"Not a Contribution.\"\r\n\r\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\r\n on behalf of whom a Contribution has been received by Licensor and\r\n subsequently incorporated within the Work.\r\n\r\n 2. Grant of Copyright License. Subject to the terms and conditions of\r\n this License, each Contributor hereby grants to You a perpetual,\r\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n copyright license to reproduce, prepare Derivative Works of,\r\n publicly display, publicly perform, sublicense, and distribute the\r\n Work and such Derivative Works in Source or Object form.\r\n\r\n 3. Grant of Patent License. Subject to the terms and conditions of\r\n this License, each Contributor hereby grants to You a perpetual,\r\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n (except as stated in this section) patent license to make, have made,\r\n use, offer to sell, sell, import, and otherwise transfer the Work,\r\n where such license applies only to those patent claims licensable\r\n by such Contributor that are necessarily infringed by their\r\n Contribution(s) alone or by combination of their Contribution(s)\r\n with the Work to which such Contribution(s) was submitted. If You\r\n institute patent litigation against any entity (including a\r\n cross-claim or counterclaim in a lawsuit) alleging that the Work\r\n or a Contribution incorporated within the Work constitutes direct\r\n or contributory patent infringement, then any patent licenses\r\n granted to You under this License for that Work shall terminate\r\n as of the date such litigation is filed.\r\n\r\n 4. Redistribution. You may reproduce and distribute copies of the\r\n Work or Derivative Works thereof in any medium, with or without\r\n modifications, and in Source or Object form, provided that You\r\n meet the following conditions:\r\n\r\n (a) You must give any other recipients of the Work or\r\n Derivative Works a copy of this License; and\r\n\r\n (b) You must cause any modified files to carry prominent notices\r\n stating that You changed the files; and\r\n\r\n (c) You must retain, in the Source form of any Derivative Works\r\n that You distribute, all copyright, patent, trademark, and\r\n attribution notices from the Source form of the Work,\r\n excluding those notices that do not pertain to any part of\r\n the Derivative Works; and\r\n\r\n (d) If the Work includes a \"NOTICE\" text file as part of its\r\n distribution, then any Derivative Works that You distribute must\r\n include a readable copy of the attribution notices contained\r\n within such NOTICE file, excluding those notices that do not\r\n pertain to any part of the Derivative Works, in at least one\r\n of the following places: within a NOTICE text file distributed\r\n as part of the Derivative Works; within the Source form or\r\n documentation, if provided along with the Derivative Works; or,\r\n within a display generated by the Derivative Works, if and\r\n wherever such third-party notices normally appear. The contents\r\n of the NOTICE file are for informational purposes only and\r\n do not modify the License. You may add Your own attribution\r\n notices within Derivative Works that You distribute, alongside\r\n or as an addendum to the NOTICE text from the Work, provided\r\n that such additional attribution notices cannot be construed\r\n as modifying the License.\r\n\r\n You may add Your own copyright statement to Your modifications and\r\n may provide additional or different license terms and conditions\r\n for use, reproduction, or distribution of Your modifications, or\r\n for any such Derivative Works as a whole, provided Your use,\r\n reproduction, and distribution of the Work otherwise complies with\r\n the conditions stated in this License.\r\n\r\n 5. Submission of Contributions. Unless You explicitly state otherwise,\r\n any Contribution intentionally submitted for inclusion in the Work\r\n by You to the Licensor shall be under the terms and conditions of\r\n this License, without any additional terms or conditions.\r\n Notwithstanding the above, nothing herein shall supersede or modify\r\n the terms of any separate license agreement you may have executed\r\n with Licensor regarding such Contributions.\r\n\r\n 6. Trademarks. This License does not grant permission to use the trade\r\n names, trademarks, service marks, or product names of the Licensor,\r\n except as required for reasonable and customary use in describing the\r\n origin of the Work and reproducing the content of the NOTICE file.\r\n\r\n 7. Disclaimer of Warranty. Unless required by applicable law or\r\n agreed to in writing, Licensor provides the Work (and each\r\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\r\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r\n implied, including, without limitation, any warranties or conditions\r\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\r\n PARTICULAR PURPOSE. You are solely responsible for determining the\r\n appropriateness of using or redistributing the Work and assume any\r\n risks associated with Your exercise of permissions under this License.\r\n\r\n 8. Limitation of Liability. In no event and under no legal theory,\r\n whether in tort (including negligence), contract, or otherwise,\r\n unless required by applicable law (such as deliberate and grossly\r\n negligent acts) or agreed to in writing, shall any Contributor be\r\n liable to You for damages, including any direct, indirect, special,\r\n incidental, or consequential damages of any character arising as a\r\n result of this License or out of the use or inability to use the\r\n Work (including but not limited to damages for loss of goodwill,\r\n work stoppage, computer failure or malfunction, or any and all\r\n other commercial damages or losses), even if such Contributor\r\n has been advised of the possibility of such damages.\r\n\r\n 9. Accepting Warranty or Additional Liability. While redistributing\r\n the Work or Derivative Works thereof, You may choose to offer,\r\n and charge a fee for, acceptance of support, warranty, indemnity,\r\n or other liability obligations and/or rights consistent with this\r\n License. However, in accepting such obligations, You may act only\r\n on Your own behalf and on Your sole responsibility, not on behalf\r\n of any other Contributor, and only if You agree to indemnify,\r\n defend, and hold each Contributor harmless for any liability\r\n incurred by, or claims asserted against, such Contributor by reason\r\n of your accepting any such warranty or additional liability.\r\n\r\nEND OF TERMS AND CONDITIONS" }, - { - "name": "github.com/mitchellh/mapstructure", - "path": "github.com/mitchellh/mapstructure/LICENSE", - "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2013 Mitchell Hashimoto\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" - }, { "name": "github.com/modern-go/concurrent", "path": "github.com/modern-go/concurrent/LICENSE", @@ -924,6 +929,11 @@ "path": "github.com/modern-go/reflect2/LICENSE", "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, + { + "name": "github.com/mschoch/smat", + "path": "github.com/mschoch/smat/LICENSE", + "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License." + }, { "name": "github.com/munnerz/goautoneg", "path": "github.com/munnerz/goautoneg/LICENSE", @@ -1049,11 +1059,6 @@ "path": "github.com/rhysd/actionlint/LICENSE.txt", "licenseText": "the MIT License\n\nCopyright (c) 2021 rhysd\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 copies\nof the Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\nINCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\nPURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR\nTHE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n" }, - { - "name": "github.com/rivo/uniseg", - "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/v3", "path": "github.com/robfig/cron/v3/LICENSE", @@ -1070,8 +1075,8 @@ "licenseText": "Blackfriday is distributed under the Simplified BSD License:\n\n\u003e Copyright © 2011 Russ Ross\n\u003e All rights reserved.\n\u003e\n\u003e Redistribution and use in source and binary forms, with or without\n\u003e modification, are permitted provided that the following conditions\n\u003e are met:\n\u003e\n\u003e 1. Redistributions of source code must retain the above copyright\n\u003e notice, this list of conditions and the following disclaimer.\n\u003e\n\u003e 2. Redistributions in binary form must reproduce the above\n\u003e copyright notice, this list of conditions and the following\n\u003e disclaimer in the documentation and/or other materials provided with\n\u003e the distribution.\n\u003e\n\u003e THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\u003e \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n\u003e LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\n\u003e FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n\u003e COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n\u003e INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n\u003e BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n\u003e LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n\u003e CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n\u003e LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\n\u003e ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n\u003e POSSIBILITY OF SUCH DAMAGE.\n" }, { - "name": "github.com/santhosh-tekuri/jsonschema/v5", - "path": "github.com/santhosh-tekuri/jsonschema/v5/LICENSE", + "name": "github.com/santhosh-tekuri/jsonschema/v6", + "path": "github.com/santhosh-tekuri/jsonschema/v6/LICENSE", "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability." }, { @@ -1154,11 +1159,6 @@ "path": "github.com/urfave/cli/v3/LICENSE", "licenseText": "MIT License\n\nCopyright (c) 2023 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, - { - "name": "github.com/valyala/fastjson", - "path": "github.com/valyala/fastjson/LICENSE", - "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2018 Aliaksandr Valialkin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n" - }, { "name": "github.com/wneessen/go-mail", "path": "github.com/wneessen/go-mail/LICENSE", @@ -1194,11 +1194,6 @@ "path": "github.com/yuin/goldmark-highlighting/v2/LICENSE", "licenseText": "MIT License\n\nCopyright (c) 2019 Yusuke Inuzuka\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/yuin/goldmark-meta", - "path": "github.com/yuin/goldmark-meta/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2019 Yusuke Inuzuka\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/yuin/goldmark", "path": "github.com/yuin/goldmark/LICENSE", @@ -1252,7 +1247,7 @@ { "name": "go.yaml.in/yaml/v4", "path": "go.yaml.in/yaml/v4/LICENSE", - "licenseText": "\nThis project is covered by two different licenses: MIT and Apache.\n\n#### MIT License ####\n\nThe following files were ported to Go from C files of libyaml, and thus\nare still covered by their original MIT license, with the additional\ncopyright staring in 2011 when the project was ported over:\n\n apic.go emitterc.go parserc.go readerc.go scannerc.go\n writerc.go yamlh.go yamlprivateh.go\n\nCopyright (c) 2006-2010 Kirill Simonov\nCopyright (c) 2006-2011 Kirill Simonov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n### Apache License ###\n\nAll the remaining project files are covered by the Apache license:\n\nCopyright (c) 2011-2019 Canonical Ltd\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n" + "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright 2025 - The go-yaml Project Contributors\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, { "name": "go4.org", @@ -1329,11 +1324,6 @@ "path": "gopkg.in/warnings.v0/LICENSE", "licenseText": "Copyright (c) 2016 Péter Surányi.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, - { - "name": "gopkg.in/yaml.v2", - "path": "gopkg.in/yaml.v2/LICENSE", - "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" - }, { "name": "gopkg.in/yaml.v3", "path": "gopkg.in/yaml.v3/LICENSE", diff --git a/build/generate-go-licenses.go b/build/generate-go-licenses.go index b710fdb841..057e6a6e49 100644 --- a/build/generate-go-licenses.go +++ b/build/generate-go-licenses.go @@ -19,6 +19,7 @@ import ( // regexp is based on go-license, excluding README and NOTICE // https://github.com/google/go-licenses/blob/master/licenses/find.go +// also defined in vite.config.ts var licenseRe = regexp.MustCompile(`^(?i)((UN)?LICEN(S|C)E|COPYING).*$`) // primaryLicenseRe matches exact primary license filenames without suffixes. diff --git a/cmd/actions.go b/cmd/actions.go index 2c51c6a1bc..44b6b7b54c 100644 --- a/cmd/actions.go +++ b/cmd/actions.go @@ -13,17 +13,18 @@ import ( "github.com/urfave/cli/v3" ) -var ( - // CmdActions represents the available actions sub-commands. - CmdActions = &cli.Command{ +func newActionsCommand() *cli.Command { + return &cli.Command{ Name: "actions", Usage: "Manage Gitea Actions", Commands: []*cli.Command{ - subcmdActionsGenRunnerToken, + newActionsGenerateRunnerTokenCommand(), }, } +} - subcmdActionsGenRunnerToken = &cli.Command{ +func newActionsGenerateRunnerTokenCommand() *cli.Command { + return &cli.Command{ Name: "generate-runner-token", Usage: "Generate a new token for a runner to use to register with the server", Action: runGenerateActionsRunnerToken, @@ -37,7 +38,7 @@ var ( }, }, } -) +} func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error { setting.MustInstalled() diff --git a/cmd/admin.go b/cmd/admin.go index dbd48e5727..c0e21731cb 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -18,36 +18,41 @@ import ( "github.com/urfave/cli/v3" ) -var ( - // CmdAdmin represents the available admin sub-command. - CmdAdmin = &cli.Command{ +func newAdminCommand() *cli.Command { + return &cli.Command{ Name: "admin", Usage: "Perform common administrative operations", Commands: []*cli.Command{ - subcmdUser, - subcmdRepoSyncReleases, - subcmdRegenerate, - subcmdAuth, - subcmdSendMail, + newUserCommand(), + newRepoSyncReleasesCommand(), + newRegenerateCommand(), + newAuthCommand(), + newSendMailCommand(), }, } +} - subcmdRepoSyncReleases = &cli.Command{ +func newRepoSyncReleasesCommand() *cli.Command { + return &cli.Command{ Name: "repo-sync-releases", Usage: "Synchronize repository releases with tags", Action: runRepoSyncReleases, } +} - subcmdRegenerate = &cli.Command{ +func newRegenerateCommand() *cli.Command { + return &cli.Command{ Name: "regenerate", Usage: "Regenerate specific files", Commands: []*cli.Command{ - microcmdRegenHooks, - microcmdRegenKeys, + newRegenerateHooksCommand(), + newRegenerateKeysCommand(), }, } +} - subcmdAuth = &cli.Command{ +func newAuthCommand() *cli.Command { + return &cli.Command{ Name: "auth", Usage: "Modify external auth providers", Commands: []*cli.Command{ @@ -59,12 +64,14 @@ var ( microcmdAuthUpdateLdapSimpleAuth(), microcmdAuthAddSMTP(), microcmdAuthUpdateSMTP(), - microcmdAuthList, - microcmdAuthDelete, + newAuthListCommand(), + newAuthDeleteCommand(), }, } +} - subcmdSendMail = &cli.Command{ +func newSendMailCommand() *cli.Command { + return &cli.Command{ Name: "sendmail", Usage: "Send a message to all users", Action: runSendMail, @@ -86,7 +93,7 @@ var ( }, }, } -) +} func idFlag() *cli.Int64Flag { return &cli.Int64Flag{ diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go index 1a09366722..b55bb1481c 100644 --- a/cmd/admin_auth.go +++ b/cmd/admin_auth.go @@ -17,14 +17,17 @@ import ( "github.com/urfave/cli/v3" ) -var ( - microcmdAuthDelete = &cli.Command{ +func newAuthDeleteCommand() *cli.Command { + return &cli.Command{ Name: "delete", Usage: "Delete specific auth source", Flags: []cli.Flag{idFlag()}, Action: runDeleteAuth, } - microcmdAuthList = &cli.Command{ +} + +func newAuthListCommand() *cli.Command { + return &cli.Command{ Name: "list", Usage: "List auth sources", Action: runListAuth, @@ -55,7 +58,7 @@ var ( }, }, } -) +} func runListAuth(ctx context.Context, c *cli.Command) error { if err := initDB(ctx); err != nil { diff --git a/cmd/admin_regenerate.go b/cmd/admin_regenerate.go index a5f1bd5105..aa235441ba 100644 --- a/cmd/admin_regenerate.go +++ b/cmd/admin_regenerate.go @@ -13,19 +13,21 @@ import ( "github.com/urfave/cli/v3" ) -var ( - microcmdRegenHooks = &cli.Command{ +func newRegenerateHooksCommand() *cli.Command { + return &cli.Command{ Name: "hooks", Usage: "Regenerate git-hooks", Action: runRegenerateHooks, } +} - microcmdRegenKeys = &cli.Command{ +func newRegenerateKeysCommand() *cli.Command { + return &cli.Command{ Name: "keys", Usage: "Regenerate authorized_keys file", Action: runRegenerateKeys, } -) +} func runRegenerateHooks(ctx context.Context, _ *cli.Command) error { if err := initDB(ctx); err != nil { diff --git a/cmd/admin_user.go b/cmd/admin_user.go index 3a24c3e56f..8dd8bb4eca 100644 --- a/cmd/admin_user.go +++ b/cmd/admin_user.go @@ -7,15 +7,17 @@ import ( "github.com/urfave/cli/v3" ) -var subcmdUser = &cli.Command{ - Name: "user", - Usage: "Modify users", - Commands: []*cli.Command{ - microcmdUserCreate(), - microcmdUserList, - microcmdUserChangePassword(), - microcmdUserDelete(), - microcmdUserGenerateAccessToken, - microcmdUserMustChangePassword(), - }, +func newUserCommand() *cli.Command { + return &cli.Command{ + Name: "user", + Usage: "Modify users", + Commands: []*cli.Command{ + microcmdUserCreate(), + newUserListCommand(), + microcmdUserChangePassword(), + microcmdUserDelete(), + newUserGenerateAccessTokenCommand(), + microcmdUserMustChangePassword(), + }, + } } diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index 61064fdef4..7f8330ff7e 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -14,32 +14,34 @@ import ( "github.com/urfave/cli/v3" ) -var microcmdUserGenerateAccessToken = &cli.Command{ - Name: "generate-access-token", - Usage: "Generate an access token for a specific user", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "username", - Aliases: []string{"u"}, - Usage: "Username", +func newUserGenerateAccessTokenCommand() *cli.Command { + return &cli.Command{ + Name: "generate-access-token", + Usage: "Generate an access token for a specific user", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Usage: "Username", + }, + &cli.StringFlag{ + Name: "token-name", + Aliases: []string{"t"}, + Usage: "Token name", + Value: "gitea-admin", + }, + &cli.BoolFlag{ + Name: "raw", + Usage: "Display only the token value", + }, + &cli.StringFlag{ + Name: "scopes", + Value: "all", + Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`, + }, }, - &cli.StringFlag{ - Name: "token-name", - Aliases: []string{"t"}, - Usage: "Token name", - Value: "gitea-admin", - }, - &cli.BoolFlag{ - Name: "raw", - Usage: "Display only the token value", - }, - &cli.StringFlag{ - Name: "scopes", - Value: "all", - Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`, - }, - }, - Action: runGenerateAccessToken, + Action: runGenerateAccessToken, + } } func runGenerateAccessToken(ctx context.Context, c *cli.Command) error { diff --git a/cmd/admin_user_list.go b/cmd/admin_user_list.go index e3d345e2f2..2958fe0cc8 100644 --- a/cmd/admin_user_list.go +++ b/cmd/admin_user_list.go @@ -14,16 +14,18 @@ import ( "github.com/urfave/cli/v3" ) -var microcmdUserList = &cli.Command{ - Name: "list", - Usage: "List users", - Action: runListUsers, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "admin", - Usage: "List only admin users", +func newUserListCommand() *cli.Command { + return &cli.Command{ + Name: "list", + Usage: "List users", + Action: runListUsers, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "admin", + Usage: "List only admin users", + }, }, - }, + } } func runListUsers(ctx context.Context, c *cli.Command) error { diff --git a/cmd/docs.go b/cmd/docs.go index 098c0e9a8a..8e0f9428df 100644 --- a/cmd/docs.go +++ b/cmd/docs.go @@ -13,23 +13,24 @@ import ( "github.com/urfave/cli/v3" ) -// CmdDocs represents the available docs sub-command. -var CmdDocs = &cli.Command{ - Name: "docs", - Usage: "Output CLI documentation", - Description: "A command to output Gitea's CLI documentation, optionally to a file.", - Action: runDocs, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "man", - Usage: "Output man pages instead", +func newDocsCommand() *cli.Command { + return &cli.Command{ + Name: "docs", + Usage: "Output CLI documentation", + Description: "A command to output Gitea's CLI documentation, optionally to a file.", + Action: runDocs, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "man", + Usage: "Output man pages instead", + }, + &cli.StringFlag{ + Name: "output", + Aliases: []string{"o"}, + Usage: "Path to output to instead of stdout (will overwrite if exists)", + }, }, - &cli.StringFlag{ - Name: "output", - Aliases: []string{"o"}, - Usage: "Path to output to instead of stdout (will overwrite if exists)", - }, - }, + } } func runDocs(_ context.Context, cmd *cli.Command) error { diff --git a/cmd/doctor.go b/cmd/doctor.go index 596dd61178..188740dbce 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -24,73 +24,77 @@ import ( "xorm.io/xorm" ) -// CmdDoctor represents the available doctor sub-command. -var CmdDoctor = &cli.Command{ - Name: "doctor", - Usage: "Diagnose and optionally fix problems, convert or re-create database tables", - 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.", - - Commands: []*cli.Command{ - cmdDoctorCheck, - cmdRecreateTable, - cmdDoctorConvert, - }, +func newDoctorCommand() *cli.Command { + return &cli.Command{ + Name: "doctor", + Usage: "Diagnose and optionally fix problems, convert or re-create database tables", + 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.", + Commands: []*cli.Command{ + newDoctorCheckCommand(), + newRecreateTableCommand(), + newDoctorConvertCommand(), + }, + } } -var cmdDoctorCheck = &cli.Command{ - Name: "check", - 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.", - Action: runDoctorCheck, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "list", - Usage: "List the available checks", +func newDoctorCheckCommand() *cli.Command { + return &cli.Command{ + Name: "check", + 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.", + Action: runDoctorCheck, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "list", + Usage: "List the available checks", + }, + &cli.BoolFlag{ + Name: "default", + Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)", + }, + &cli.StringSliceFlag{ + Name: "run", + Usage: "Run the provided checks - (if --default is set, the default checks will also run)", + }, + &cli.BoolFlag{ + Name: "all", + Usage: "Run all the available checks", + }, + &cli.BoolFlag{ + Name: "fix", + Usage: "Automatically fix what we can", + }, + &cli.StringFlag{ + Name: "log-file", + Usage: `Name of the log file (no verbose log output by default). Set to "-" to output to stdout`, + }, + &cli.BoolFlag{ + Name: "color", + Aliases: []string{"H"}, + Usage: "Use color for outputted information", + }, }, - &cli.BoolFlag{ - Name: "default", - Usage: "Run the default checks (if neither --run or --all is set, this is the default behaviour)", - }, - &cli.StringSliceFlag{ - Name: "run", - Usage: "Run the provided checks - (if --default is set, the default checks will also run)", - }, - &cli.BoolFlag{ - Name: "all", - Usage: "Run all the available checks", - }, - &cli.BoolFlag{ - Name: "fix", - Usage: "Automatically fix what we can", - }, - &cli.StringFlag{ - Name: "log-file", - Usage: `Name of the log file (no verbose log output by default). Set to "-" to output to stdout`, - }, - &cli.BoolFlag{ - Name: "color", - Aliases: []string{"H"}, - Usage: "Use color for outputted information", - }, - }, + } } -var cmdRecreateTable = &cli.Command{ - Name: "recreate-table", - Usage: "Recreate tables from XORM definitions and copy the data.", - ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "debug", - Usage: "Print SQL commands sent", +func newRecreateTableCommand() *cli.Command { + return &cli.Command{ + Name: "recreate-table", + Usage: "Recreate tables from XORM definitions and copy the data.", + ArgsUsage: "[TABLE]... : (TABLEs to recreate - leave blank for all)", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "debug", + Usage: "Print SQL commands sent", + }, }, - }, - Description: `The database definitions Gitea uses change across versions, sometimes changing default values and leaving old unused columns. + Description: `The database definitions Gitea uses change across versions, sometimes changing default values and leaving old unused columns. This command will cause Xorm to recreate tables, copying over the data and deleting the old table. You should back-up your database before doing this and ensure that your database is up-to-date first.`, - Action: runRecreateTable, + Action: runRecreateTable, + } } func runRecreateTable(ctx context.Context, cmd *cli.Command) error { diff --git a/cmd/doctor_convert.go b/cmd/doctor_convert.go index 8cb718d383..f4867912ab 100644 --- a/cmd/doctor_convert.go +++ b/cmd/doctor_convert.go @@ -14,12 +14,13 @@ import ( "github.com/urfave/cli/v3" ) -// cmdDoctorConvert represents the available convert sub-command. -var cmdDoctorConvert = &cli.Command{ - Name: "convert", - Usage: "Convert the database", - Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar", - Action: runDoctorConvert, +func newDoctorConvertCommand() *cli.Command { + return &cli.Command{ + Name: "convert", + Usage: "Convert the database", + Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar", + Action: runDoctorConvert, + } } func runDoctorConvert(ctx context.Context, cmd *cli.Command) error { diff --git a/cmd/doctor_test.go b/cmd/doctor_test.go index da942b38b6..7d2f358947 100644 --- a/cmd/doctor_test.go +++ b/cmd/doctor_test.go @@ -23,7 +23,7 @@ func TestDoctorRun(t *testing.T) { SkipDatabaseInitialization: true, }) app := &cli.Command{ - Commands: []*cli.Command{cmdDoctorCheck}, + Commands: []*cli.Command{newDoctorCheckCommand()}, } err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"}) assert.NoError(t, err) diff --git a/cmd/dump.go b/cmd/dump.go index 7f0b23ed98..49f4d9e894 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -23,78 +23,79 @@ import ( "github.com/urfave/cli/v3" ) -// CmdDump represents the available dump sub-command. -var CmdDump = &cli.Command{ - Name: "dump", - Usage: "Dump Gitea files and database", - Description: `Dump compresses all related files and database into zip file. It can be used for backup and capture Gitea server image to send to maintainer`, - Action: runDump, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "file", - Aliases: []string{"f"}, - Usage: `Name of the dump file which will be created, default to "gitea-dump-{time}.zip". Supply '-' for stdout. See type for available types.`, +func newDumpCommand() *cli.Command { + return &cli.Command{ + Name: "dump", + Usage: "Dump Gitea files and database", + Description: `Dump compresses all related files and database into zip file. It can be used for backup and capture Gitea server image to send to maintainer`, + Action: runDump, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "file", + Aliases: []string{"f"}, + Usage: `Name of the dump file which will be created, default to "gitea-dump-{time}.zip". Supply '-' for stdout. See type for available types.`, + }, + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"V"}, + Usage: "Show process details", + }, + &cli.BoolFlag{ + Name: "quiet", + Aliases: []string{"q"}, + Usage: "Only display warnings and errors", + }, + &cli.StringFlag{ + Name: "tempdir", + Aliases: []string{"t"}, + Value: os.TempDir(), + Usage: "Temporary dir path", + }, + &cli.StringFlag{ + Name: "database", + Aliases: []string{"d"}, + Usage: "Specify the database SQL syntax: sqlite3, mysql, mssql, postgres", + }, + &cli.BoolFlag{ + Name: "skip-repository", + Aliases: []string{"R"}, + Usage: "Skip the repository dumping", + }, + &cli.BoolFlag{ + Name: "skip-log", + Aliases: []string{"L"}, + Usage: "Skip the log dumping", + }, + &cli.BoolFlag{ + Name: "skip-custom-dir", + Usage: "Skip custom directory", + }, + &cli.BoolFlag{ + Name: "skip-lfs-data", + Usage: "Skip LFS data", + }, + &cli.BoolFlag{ + Name: "skip-attachment-data", + Usage: "Skip attachment data", + }, + &cli.BoolFlag{ + Name: "skip-package-data", + Usage: "Skip package data", + }, + &cli.BoolFlag{ + Name: "skip-index", + Usage: "Skip bleve index data", + }, + &cli.BoolFlag{ + Name: "skip-db", + Usage: "Skip database", + }, + &cli.StringFlag{ + Name: "type", + Usage: `Dump output format, default to "zip", supported types: ` + strings.Join(dump.SupportedOutputTypes, ", "), + }, }, - &cli.BoolFlag{ - Name: "verbose", - Aliases: []string{"V"}, - Usage: "Show process details", - }, - &cli.BoolFlag{ - Name: "quiet", - Aliases: []string{"q"}, - Usage: "Only display warnings and errors", - }, - &cli.StringFlag{ - Name: "tempdir", - Aliases: []string{"t"}, - Value: os.TempDir(), - Usage: "Temporary dir path", - }, - &cli.StringFlag{ - Name: "database", - Aliases: []string{"d"}, - Usage: "Specify the database SQL syntax: sqlite3, mysql, mssql, postgres", - }, - &cli.BoolFlag{ - Name: "skip-repository", - Aliases: []string{"R"}, - Usage: "Skip the repository dumping", - }, - &cli.BoolFlag{ - Name: "skip-log", - Aliases: []string{"L"}, - Usage: "Skip the log dumping", - }, - &cli.BoolFlag{ - Name: "skip-custom-dir", - Usage: "Skip custom directory", - }, - &cli.BoolFlag{ - Name: "skip-lfs-data", - Usage: "Skip LFS data", - }, - &cli.BoolFlag{ - Name: "skip-attachment-data", - Usage: "Skip attachment data", - }, - &cli.BoolFlag{ - Name: "skip-package-data", - Usage: "Skip package data", - }, - &cli.BoolFlag{ - Name: "skip-index", - Usage: "Skip bleve index data", - }, - &cli.BoolFlag{ - Name: "skip-db", - Usage: "Skip database", - }, - &cli.StringFlag{ - Name: "type", - Usage: `Dump output format, default to "zip", supported types: ` + strings.Join(dump.SupportedOutputTypes, ", "), - }, - }, + } } func fatal(format string, args ...any) { diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go index beda305c85..367454366e 100644 --- a/cmd/dump_repo.go +++ b/cmd/dump_repo.go @@ -22,61 +22,62 @@ import ( "github.com/urfave/cli/v3" ) -// CmdDumpRepository represents the available dump repository sub-command. -var CmdDumpRepository = &cli.Command{ - Name: "dump-repo", - Usage: "Dump the repository from git/github/gitea/gitlab", - Description: "This is a command for dumping the repository data.", - Action: runDumpRepository, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "git_service", - Value: "", - Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.", - }, - &cli.StringFlag{ - Name: "repo_dir", - Aliases: []string{"r"}, - Value: "./data", - Usage: "Repository dir path to store the data", - }, - &cli.StringFlag{ - Name: "clone_addr", - Value: "", - Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL", - }, - &cli.StringFlag{ - Name: "auth_username", - Value: "", - Usage: "The username to visit the clone_addr", - }, - &cli.StringFlag{ - Name: "auth_password", - Value: "", - Usage: "The password to visit the clone_addr", - }, - &cli.StringFlag{ - Name: "auth_token", - Value: "", - Usage: "The personal token to visit the clone_addr", - }, - &cli.StringFlag{ - Name: "owner_name", - Value: "", - Usage: "The data will be stored on a directory with owner name if not empty", - }, - &cli.StringFlag{ - Name: "repo_name", - Value: "", - Usage: "The data will be stored on a directory with repository name if not empty", - }, - &cli.StringFlag{ - Name: "units", - Value: "", - Usage: `Which items will be migrated, one or more units should be separated as comma. +func newDumpRepositoryCommand() *cli.Command { + return &cli.Command{ + Name: "dump-repo", + Usage: "Dump the repository from git/github/gitea/gitlab", + Description: "This is a command for dumping the repository data.", + Action: runDumpRepository, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "git_service", + Value: "", + Usage: "Git service, git, github, gitea, gitlab. If clone_addr could be recognized, this could be ignored.", + }, + &cli.StringFlag{ + Name: "repo_dir", + Aliases: []string{"r"}, + Value: "./data", + Usage: "Repository dir path to store the data", + }, + &cli.StringFlag{ + Name: "clone_addr", + Value: "", + Usage: "The URL will be clone, currently could be a git/github/gitea/gitlab http/https URL", + }, + &cli.StringFlag{ + Name: "auth_username", + Value: "", + Usage: "The username to visit the clone_addr", + }, + &cli.StringFlag{ + Name: "auth_password", + Value: "", + Usage: "The password to visit the clone_addr", + }, + &cli.StringFlag{ + Name: "auth_token", + Value: "", + Usage: "The personal token to visit the clone_addr", + }, + &cli.StringFlag{ + Name: "owner_name", + Value: "", + Usage: "The data will be stored on a directory with owner name if not empty", + }, + &cli.StringFlag{ + Name: "repo_name", + Value: "", + Usage: "The data will be stored on a directory with repository name if not empty", + }, + &cli.StringFlag{ + Name: "units", + Value: "", + Usage: `Which items will be migrated, one or more units should be separated as comma. wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`, + }, }, - }, + } } func runDumpRepository(ctx context.Context, cmd *cli.Command) error { diff --git a/cmd/embedded.go b/cmd/embedded.go index 9180407fd1..e2110756b8 100644 --- a/cmd/embedded.go +++ b/cmd/embedded.go @@ -23,20 +23,23 @@ import ( "github.com/urfave/cli/v3" ) -// CmdEmbedded represents the available extract sub-command. -var ( - CmdEmbedded = &cli.Command{ +var matchedAssetFiles []assetFile + +func newEmbeddedCommand() *cli.Command { + return &cli.Command{ Name: "embedded", Usage: "Extract embedded resources", Description: "A command for extracting embedded resources, like templates and images", Commands: []*cli.Command{ - subcmdList, - subcmdView, - subcmdExtract, + newEmbeddedListCommand(), + newEmbeddedViewCommand(), + newEmbeddedExtractCommand(), }, } +} - subcmdList = &cli.Command{ +func newEmbeddedListCommand() *cli.Command { + return &cli.Command{ Name: "list", Usage: "List files matching the given pattern", Action: runList, @@ -48,8 +51,10 @@ var ( }, }, } +} - subcmdView = &cli.Command{ +func newEmbeddedViewCommand() *cli.Command { + return &cli.Command{ Name: "view", Usage: "View a file matching the given pattern", Action: runView, @@ -61,8 +66,10 @@ var ( }, }, } +} - subcmdExtract = &cli.Command{ +func newEmbeddedExtractCommand() *cli.Command { + return &cli.Command{ Name: "extract", Usage: "Extract resources", Action: runExtract, @@ -91,9 +98,7 @@ var ( }, }, } - - matchedAssetFiles []assetFile -) +} type assetFile struct { fs *assetfs.LayeredFS diff --git a/cmd/generate.go b/cmd/generate.go index 9cb4cf3917..21f8b42bff 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -15,45 +15,52 @@ import ( "github.com/urfave/cli/v3" ) -var ( - // CmdGenerate represents the available generate sub-command. - CmdGenerate = &cli.Command{ +func newGenerateCommand() *cli.Command { + return &cli.Command{ Name: "generate", Usage: "Generate Gitea's secrets/keys/tokens", Commands: []*cli.Command{ - subcmdSecret, + newGenerateSecretCommand(), }, } +} - subcmdSecret = &cli.Command{ +func newGenerateSecretCommand() *cli.Command { + return &cli.Command{ Name: "secret", Usage: "Generate a secret token", Commands: []*cli.Command{ - microcmdGenerateInternalToken, - microcmdGenerateLfsJwtSecret, - microcmdGenerateSecretKey, + newGenerateInternalTokenCommand(), + newGenerateLfsJWTSecretCommand(), + newGenerateSecretKeyCommand(), }, } +} - microcmdGenerateInternalToken = &cli.Command{ +func newGenerateInternalTokenCommand() *cli.Command { + return &cli.Command{ Name: "INTERNAL_TOKEN", Usage: "Generate a new INTERNAL_TOKEN", Action: runGenerateInternalToken, } +} - microcmdGenerateLfsJwtSecret = &cli.Command{ +func newGenerateLfsJWTSecretCommand() *cli.Command { + return &cli.Command{ Name: "JWT_SECRET", Aliases: []string{"LFS_JWT_SECRET"}, Usage: "Generate a new JWT_SECRET", Action: runGenerateLfsJwtSecret, } +} - microcmdGenerateSecretKey = &cli.Command{ +func newGenerateSecretKeyCommand() *cli.Command { + return &cli.Command{ Name: "SECRET_KEY", Usage: "Generate a new SECRET_KEY", Action: runGenerateSecretKey, } -) +} func runGenerateInternalToken(_ context.Context, c *cli.Command) error { internalToken, err := generate.NewInternalToken() @@ -71,11 +78,7 @@ func runGenerateInternalToken(_ context.Context, c *cli.Command) error { } func runGenerateLfsJwtSecret(_ context.Context, c *cli.Command) error { - _, jwtSecretBase64, err := generate.NewJwtSecretWithBase64() - if err != nil { - return err - } - + _, jwtSecretBase64 := generate.NewJwtSecretWithBase64() fmt.Printf("%s", jwtSecretBase64) if isatty.IsTerminal(os.Stdout.Fd()) { diff --git a/cmd/hook.go b/cmd/hook.go index 6004f679ac..4a6c7c2905 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -28,23 +28,24 @@ const ( hookBatchSize = 500 ) -var ( - // CmdHook represents the available hooks sub-command. - CmdHook = &cli.Command{ +func newHookCommand() *cli.Command { + return &cli.Command{ Name: "hook", Usage: "(internal) Should only be called by Git", Hidden: true, // internal commands shouldn't be visible Description: "Delegate commands to corresponding Git hooks", Before: PrepareConsoleLoggerLevel(log.FATAL), Commands: []*cli.Command{ - subcmdHookPreReceive, - subcmdHookUpdate, - subcmdHookPostReceive, - subcmdHookProcReceive, + newHookPreReceiveCommand(), + newHookUpdateCommand(), + newHookPostReceiveCommand(), + newHookProcReceiveCommand(), }, } +} - subcmdHookPreReceive = &cli.Command{ +func newHookPreReceiveCommand() *cli.Command { + return &cli.Command{ Name: "pre-receive", Usage: "Delegate pre-receive Git hook", Description: "This command should only be called by Git", @@ -55,7 +56,10 @@ var ( }, }, } - subcmdHookUpdate = &cli.Command{ +} + +func newHookUpdateCommand() *cli.Command { + return &cli.Command{ Name: "update", Usage: "Delegate update Git hook", Description: "This command should only be called by Git", @@ -66,7 +70,10 @@ var ( }, }, } - subcmdHookPostReceive = &cli.Command{ +} + +func newHookPostReceiveCommand() *cli.Command { + return &cli.Command{ Name: "post-receive", Usage: "Delegate post-receive Git hook", Description: "This command should only be called by Git", @@ -77,8 +84,11 @@ var ( }, }, } - // Note: new hook since git 2.29 - subcmdHookProcReceive = &cli.Command{ +} + +// Note: new hook since git 2.29 +func newHookProcReceiveCommand() *cli.Command { + return &cli.Command{ Name: "proc-receive", Usage: "Delegate proc-receive Git hook", Description: "This command should only be called by Git", @@ -89,7 +99,7 @@ var ( }, }, } -) +} type delayWriter struct { internal io.Writer @@ -194,7 +204,7 @@ Gitea or set your environment appropriately.`, "") userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64) deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64) - actionPerm, _ := strconv.Atoi(os.Getenv(repo_module.EnvActionPerm)) + actionsTaskID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvActionsTaskID), 10, 64) hookOptions := private.HookOptions{ UserID: userID, @@ -204,7 +214,8 @@ Gitea or set your environment appropriately.`, "") GitPushOptions: pushOptions(), PullRequestID: prID, DeployKeyID: deployKeyID, - ActionPerm: actionPerm, + ActionsTaskID: actionsTaskID, + IsWiki: isWiki, } scanner := bufio.NewScanner(os.Stdin) @@ -275,6 +286,9 @@ Gitea or set your environment appropriately.`, "") lastline = 0 } } + if err := scanner.Err(); err != nil { + return fail(ctx, "Hook failed: stdin read error", "scanner error: %v", err) + } if count > 0 { hookOptions.OldCommitIDs = oldCommitIDs[:count] @@ -366,6 +380,7 @@ Gitea or set your environment appropriately.`, "") GitPushOptions: pushOptions(), PullRequestID: prID, PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)), + IsWiki: isWiki, } oldCommitIDs := make([]string, hookBatchSize) newCommitIDs := make([]string, hookBatchSize) @@ -413,6 +428,11 @@ Gitea or set your environment appropriately.`, "") count = 0 } } + if err := scanner.Err(); err != nil { + _ = dWriter.Close() + hookPrintResults(results) + return fail(ctx, "Hook failed: stdin read error", "scanner error: %v", err) + } if count == 0 { if wasEmpty && masterPushed { @@ -513,6 +533,7 @@ Gitea or set your environment appropriately.`, "") reader := bufio.NewReader(os.Stdin) repoUser := os.Getenv(repo_module.EnvRepoUsername) + isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki)) repoName := os.Getenv(repo_module.EnvRepoName) pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) pusherName := os.Getenv(repo_module.EnvPusherName) @@ -590,6 +611,7 @@ Gitea or set your environment appropriately.`, "") UserName: pusherName, UserID: pusherID, GitPushOptions: make(map[string]string), + IsWiki: isWiki, } hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize) hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize) diff --git a/cmd/keys.go b/cmd/keys.go index 035d39bfb8..912cf25091 100644 --- a/cmd/keys.go +++ b/cmd/keys.go @@ -15,40 +15,42 @@ import ( "github.com/urfave/cli/v3" ) -// CmdKeys represents the available keys sub-command -var CmdKeys = &cli.Command{ - Name: "keys", - Usage: "(internal) Should only be called by SSH server", - Hidden: true, // internal commands shouldn't be visible - Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint", - Before: PrepareConsoleLoggerLevel(log.FATAL), - Action: runKeys, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "expected", - Aliases: []string{"e"}, - Value: "git", - Usage: "Expected user for whom provide key commands", +// NewKeysCommand returns the internal SSH key lookup sub-command. +func NewKeysCommand() *cli.Command { + return &cli.Command{ + Name: "keys", + Usage: "(internal) Should only be called by SSH server", + Hidden: true, // internal commands shouldn't be visible + Description: "Queries the Gitea database to get the authorized command for a given ssh key fingerprint", + Before: PrepareConsoleLoggerLevel(log.FATAL), + Action: runKeys, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "expected", + Aliases: []string{"e"}, + Value: "git", + Usage: "Expected user for whom provide key commands", + }, + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Value: "", + Usage: "Username trying to log in by SSH", + }, + &cli.StringFlag{ + Name: "type", + Aliases: []string{"t"}, + Value: "", + Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)", + }, + &cli.StringFlag{ + Name: "content", + Aliases: []string{"k"}, + Value: "", + Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)", + }, }, - &cli.StringFlag{ - Name: "username", - Aliases: []string{"u"}, - Value: "", - Usage: "Username trying to log in by SSH", - }, - &cli.StringFlag{ - Name: "type", - Aliases: []string{"t"}, - Value: "", - Usage: "Type of the SSH key provided to the SSH Server (requires content to be provided too)", - }, - &cli.StringFlag{ - Name: "content", - Aliases: []string{"k"}, - Value: "", - Usage: "Base64 encoded content of the SSH key provided to the SSH Server (requires type to be provided too)", - }, - }, + } } func runKeys(ctx context.Context, c *cli.Command) error { diff --git a/cmd/mailer.go b/cmd/mailer.go index 72bd8e5601..a7dbd68e20 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -17,10 +17,10 @@ func runSendMail(ctx context.Context, c *cli.Command) error { setting.MustInstalled() subject := c.String("title") - confirmSkiped := c.Bool("force") + confirmSkipped := c.Bool("force") body := c.String("content") - if !confirmSkiped { + if !confirmSkipped { if len(body) == 0 { fmt.Print("warning: Content is empty") } diff --git a/cmd/main.go b/cmd/main.go index 2ee00382d7..a6b89a6fad 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -112,35 +112,36 @@ func NewMainApp(appVer AppVersion) *cli.Command { Usage: "Set custom path (defaults to '{WorkPath}/custom')", }, } + webCmd := newWebCommand() // these sub-commands need to use a config file subCmdWithConfig := []*cli.Command{ - CmdWeb, - CmdServ, - CmdHook, - CmdKeys, - CmdDump, - CmdAdmin, - CmdMigrate, - CmdDoctor, - CmdManager, - CmdEmbedded, - CmdMigrateStorage, - CmdDumpRepository, - CmdRestoreRepository, - CmdActions, + webCmd, + newServCommand(), + newHookCommand(), + NewKeysCommand(), + newDumpCommand(), + newAdminCommand(), + newMigrateCommand(), + newDoctorCommand(), + newManagerCommand(), + newEmbeddedCommand(), + newMigrateStorageCommand(), + newDumpRepositoryCommand(), + newRestoreRepositoryCommand(), + newActionsCommand(), } // these sub-commands do not need the config file, and they do not depend on any path or environment variable. subCmdStandalone := []*cli.Command{ cmdConfig(), cmdCert(), - CmdGenerate, - CmdDocs, + newGenerateCommand(), + newDocsCommand(), } // TODO: we should eventually drop the default command, // but not sure whether it would break Windows users who used to double-click the EXE to run. - app.DefaultCommand = CmdWeb.Name + app.DefaultCommand = webCmd.Name app.Before = PrepareConsoleLoggerLevel(log.INFO) for i := range subCmdWithConfig { diff --git a/cmd/manager.go b/cmd/manager.go index f0935ea065..586c65990b 100644 --- a/cmd/manager.go +++ b/cmd/manager.go @@ -13,22 +13,24 @@ import ( "github.com/urfave/cli/v3" ) -var ( - // CmdManager represents the manager command - CmdManager = &cli.Command{ +func newManagerCommand() *cli.Command { + return &cli.Command{ Name: "manager", Usage: "Manage the running gitea process", Description: "This is a command for managing the running gitea process", Commands: []*cli.Command{ - subcmdShutdown, - subcmdRestart, - subcmdReloadTemplates, - subcmdFlushQueues, - subcmdLogging, - subCmdProcesses, + newShutdownCommand(), + newRestartCommand(), + newReloadTemplatesCommand(), + newFlushQueuesCommand(), + newLoggingCommand(), + newProcessesCommand(), }, } - subcmdShutdown = &cli.Command{ +} + +func newShutdownCommand() *cli.Command { + return &cli.Command{ Name: "shutdown", Usage: "Gracefully shutdown the running process", Flags: []cli.Flag{ @@ -38,7 +40,10 @@ var ( }, Action: runShutdown, } - subcmdRestart = &cli.Command{ +} + +func newRestartCommand() *cli.Command { + return &cli.Command{ Name: "restart", Usage: "Gracefully restart the running process - (not implemented for windows servers)", Flags: []cli.Flag{ @@ -48,7 +53,10 @@ var ( }, Action: runRestart, } - subcmdReloadTemplates = &cli.Command{ +} + +func newReloadTemplatesCommand() *cli.Command { + return &cli.Command{ Name: "reload-templates", Usage: "Reload template files in the running process", Flags: []cli.Flag{ @@ -58,7 +66,10 @@ var ( }, Action: runReloadTemplates, } - subcmdFlushQueues = &cli.Command{ +} + +func newFlushQueuesCommand() *cli.Command { + return &cli.Command{ Name: "flush-queues", Usage: "Flush queues in the running process", Action: runFlushQueues, @@ -77,7 +88,10 @@ var ( }, }, } - subCmdProcesses = &cli.Command{ +} + +func newProcessesCommand() *cli.Command { + return &cli.Command{ Name: "processes", Usage: "Display running processes within the current process", Action: runProcesses, @@ -107,7 +121,7 @@ var ( }, }, } -) +} func runShutdown(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go index ac29e7d3e5..5812e707e2 100644 --- a/cmd/manager_logging.go +++ b/cmd/manager_logging.go @@ -15,8 +15,8 @@ import ( "github.com/urfave/cli/v3" ) -var ( - defaultLoggingFlags = []cli.Flag{ +func defaultLoggingFlags() []cli.Flag { + return []cli.Flag{ &cli.StringFlag{ Name: "logger", Usage: `Logger name - will default to "default"`, @@ -57,8 +57,10 @@ var ( Name: "debug", }, } +} - subcmdLogging = &cli.Command{ +func newLoggingCommand() *cli.Command { + return &cli.Command{ Name: "logging", Usage: "Adjust logging commands", Commands: []*cli.Command{ @@ -109,7 +111,7 @@ var ( { Name: "file", Usage: "Add a file logger", - Flags: append(defaultLoggingFlags, []cli.Flag{ + Flags: append(defaultLoggingFlags(), []cli.Flag{ &cli.StringFlag{ Name: "filename", Aliases: []string{"f"}, @@ -150,7 +152,7 @@ var ( }, { Name: "conn", Usage: "Add a net conn logger", - Flags: append(defaultLoggingFlags, []cli.Flag{ + Flags: append(defaultLoggingFlags(), []cli.Flag{ &cli.BoolFlag{ Name: "reconnect-on-message", Aliases: []string{"R"}, @@ -191,7 +193,7 @@ var ( }, }, } -) +} func runRemoveLogger(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) diff --git a/cmd/migrate.go b/cmd/migrate.go index e24dc9e572..016f8a0db5 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -14,12 +14,13 @@ import ( "github.com/urfave/cli/v3" ) -// CmdMigrate represents the available migrate sub-command. -var CmdMigrate = &cli.Command{ - Name: "migrate", - Usage: "Migrate the database", - Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`, - Action: runMigrate, +func newMigrateCommand() *cli.Command { + return &cli.Command{ + Name: "migrate", + Usage: "Migrate the database", + Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`, + Action: runMigrate, + } } func runMigrate(ctx context.Context, c *cli.Command) error { diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go index a6bf9fa4b5..c9b82055c1 100644 --- a/cmd/migrate_storage.go +++ b/cmd/migrate_storage.go @@ -25,107 +25,108 @@ import ( "github.com/urfave/cli/v3" ) -// CmdMigrateStorage represents the available migrate storage sub-command. -var CmdMigrateStorage = &cli.Command{ - Name: "migrate-storage", - Usage: "Migrate the storage", - Description: "Copies stored files from storage configured in app.ini to parameter-configured storage", - Action: runMigrateStorage, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "type", - Aliases: []string{"t"}, - Value: "", - Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts'", +func newMigrateStorageCommand() *cli.Command { + return &cli.Command{ + Name: "migrate-storage", + Usage: "Migrate the storage", + Description: "Copies stored files from storage configured in app.ini to parameter-configured storage", + Action: runMigrateStorage, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "type", + Aliases: []string{"t"}, + Value: "", + Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts'", + }, + &cli.StringFlag{ + Name: "storage", + Aliases: []string{"s"}, + Value: "", + Usage: "New storage type: local (default), minio or azureblob", + }, + &cli.StringFlag{ + Name: "path", + Aliases: []string{"p"}, + Value: "", + Usage: "New storage placement if store is local (leave blank for default)", + }, + // Minio Storage special configurations + &cli.StringFlag{ + Name: "minio-endpoint", + Value: "", + Usage: "Minio storage endpoint", + }, + &cli.StringFlag{ + Name: "minio-access-key-id", + Value: "", + Usage: "Minio storage accessKeyID", + }, + &cli.StringFlag{ + Name: "minio-secret-access-key", + Value: "", + Usage: "Minio storage secretAccessKey", + }, + &cli.StringFlag{ + Name: "minio-bucket", + Value: "", + Usage: "Minio storage bucket", + }, + &cli.StringFlag{ + Name: "minio-location", + Value: "", + Usage: "Minio storage location to create bucket", + }, + &cli.StringFlag{ + Name: "minio-base-path", + Value: "", + Usage: "Minio storage base path on the bucket", + }, + &cli.BoolFlag{ + Name: "minio-use-ssl", + Usage: "Enable SSL for minio", + }, + &cli.BoolFlag{ + Name: "minio-insecure-skip-verify", + Usage: "Skip SSL verification", + }, + &cli.StringFlag{ + Name: "minio-checksum-algorithm", + Value: "", + Usage: "Minio checksum algorithm (default/md5)", + }, + &cli.StringFlag{ + Name: "minio-bucket-lookup-type", + Value: "", + Usage: "Minio bucket lookup type", + }, + // Azure Blob Storage special configurations + &cli.StringFlag{ + Name: "azureblob-endpoint", + Value: "", + Usage: "Azure Blob storage endpoint", + }, + &cli.StringFlag{ + Name: "azureblob-account-name", + Value: "", + Usage: "Azure Blob storage account name", + }, + &cli.StringFlag{ + Name: "azureblob-account-key", + Value: "", + Usage: "Azure Blob storage account key", + }, + &cli.StringFlag{ + Name: "azureblob-container", + Value: "", + Usage: "Azure Blob storage container", + }, + &cli.StringFlag{ + Name: "azureblob-base-path", + Value: "", + Usage: "Azure Blob storage base path", + }, }, - &cli.StringFlag{ - Name: "storage", - Aliases: []string{"s"}, - Value: "", - Usage: "New storage type: local (default), minio or azureblob", - }, - &cli.StringFlag{ - Name: "path", - Aliases: []string{"p"}, - Value: "", - Usage: "New storage placement if store is local (leave blank for default)", - }, - // Minio Storage special configurations - &cli.StringFlag{ - Name: "minio-endpoint", - Value: "", - Usage: "Minio storage endpoint", - }, - &cli.StringFlag{ - Name: "minio-access-key-id", - Value: "", - Usage: "Minio storage accessKeyID", - }, - &cli.StringFlag{ - Name: "minio-secret-access-key", - Value: "", - Usage: "Minio storage secretAccessKey", - }, - &cli.StringFlag{ - Name: "minio-bucket", - Value: "", - Usage: "Minio storage bucket", - }, - &cli.StringFlag{ - Name: "minio-location", - Value: "", - Usage: "Minio storage location to create bucket", - }, - &cli.StringFlag{ - Name: "minio-base-path", - Value: "", - Usage: "Minio storage base path on the bucket", - }, - &cli.BoolFlag{ - Name: "minio-use-ssl", - Usage: "Enable SSL for minio", - }, - &cli.BoolFlag{ - Name: "minio-insecure-skip-verify", - Usage: "Skip SSL verification", - }, - &cli.StringFlag{ - Name: "minio-checksum-algorithm", - Value: "", - Usage: "Minio checksum algorithm (default/md5)", - }, - &cli.StringFlag{ - Name: "minio-bucket-lookup-type", - Value: "", - Usage: "Minio bucket lookup type", - }, - // Azure Blob Storage special configurations - &cli.StringFlag{ - Name: "azureblob-endpoint", - Value: "", - Usage: "Azure Blob storage endpoint", - }, - &cli.StringFlag{ - Name: "azureblob-account-name", - Value: "", - Usage: "Azure Blob storage account name", - }, - &cli.StringFlag{ - Name: "azureblob-account-key", - Value: "", - Usage: "Azure Blob storage account key", - }, - &cli.StringFlag{ - Name: "azureblob-container", - Value: "", - Usage: "Azure Blob storage container", - }, - &cli.StringFlag{ - Name: "azureblob-base-path", - Value: "", - Usage: "Azure Blob storage base path", - }, - }, + } } func migrateAttachments(ctx context.Context, dstStorage storage.ObjectStorage) error { diff --git a/cmd/restore_repo.go b/cmd/restore_repo.go index c61f5a582e..26b4682f13 100644 --- a/cmd/restore_repo.go +++ b/cmd/restore_repo.go @@ -13,40 +13,41 @@ import ( "github.com/urfave/cli/v3" ) -// CmdRestoreRepository represents the available restore a repository sub-command. -var CmdRestoreRepository = &cli.Command{ - Name: "restore-repo", - Usage: "Restore the repository from disk", - Description: "This is a command for restoring the repository data.", - Action: runRestoreRepository, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "repo_dir", - Aliases: []string{"r"}, - Value: "./data", - Usage: "Repository dir path to restore from", - }, - &cli.StringFlag{ - Name: "owner_name", - Value: "", - Usage: "Restore destination owner name", - }, - &cli.StringFlag{ - Name: "repo_name", - Value: "", - Usage: "Restore destination repository name", - }, - &cli.StringFlag{ - Name: "units", - Value: "", - Usage: `Which items will be restored, one or more units should be separated as comma. +func newRestoreRepositoryCommand() *cli.Command { + return &cli.Command{ + Name: "restore-repo", + Usage: "Restore the repository from disk", + Description: "This is a command for restoring the repository data.", + Action: runRestoreRepository, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "repo_dir", + Aliases: []string{"r"}, + Value: "./data", + Usage: "Repository dir path to restore from", + }, + &cli.StringFlag{ + Name: "owner_name", + Value: "", + Usage: "Restore destination owner name", + }, + &cli.StringFlag{ + Name: "repo_name", + Value: "", + Usage: "Restore destination repository name", + }, + &cli.StringFlag{ + Name: "units", + Value: "", + Usage: `Which items will be restored, one or more units should be separated as comma. wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`, + }, + &cli.BoolFlag{ + Name: "validation", + Usage: "Sanity check the content of the files before trying to load them", + }, }, - &cli.BoolFlag{ - Name: "validation", - Usage: "Sanity check the content of the files before trying to load them", - }, - }, + } } func runRestoreRepository(ctx context.Context, c *cli.Command) error { diff --git a/cmd/serv.go b/cmd/serv.go index 4110fda0d5..a35d476c86 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -35,22 +35,23 @@ import ( "github.com/urfave/cli/v3" ) -// CmdServ represents the available serv sub-command. -var CmdServ = &cli.Command{ - Name: "serv", - Usage: "(internal) Should only be called by SSH shell", - Description: "Serv provides access auth for repositories", - Hidden: true, // Internal commands shouldn't be visible in help - Before: PrepareConsoleLoggerLevel(log.FATAL), - Action: runServ, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "enable-pprof", +func newServCommand() *cli.Command { + return &cli.Command{ + Name: "serv", + Usage: "(internal) Should only be called by SSH shell", + Description: "Serv provides access auth for repositories", + Hidden: true, // Internal commands shouldn't be visible in help + Before: PrepareConsoleLoggerLevel(log.FATAL), + Action: runServ, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "enable-pprof", + }, + &cli.BoolFlag{ + Name: "debug", + }, }, - &cli.BoolFlag{ - Name: "debug", - }, - }, + } } func setup(ctx context.Context, debug bool) { diff --git a/cmd/web.go b/cmd/web.go index 5000e780c5..994c481fc0 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -34,42 +34,43 @@ import ( // PIDFile could be set from build tag var PIDFile = "/run/gitea.pid" -// CmdWeb represents the available web sub-command. -var CmdWeb = &cli.Command{ - Name: "web", - Usage: "Start Gitea web server", - Description: `Gitea web server is the only thing you need to run, +func newWebCommand() *cli.Command { + return &cli.Command{ + Name: "web", + Usage: "Start Gitea web server", + Description: `Gitea web server is the only thing you need to run, and it takes care of all the other things for you`, - Before: PrepareConsoleLoggerLevel(log.INFO), - Action: runWeb, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "port", - Aliases: []string{"p"}, - Value: "3000", - Usage: "Temporary port number to prevent conflict", + Before: PrepareConsoleLoggerLevel(log.INFO), + Action: runWeb, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "port", + Aliases: []string{"p"}, + Value: "3000", + Usage: "Temporary port number to prevent conflict", + }, + &cli.StringFlag{ + Name: "install-port", + Value: "3000", + Usage: "Temporary port number to run the install page on to prevent conflict", + }, + &cli.StringFlag{ + Name: "pid", + Aliases: []string{"P"}, + Value: PIDFile, + Usage: "Custom pid file path", + }, + &cli.BoolFlag{ + Name: "quiet", + Aliases: []string{"q"}, + Usage: "Only display Fatal logging errors until logging is set-up", + }, + &cli.BoolFlag{ + Name: "verbose", + Usage: "Set initial logging to TRACE level until logging is properly set-up", + }, }, - &cli.StringFlag{ - Name: "install-port", - Value: "3000", - Usage: "Temporary port number to run the install page on to prevent conflict", - }, - &cli.StringFlag{ - Name: "pid", - Aliases: []string{"P"}, - Value: PIDFile, - Usage: "Custom pid file path", - }, - &cli.BoolFlag{ - Name: "quiet", - Aliases: []string{"q"}, - Usage: "Only display Fatal logging errors until logging is set-up", - }, - &cli.BoolFlag{ - Name: "verbose", - Usage: "Set initial logging to TRACE level until logging is properly set-up", - }, - }, + } } func runHTTPRedirector() { diff --git a/contrib/backport/backport.go b/contrib/backport/backport.go index 266b3c0640..0c10c59bd8 100644 --- a/contrib/backport/backport.go +++ b/contrib/backport/backport.go @@ -16,7 +16,7 @@ import ( "strconv" "strings" - "github.com/google/go-github/v74/github" + "github.com/google/go-github/v84/github" "github.com/urfave/cli/v3" "go.yaml.in/yaml/v4" ) diff --git a/contrib/upgrade.sh b/contrib/upgrade.sh index e5e296ea8b..2593d24509 100755 --- a/contrib/upgrade.sh +++ b/contrib/upgrade.sh @@ -108,7 +108,9 @@ curl --connect-timeout 10 --silent --show-error --fail --location -O "$binurl{,. sha256sum -c "${binname}.xz.sha256" if [[ -z "${ignore_gpg:-}" ]]; then require gpg - gpg --keyserver keys.openpgp.org --recv 7C9E68152594688862D62AF62D9AE806EC1592E2 + # try to use curl first, it uses standard tcp 443 port and works better behind strict firewall rules + curl -fsSL --connect-timeout 10 "https://keys.openpgp.org/vks/v1/by-fingerprint/7C9E68152594688862D62AF62D9AE806EC1592E2" | gpg --import \ + || gpg --keyserver keys.openpgp.org --recv 7C9E68152594688862D62AF62D9AE806EC1592E2 gpg --verify "${binname}.xz.asc" "${binname}.xz" || { echo 'Signature does not match'; exit 1; } fi rm "${binname}".xz.{sha256,asc} @@ -127,6 +129,8 @@ echo "Creating backup in $giteahome" giteacmd dump $backupopts echo "Updating binary at $giteabin" cp -f "$giteabin" "$giteabin.bak" && mv -f "$binname" "$giteabin" +# Restore SELinux context if applicable (e.g. RHEL/Fedora) +command -v restorecon &>/dev/null && restorecon -v "$giteabin" || true $service_start $service_status diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index f5e8f03ae2..c714ed9660 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -69,12 +69,12 @@ RUN_USER = ; git ;; Most users should set it to the real website URL of their Gitea instance when there is a reverse proxy. ;ROOT_URL = ;; -;; Controls how to detect the public URL. -;; Although it defaults to "legacy" (to avoid breaking existing users), most instances should use the "auto" behavior, +;; Controls how to detect the public URL. Most instances should use the "auto" behavior, ;; especially when the Gitea instance needs to be accessed in a container network. -;; * legacy: detect the public URL from "Host" header if "X-Forwarded-Proto" header exists, otherwise use "ROOT_URL". -;; * auto: always use "Host" header, and also use "X-Forwarded-Proto" header if it exists. If no "Host" header, use "ROOT_URL". -;PUBLIC_URL_DETECTION = legacy +;; * legacy: (default <= 1.25) detect the public URL from "Host" header if "X-Forwarded-Proto" header exists, otherwise use "ROOT_URL". +;; * auto: (default >= 1.26) always use "Host" header, and also use "X-Forwarded-Proto" header if it exists. If no "Host" header, use "ROOT_URL". +;; * never: always use "ROOT_URL", never detect from request headers. +;PUBLIC_URL_DETECTION = auto ;; ;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy. ;; DO NOT USE IT IN PRODUCTION!!! @@ -175,14 +175,15 @@ RUN_USER = ; git ;; The port number the builtin SSH server should listen on, defaults to SSH_PORT ;SSH_LISTEN_PORT = ;; -;; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'. +;; Root path of SSH user directory for the system's standalone SSH server if Gitea is not using its builtin SSH server. +;; Default is the '.ssh' directory in the run user's home directory. ;SSH_ROOT_PATH = ;; -;; Gitea will create a authorized_keys file by default when it is not using the internal ssh server +;; Gitea will create an authorized_keys file by default when it is not using the builtin SSH server ;; If you intend to use the AuthorizedKeysCommand functionality then you should turn this off. ;SSH_CREATE_AUTHORIZED_KEYS_FILE = true ;; -;; Gitea will create a authorized_principals file by default when it is not using the internal ssh server +;; Gitea will create an authorized_principals file by default when it is not using the builtin SSH server ;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off. ;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true ;; @@ -238,9 +239,6 @@ RUN_USER = ; git ;; Indicate whether to check minimum key size with corresponding type ;MINIMUM_KEY_SIZE_CHECK = false ;; -;; Disable CDN even in "prod" mode -;OFFLINE_MODE = true -;; ;; TLS Settings: Either ACME or manual ;; (Other common TLS configuration are found before) ;ENABLE_ACME = false @@ -463,6 +461,11 @@ INTERNAL_TOKEN = ;; Name of cookie used to store authentication information. ;COOKIE_REMEMBER_NAME = gitea_incredible ;; +;; URL or path that Gitea should redirect users to *after* performing its own logout. +;; Use this, if needed, when authentication is handled by a reverse proxy or SSO. +;; For example: "/my-sso/logout?return=/my-sso/home" +;REVERSE_PROXY_LOGOUT_REDIRECT = +;; ;; Reverse proxy authentication header name of user name, email, and full name ;REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER ;REVERSE_PROXY_AUTHENTICATION_EMAIL = X-WEBAUTH-EMAIL @@ -1155,15 +1158,15 @@ LEVEL = Info ;; Add co-authored-by and co-committed-by trailers if committer does not match author ;ADD_CO_COMMITTER_TRAILERS = true ;; -;; In addition to testing patches using the three-way merge method, re-test conflicting patches with git apply -;TEST_CONFLICTING_PATCHES_WITH_GIT_APPLY = false -;; ;; Retarget child pull requests to the parent pull request branch target on merge of parent pull request. It only works on merged PRs where the head and base branch target the same repo. ;RETARGET_CHILDREN_ON_MERGE = true ;; ;; Delay mergeable check until page view or API access, for pull requests that have not been updated in the specified days when their base branches get updated. ;; Use "-1" to always check all pull requests (old behavior). Use "0" to always delay the checks. ;DELAY_CHECK_FOR_INACTIVE_DAYS = 7 +;; +;; Set the default value for "Delete pull request branch after merge by default" for new repositories +;DEFAULT_DELETE_BRANCH_AFTER_MERGE = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1181,16 +1184,16 @@ LEVEL = Info ;[repository.release] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. +;; Comma-separated list of allowed release attachment file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. ;ALLOWED_TYPES = ;; ;; Number of releases that are displayed on release page ;DEFAULT_PAGING_NUM = 10 ;; -;; Max size of each file in megabytes. Defaults to 2GB +;; Max size of each release attachment file in megabytes. Defaults to 2GB ;FILE_MAX_SIZE = 2048 ;; -;; Max number of files per upload. Defaults to 5 +;; Max number of release attachment files per upload. Defaults to 5 ;MAX_FILES = 5 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1983,12 +1986,12 @@ LEVEL = Info ;; or a custom avatar source, like: http://cn.gravatar.com/avatar/ ;GRAVATAR_SOURCE = gravatar ;; -;; This value will always be true in offline mode. +;; Deprecated, see Web UI Admin Panel -> Config -> Settings ;DISABLE_GRAVATAR = false ;; ;; Federated avatar lookup uses DNS to discover avatar associated ;; with emails, see https://www.libravatar.org -;; This value will always be false in offline mode or when Gravatar is disabled. +;; Deprecated, see Web UI Admin Panel -> Config -> Settings ;ENABLE_FEDERATED_AVATAR = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1997,16 +2000,18 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; Whether issue and pull request attachments are enabled. Defaults to `true` +;; Whether issue, pull-request and release attachments are enabled. Defaults to `true` +;; ALLOWED_TYPES/MAX_SIZE/MAX_FILES in this section only affect issue and pull-request attachments, not release attachments. +;; Release attachment has its own config options in [repository.release] section. ;ENABLED = true ;; -;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. +;; Comma-separated list of allowed issue/pull-request attachment file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. ;ALLOWED_TYPES = .avif,.cpuprofile,.csv,.dmp,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.json,.jsonc,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.webp,.xls,.xlsx,.zip ;; -;; Max size of each file. Defaults to 100MB +;; Max size of each issue/pull-request attachment file. Defaults to 100MB ;MAX_SIZE = 100 ;; -;; Max number of files per upload. Defaults to 5 +;; Max number of issue/pull-request attachment files per upload. Defaults to 5 ;MAX_FILES = 5 ;; ;; Storage type for attachments, `local` for local disk or `minio` for s3 compatible @@ -2279,6 +2284,22 @@ LEVEL = Info ;; Unreferenced blobs created more than OLDER_THAN ago are subject to deletion ;OLDER_THAN = 24h +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Synchronize repository licenses +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.sync_repo_licenses] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Whether to enable the job +;ENABLED = false +;; Whether to always run at least once at start up time (if ENABLED) +;RUN_AT_START = false +;; Whether to emit notice on successful execution too +;NOTICE_ON_SUCCESS = false +;; Time interval for job to run +;SCHEDULE = @annually + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -2340,6 +2361,18 @@ LEVEL = Info ;NOTICE_ON_SUCCESS = false ;SCHEDULE = @every 72h +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Update the '.ssh/authorized_principals' file +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.resync_all_sshprincipals] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = false +;RUN_AT_START = false +;NOTICE_ON_SUCCESS = false +;SCHEDULE = @every 72h + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Resynchronize git hooks of all repositories (pre-receive, update, post-receive, proc-receive, ...) @@ -2448,6 +2481,70 @@ LEVEL = Info ;Check at least this proportion of LFSMetaObjects per repo. (This may cause all stale LFSMetaObjects to be checked.) ;PROPORTION_TO_CHECK_PER_REPO = 0.6 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rebuild issue index +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.rebuild_issue_indexer] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = false +;RUN_AT_START = false +;NO_SUCCESS_NOTICE = false +;SCHEDULE = @annually + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Actions cron tasks +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Stop running tasks which haven't been updated for a long time +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.stop_zombie_tasks] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = true +;RUN_AT_START = true +;NO_SUCCESS_NOTICE = false +;SCHEDULE = @every 5m + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Stop running tasks which have running status and continuous updates but don't end for a long time +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.stop_endless_tasks] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = true +;RUN_AT_START = true +;NO_SUCCESS_NOTICE = false +;SCHEDULE = @every 30m + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Cancel jobs which haven't been picked up for a long time +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.cancel_abandoned_jobs] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = true +;RUN_AT_START = false +;NO_SUCCESS_NOTICE = false +;SCHEDULE = @every 6h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Start cron based actions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.start_schedule_tasks] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = true +;RUN_AT_START = false +;NO_SUCCESS_NOTICE = false +;SCHEDULE = @every 1m + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[mirror] @@ -2701,6 +2798,8 @@ LEVEL = Info ;LIMIT_SIZE_SWIFT = -1 ;; Maximum size of a Vagrant upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;LIMIT_SIZE_VAGRANT = -1 +;; Maximum size of a Terraform state upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) +;LIMIT_SIZE_TERRAFORM_STATE = -1 ;; Enable RPM re-signing by default. (It will overwrite the old signature ,using v4 format, not compatible with CentOS 6 or older) ;DEFAULT_RPM_SIGN_ENABLED = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docker/root/etc/s6/openssh/finish b/docker/root/etc/s6/openssh/finish index 06bd986563..8e89b35c51 100755 --- a/docker/root/etc/s6/openssh/finish +++ b/docker/root/etc/s6/openssh/finish @@ -1,2 +1,8 @@ #!/bin/bash +# $1 = exit code of the run script, $2 = signal +if [ "$1" -ne 0 ]; then + # avoid immediately restarting the sshd service, which may cause CPU 100% if the error (permission, configuration) is not fixed + echo "openssh failed with exit code $1 - waiting a short delay before attempting a restart" + sleep 3 +fi exit 0 diff --git a/eslint.config.ts b/eslint.config.ts index 53edfc364d..5b7884bdce 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -1,5 +1,6 @@ import arrayFunc from 'eslint-plugin-array-func'; import comments from '@eslint-community/eslint-plugin-eslint-comments'; +import deMorgan from 'eslint-plugin-de-morgan'; import github from 'eslint-plugin-github'; import globals from 'globals'; import importPlugin from 'eslint-plugin-import-x'; @@ -63,6 +64,7 @@ export default defineConfig([ '@stylistic': stylistic, '@typescript-eslint': typescriptPlugin.plugin, 'array-func': arrayFunc, + 'de-morgan': deMorgan, 'import-x': importPlugin as unknown as ESLint.Plugin, // https://github.com/un-ts/eslint-plugin-import-x/issues/203 regexp, sonarjs, @@ -179,7 +181,7 @@ export default defineConfig([ '@typescript-eslint/naming-convention': [0], '@typescript-eslint/no-array-constructor': [2], '@typescript-eslint/no-array-delete': [2], - '@typescript-eslint/no-base-to-string': [0], + '@typescript-eslint/no-base-to-string': [2], '@typescript-eslint/no-confusing-non-null-assertion': [2], '@typescript-eslint/no-confusing-void-expression': [0], '@typescript-eslint/no-deprecated': [2], @@ -254,10 +256,10 @@ export default defineConfig([ '@typescript-eslint/prefer-function-type': [2], '@typescript-eslint/prefer-includes': [2], '@typescript-eslint/prefer-literal-enum-member': [0], - '@typescript-eslint/prefer-namespace-keyword': [0], + '@typescript-eslint/prefer-namespace-keyword': [2], '@typescript-eslint/prefer-nullish-coalescing': [0], '@typescript-eslint/prefer-optional-chain': [2, {requireNullish: true}], - '@typescript-eslint/prefer-promise-reject-errors': [0], + '@typescript-eslint/prefer-promise-reject-errors': [2], '@typescript-eslint/prefer-readonly': [0], '@typescript-eslint/prefer-readonly-parameter-types': [0], '@typescript-eslint/prefer-reduce-type-parameter': [0], @@ -268,7 +270,7 @@ export default defineConfig([ '@typescript-eslint/require-array-sort-compare': [0], '@typescript-eslint/require-await': [0], '@typescript-eslint/restrict-plus-operands': [2], - '@typescript-eslint/restrict-template-expressions': [0], + '@typescript-eslint/restrict-template-expressions': [2], '@typescript-eslint/return-await': [0], '@typescript-eslint/strict-boolean-expressions': [0], '@typescript-eslint/strict-void-return': [0], @@ -295,6 +297,8 @@ export default defineConfig([ 'consistent-this': [0], 'constructor-super': [2], 'curly': [0], + 'de-morgan/no-negated-conjunction': [2], + 'de-morgan/no-negated-disjunction': [2], 'default-case-last': [2], 'default-case': [0], 'default-param-last': [0], @@ -325,7 +329,7 @@ export default defineConfig([ 'github/no-innerText': [2], 'github/no-then': [2], 'github/no-useless-passive': [2], - 'github/prefer-observers': [2], + 'github/prefer-observers': [0], 'github/require-passive-events': [2], 'github/unescaped-html-literal': [2], 'grouped-accessor-pairs': [2], @@ -568,7 +572,11 @@ export default defineConfig([ 'no-restricted-exports': [0], 'no-restricted-globals': [2, ...restrictedGlobals], 'no-restricted-properties': [2, ...restrictedProperties], - 'no-restricted-imports': [0], + 'no-restricted-imports': [2, {paths: [ + {name: 'jquery', message: 'Use the global $ instead', allowTypeImports: true}, + {name: 'htmx.org', message: 'Use the global htmx instead', allowTypeImports: true}, + {name: 'idiomorph/htmx', message: 'Loaded in globals.ts', allowTypeImports: true}, + ]}], 'no-restricted-syntax': [2, 'WithStatement', 'ForInStatement', 'LabeledStatement', 'SequenceExpression'], 'no-return-assign': [0], 'no-script-url': [2], @@ -586,6 +594,7 @@ export default defineConfig([ 'no-undef-init': [2], 'no-undef': [2], // it is still needed by eslint & IDE to prompt undefined names in real time 'no-undefined': [0], + 'no-unassigned-vars': [2], 'no-underscore-dangle': [0], 'no-unexpected-multiline': [2], 'no-unmodified-loop-condition': [2], @@ -626,19 +635,20 @@ export default defineConfig([ 'prefer-numeric-literals': [2], 'prefer-object-has-own': [2], 'prefer-object-spread': [2], - 'prefer-promise-reject-errors': [2, {allowEmptyReject: false}], + 'prefer-promise-reject-errors': [0], // handled by @typescript-eslint/prefer-promise-reject-errors 'prefer-regex-literals': [2], 'prefer-rest-params': [2], 'prefer-spread': [2], 'prefer-template': [2], - 'radix': [2, 'as-needed'], + 'preserve-caught-error': [0], + 'radix': [0], 'regexp/confusing-quantifier': [2], 'regexp/control-character-escape': [2], 'regexp/hexadecimal-escape': [0], 'regexp/letter-case': [0], 'regexp/match-any': [2], 'regexp/negation': [2], - 'regexp/no-contradiction-with-assertion': [0], + 'regexp/no-contradiction-with-assertion': [2], 'regexp/no-control-character': [0], 'regexp/no-dupe-characters-character-class': [2], 'regexp/no-dupe-disjunctions': [2], @@ -654,8 +664,8 @@ export default defineConfig([ 'regexp/no-invisible-character': [2], 'regexp/no-lazy-ends': [2], 'regexp/no-legacy-features': [2], - 'regexp/no-misleading-capturing-group': [0], - 'regexp/no-misleading-unicode-character': [0], + 'regexp/no-misleading-capturing-group': [2], + 'regexp/no-misleading-unicode-character': [2], 'regexp/no-missing-g-flag': [2], 'regexp/no-non-standard-flag': [2], 'regexp/no-obscure-range': [2], @@ -756,6 +766,7 @@ export default defineConfig([ 'unicorn/catch-error-name': [0], 'unicorn/consistent-destructuring': [2], 'unicorn/consistent-empty-array-spread': [2], + 'unicorn/consistent-template-literal-escape': [2], 'unicorn/consistent-existence-index-check': [0], 'unicorn/consistent-function-scoping': [0], 'unicorn/custom-error-definition': [0], @@ -811,6 +822,7 @@ export default defineConfig([ 'unicorn/no-unused-properties': [2], 'unicorn/no-useless-collection-argument': [2], 'unicorn/no-useless-fallback-in-spread': [2], + 'unicorn/no-useless-iterator-to-array': [2], 'unicorn/no-useless-length-check': [2], 'unicorn/no-useless-promise-resolve-reject': [2], 'unicorn/no-useless-spread': [2], @@ -860,6 +872,7 @@ export default defineConfig([ 'unicorn/prefer-response-static-json': [2], 'unicorn/prefer-set-has': [0], 'unicorn/prefer-set-size': [2], + 'unicorn/prefer-simple-condition-first': [0], 'unicorn/prefer-spread': [0], 'unicorn/prefer-string-raw': [0], 'unicorn/prefer-string-replace-all': [0], @@ -878,6 +891,7 @@ export default defineConfig([ 'unicorn/require-post-message-target-origin': [0], 'unicorn/string-content': [0], 'unicorn/switch-case-braces': [0], + 'unicorn/switch-case-break-position': [2], 'unicorn/template-indent': [2], 'unicorn/text-encoding-identifier-case': [0], 'unicorn/throw-new-error': [2], @@ -926,7 +940,7 @@ export default defineConfig([ }, extends: [ vue.configs['flat/recommended'], - vueScopedCss.configs['flat/recommended'] as any, + vueScopedCss.configs.recommended as any, ], rules: { 'vue/attributes-order': [0], @@ -991,6 +1005,7 @@ export default defineConfig([ 'vitest/require-top-level-describe': [0], 'vitest/valid-describe-callback': [2], 'vitest/valid-expect': [2, {maxArgs: 2}], + 'vitest/valid-expect-in-promise': [2], 'vitest/valid-title': [2], }, }, @@ -1002,11 +1017,11 @@ export default defineConfig([ }, }, { - files: ['*', 'tools/**/*'], + files: ['*', 'tools/**/*', 'tests/**/*'], languageOptions: {globals: globals.nodeBuiltin}, }, { files: ['web_src/**/*'], - languageOptions: {globals: {...globals.browser, ...globals.webpack}}, + languageOptions: {globals: {...globals.browser, ...globals.jquery, htmx: false}}, }, ]); diff --git a/flake.lock b/flake.lock index a608aa3b89..25ce7939b3 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1771369470, - "narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=", + "lastModified": 1775036866, + "narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=", "owner": "nixos", "repo": "nixpkgs", - "rev": "0182a361324364ae3f436a63005877674cf45efb", + "rev": "6201e203d09599479a3b3450ed24fa81537ebc4e", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 6fb3891963..7b9fbb193c 100644 --- a/flake.nix +++ b/flake.nix @@ -33,9 +33,9 @@ inherit (pkgs) lib; # only bump toolchain versions here - go = pkgs.go_1_25; + go = pkgs.go_1_26; nodejs = pkgs.nodejs_24; - python3 = pkgs.python312; + python3 = pkgs.python314; pnpm = pkgs.pnpm_10; # Platform-specific dependencies diff --git a/go.mod b/go.mod index 98930f3fea..d67eaaec27 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module code.gitea.io/gitea -go 1.26.0 +go 1.26.2 // rfc5280 said: "The serial number is an integer assigned by the CA to each certificate." // But some CAs use negative serial number, just relax the check. related: @@ -9,7 +9,7 @@ godebug x509negativeserial=1 require ( code.gitea.io/actions-proto-go v0.4.1 - code.gitea.io/sdk/gitea v0.23.2 + code.gitea.io/sdk/gitea v0.24.1 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 connectrpc.com/connect v1.19.1 gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed @@ -18,25 +18,26 @@ require ( gitea.com/go-chi/session v0.0.0-20251124165456-68e0254e989e gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 - github.com/42wim/httpsig v1.2.3 - github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920 + github.com/42wim/httpsig v1.2.4 + github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 - github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 - github.com/ProtonMail/go-crypto v1.3.0 - github.com/PuerkitoBio/goquery v1.11.0 + github.com/Azure/go-ntlmssp v0.1.0 + github.com/ProtonMail/go-crypto v1.4.1 + github.com/PuerkitoBio/goquery v1.12.0 github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0 github.com/alecthomas/chroma/v2 v2.23.1 - github.com/aws/aws-sdk-go-v2/credentials v1.19.7 - github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.8 + github.com/aws/aws-sdk-go-v2/credentials v1.19.14 + github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.12 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/blevesearch/bleve/v2 v2.5.7 github.com/bohde/codel v0.2.0 github.com/buildkite/terminal-to-html/v3 v3.16.8 - github.com/caddyserver/certmagic v0.25.1 + github.com/caddyserver/certmagic v0.25.2 github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21 github.com/chi-middleware/proxy v1.1.1 github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 + github.com/dlclark/regexp2 v1.11.5 github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 github.com/dustin/go-humanize v1.0.1 github.com/editorconfig/editorconfig-core-go/v2 v2.6.4 @@ -46,46 +47,44 @@ require ( github.com/felixge/fgprof v0.9.5 github.com/fsnotify/fsnotify v1.9.0 github.com/gliderlabs/ssh v0.3.8 - github.com/go-ap/activitypub v0.0.0-20250810115208-cb73b20a1742 - github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 github.com/go-chi/chi/v5 v5.2.5 github.com/go-chi/cors v1.2.2 - github.com/go-co-op/gocron v1.37.0 - github.com/go-enry/go-enry/v2 v2.9.4 - github.com/go-git/go-billy/v5 v5.7.0 - github.com/go-git/go-git/v5 v5.16.5 - github.com/go-ldap/ldap/v3 v3.4.12 - github.com/go-redsync/redsync/v4 v4.15.0 + github.com/go-co-op/gocron/v2 v2.20.0 + github.com/go-enry/go-enry/v2 v2.9.6 + github.com/go-git/go-billy/v5 v5.8.0 + github.com/go-git/go-git/v5 v5.17.2 + github.com/go-ldap/ldap/v3 v3.4.13 + github.com/go-redsync/redsync/v4 v4.16.0 github.com/go-sql-driver/mysql v1.9.3 - github.com/go-webauthn/webauthn v0.13.4 - github.com/goccy/go-json v0.10.5 + github.com/go-webauthn/webauthn v0.16.3 + github.com/goccy/go-json v0.10.6 github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 github.com/golang-jwt/jwt/v5 v5.3.1 - github.com/google/go-github/v74 v74.0.0 + github.com/google/go-github/v84 v84.0.0 github.com/google/licenseclassifier/v2 v2.0.0 - github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef + github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 github.com/google/uuid v1.6.0 github.com/gorilla/feeds v1.2.0 github.com/gorilla/sessions v1.4.0 - github.com/hashicorp/go-version v1.8.0 + github.com/hashicorp/go-version v1.9.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/huandu/xstrings v1.5.0 github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 - github.com/jhillyerd/enmime v1.3.0 + github.com/jhillyerd/enmime/v2 v2.3.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/klauspost/compress v1.18.3 + github.com/klauspost/compress v1.18.5 github.com/klauspost/cpuid/v2 v2.3.0 - github.com/lib/pq v1.11.1 + github.com/lib/pq v1.12.3 github.com/markbates/goth v1.82.0 - github.com/mattn/go-isatty v0.0.20 - github.com/mattn/go-sqlite3 v1.14.33 - github.com/meilisearch/meilisearch-go v0.36.0 + github.com/mattn/go-isatty v0.0.21 + github.com/mattn/go-sqlite3 v1.14.42 + github.com/meilisearch/meilisearch-go v0.36.1 github.com/mholt/archives v0.1.5 github.com/microcosm-cc/bluemonday v1.0.27 github.com/microsoft/go-mssqldb v1.9.6 - github.com/minio/minio-go/v7 v7.0.98 - github.com/msteinert/pam v1.2.0 + github.com/minio/minio-go/v7 v7.0.100 + github.com/msteinert/pam/v2 v2.1.0 github.com/nektos/act v0.2.63 github.com/niklasfasching/go-org v1.9.1 github.com/olivere/elastic/v7 v7.0.32 @@ -94,37 +93,37 @@ require ( github.com/pquerna/otp v1.5.0 github.com/prometheus/client_golang v1.23.2 github.com/quasoft/websspi v1.1.2 - github.com/redis/go-redis/v9 v9.17.3 + github.com/redis/go-redis/v9 v9.18.0 github.com/robfig/cron/v3 v3.0.1 - github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/sassoftware/go-rpmutils v0.4.0 github.com/sergi/go-diff v1.4.0 github.com/stretchr/testify v1.11.1 github.com/syndtr/goleveldb v1.0.0 github.com/tstranex/u2f v1.0.0 github.com/ulikunitz/xz v0.5.15 - github.com/urfave/cli-docs/v3 v3.0.0-alpha6 + github.com/urfave/cli-docs/v3 v3.1.0 github.com/urfave/cli/v3 v3.4.1 github.com/wneessen/go-mail v0.7.2 github.com/xeipuuv/gojsonschema v1.2.0 github.com/yohcop/openid-go v1.0.1 - github.com/yuin/goldmark v1.7.16 + github.com/yuin/goldmark v1.8.2 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc - github.com/yuin/goldmark-meta v1.1.0 - gitlab.com/gitlab-org/api/client-go v0.142.4 - go.yaml.in/yaml/v4 v4.0.0-rc.2 - golang.org/x/crypto v0.47.0 - golang.org/x/image v0.35.0 - golang.org/x/net v0.49.0 - golang.org/x/oauth2 v0.34.0 - golang.org/x/sync v0.19.0 - golang.org/x/sys v0.40.0 - golang.org/x/text v0.33.0 - google.golang.org/grpc v1.78.0 + gitlab.com/gitlab-org/api/client-go v1.46.0 + go.yaml.in/yaml/v4 v4.0.0-rc.3 + golang.org/x/crypto v0.49.0 + golang.org/x/image v0.38.0 + golang.org/x/net v0.52.0 + golang.org/x/oauth2 v0.36.0 + golang.org/x/sync v0.20.0 + golang.org/x/sys v0.43.0 + golang.org/x/text v0.35.0 + google.golang.org/grpc v1.80.0 google.golang.org/protobuf v1.36.11 gopkg.in/ini.v1 v1.67.1 + gopkg.in/yaml.v3 v3.0.1 mvdan.cc/xurls/v2 v2.6.0 - strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 + strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab xorm.io/builder v0.3.13 xorm.io/xorm v1.3.11 ) @@ -133,78 +132,78 @@ require ( cloud.google.com/go/compute/metadata v0.9.0 // indirect code.gitea.io/gitea-vet v0.2.3 // indirect dario.cat/mergo v1.0.2 // indirect - filippo.io/edwards25519 v1.1.0 // indirect - git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect + filippo.io/edwards25519 v1.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/RoaringBitmap/roaring/v2 v2.10.0 // indirect + github.com/RoaringBitmap/roaring/v2 v2.16.0 // indirect github.com/STARRY-S/zip v0.2.3 // indirect - github.com/andybalholm/brotli v1.2.0 // indirect + github.com/andybalholm/brotli v1.2.1 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect - github.com/aws/aws-sdk-go-v2 v1.41.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect - github.com/aws/smithy-go v1.24.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect + github.com/aws/smithy-go v1.24.2 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.24.0 // indirect - github.com/blevesearch/bleve_index_api v1.2.11 // indirect - github.com/blevesearch/geo v0.2.4 // indirect - github.com/blevesearch/go-faiss v1.0.26 // indirect + github.com/bits-and-blooms/bitset v1.24.4 // indirect + github.com/blevesearch/bleve_index_api v1.3.7 // indirect + github.com/blevesearch/geo v0.2.5 // indirect + github.com/blevesearch/go-faiss v1.0.30 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect github.com/blevesearch/gtreap v0.1.1 // indirect - github.com/blevesearch/mmap-go v1.0.4 // indirect - github.com/blevesearch/scorch_segment_api/v2 v2.3.13 // indirect + github.com/blevesearch/mmap-go v1.2.0 // indirect + github.com/blevesearch/scorch_segment_api/v2 v2.4.5 // indirect github.com/blevesearch/segment v0.9.1 // indirect github.com/blevesearch/snowballstem v0.9.0 // indirect github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect - github.com/blevesearch/vellum v1.1.0 // indirect - github.com/blevesearch/zapx/v11 v11.4.2 // indirect - github.com/blevesearch/zapx/v12 v12.4.2 // indirect - github.com/blevesearch/zapx/v13 v13.4.2 // indirect - github.com/blevesearch/zapx/v14 v14.4.2 // indirect - github.com/blevesearch/zapx/v15 v15.4.2 // indirect - github.com/blevesearch/zapx/v16 v16.2.8 // indirect - github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect + github.com/blevesearch/vellum v1.2.0 // indirect + github.com/blevesearch/zapx/v11 v11.4.3 // indirect + github.com/blevesearch/zapx/v12 v12.4.3 // indirect + github.com/blevesearch/zapx/v13 v13.4.3 // indirect + github.com/blevesearch/zapx/v14 v14.4.3 // indirect + github.com/blevesearch/zapx/v15 v15.4.3 // indirect + github.com/blevesearch/zapx/v16 v16.3.2 // indirect + github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/sevenzip v1.6.1 // indirect github.com/bodgit/windows v1.0.1 // indirect github.com/boombuler/barcode v1.1.0 // indirect github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect - github.com/caddyserver/zerossl v0.1.4 // indirect + github.com/caddyserver/zerossl v0.1.5 // indirect github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/couchbase/go-couchbase v0.1.1 // indirect - github.com/couchbase/gomemcached v0.3.3 // indirect - github.com/couchbase/goutils v0.1.2 // indirect + github.com/couchbase/gomemcached v0.3.4 // indirect + github.com/couchbase/goutils v0.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidmz/go-pageant v1.0.2 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/dlclark/regexp2 v1.11.5 // indirect github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect - github.com/fatih/color v1.18.0 // indirect - github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/fatih/color v1.19.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect - github.com/go-ap/errors v0.0.0-20250527110557-c8db454e53fd // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-enry/go-oniguruma v1.2.1 // indirect github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-ini/ini v1.67.0 // indirect - github.com/go-webauthn/x v0.1.24 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/go-webauthn/x v0.2.2 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/flatbuffers v25.2.10+incompatible // indirect - github.com/google/go-querystring v1.1.0 // indirect - github.com/google/go-tpm v0.9.5 // indirect + github.com/google/flatbuffers v25.12.19+incompatible // indirect + github.com/google/go-querystring v1.2.0 // indirect + github.com/google/go-tpm v0.9.8 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/securecookie v1.1.2 // indirect @@ -212,57 +211,56 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect + github.com/inbucket/html2text v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/kevinburke/ssh_config v1.6.0 // indirect github.com/klauspost/crc32 v1.3.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/libdns/libdns v1.1.1 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.2 // indirect github.com/markbates/going v1.0.3 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.21 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect - github.com/mholt/acmez/v3 v3.1.4 // indirect - github.com/miekg/dns v1.1.69 // indirect + github.com/mholt/acmez/v3 v3.1.6 // indirect + github.com/miekg/dns v1.1.72 // indirect github.com/mikelolasagasti/xz v1.0.1 // indirect github.com/minio/crc64nvme v1.1.1 // indirect github.com/minio/md5-simd v1.1.2 // indirect - github.com/minio/minlz v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/minio/minlz v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nwaples/rardecode/v2 v2.2.0 // indirect - github.com/olekukonko/cat v0.0.0-20250817074551-3280053e4e00 // indirect - github.com/olekukonko/errors v1.1.0 // indirect - github.com/olekukonko/ll v0.1.0 // indirect - github.com/olekukonko/tablewriter v1.0.9 // indirect + github.com/nwaples/rardecode/v2 v2.2.2 // indirect + github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect + github.com/olekukonko/errors v1.2.0 // indirect + github.com/olekukonko/ll v0.1.8 // indirect + github.com/olekukonko/tablewriter v1.1.4 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/philhofer/fwd v1.2.0 // indirect - github.com/pierrec/lz4/v4 v4.1.22 // indirect - github.com/pjbgf/sha1cd v0.4.0 // indirect + github.com/pierrec/lz4/v4 v4.1.26 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.17.0 // indirect - github.com/rhysd/actionlint v1.7.7 // indirect - github.com/rivo/uniseg v0.4.7 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.20.1 // indirect + github.com/rhysd/actionlint v1.7.12 // indirect github.com/rs/xid v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/skeema/knownhosts v1.3.1 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect github.com/sorairolake/lzip-go v0.3.8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect - github.com/tinylib/msgp v1.6.1 // indirect + github.com/tinylib/msgp v1.6.3 // indirect github.com/unknwon/com v1.0.1 // indirect - github.com/valyala/fastjson v1.6.4 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect @@ -275,17 +273,15 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.uber.org/zap/exp v0.3.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - go4.org v0.0.0-20230225012048-214862532bf5 // indirect + go4.org v0.0.0-20260112195520-a5071408f32f // indirect golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect - golang.org/x/mod v0.31.0 // indirect - golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.40.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect + golang.org/x/mod v0.34.0 // indirect + golang.org/x/time v0.15.0 // indirect + golang.org/x/tools v0.43.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260401020348-3a24fdc17823 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) ignore ( @@ -293,18 +289,22 @@ ignore ( ./node_modules ) -replace github.com/jaytaylor/html2text => github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 +// When doing "go get -u ./...", Golang will try to update all dependencies +// But not all latest versions of dependencies are compatible with other packages or our codebase, so we need to pin some dependencies to specific versions +// Need to regularly maintain this list to try to update them to latest versions, especially the TODO ones -replace github.com/nektos/act => gitea.com/gitea/act v0.261.8 +replace github.com/jaytaylor/html2text => github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 // jaytaylor/html2text is unmaintained -replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 +replace github.com/nektos/act => gitea.com/gitea/act v0.261.10 // gitea maintains its own package -exclude github.com/gofrs/uuid v3.2.0+incompatible +replace github.com/urfave/cli/v3 => github.com/urfave/cli/v3 v3.4.1 // v3.6.2 breaks -c flag parsing in help commands -exclude github.com/gofrs/uuid v4.0.0+incompatible +replace go.yaml.in/yaml/v4 => go.yaml.in/yaml/v4 v4.0.0-rc.3 // rc.4 changes block scalar serialization, wait for stable release -exclude github.com/goccy/go-json v0.4.11 +replace github.com/Azure/azure-sdk-for-go/sdk/azcore => github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 // v1.21.0+ uses API version unsupported by Azurite in CI -exclude github.com/satori/go.uuid v1.2.0 +replace github.com/Azure/azure-sdk-for-go/sdk/storage/azblob => github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 // v1.6.4+ uses API version unsupported by Azurite in CI + +replace github.com/microsoft/go-mssqldb => github.com/microsoft/go-mssqldb v1.9.7 // downgraded with Azure SDK tool code.gitea.io/gitea-vet diff --git a/go.sum b/go.sum index 883547adad..f8a3c5562c 100644 --- a/go.sum +++ b/go.sum @@ -1,40 +1,23 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= code.gitea.io/actions-proto-go v0.4.1 h1:l0EYhjsgpUe/1VABo2eK7zcoNX2W44WOnb0MSLrKfls= code.gitea.io/actions-proto-go v0.4.1/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas= 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.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg= -code.gitea.io/sdk/gitea v0.23.2/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM= +code.gitea.io/sdk/gitea v0.24.1 h1:hpaqcdGcBmfMpV7JSbBJVwE99qo+WqGreJYKrDKEyW8= +code.gitea.io/sdk/gitea v0.24.1/go.mod h1:5/77BL3sHneCMEiZaMT9lfTvnnibsYxyO48mceCF3qA= +code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE= +code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM= codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 h1:TXbikPqa7YRtfU9vS6QJBg77pUvbEb6StRdZO8t1bEY= codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570/go.mod h1:IIAjsijsd8q1isWX8MACefDEgTQslQ4stk2AeeTt3kM= connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14= connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -gitea.com/gitea/act v0.261.8 h1:rUWB5GOZOubfe2VteKb7XP3HRIbcW3UUmfh7bVAgQcA= -gitea.com/gitea/act v0.261.8/go.mod h1:lTp4136rwbZiZS3ZVQeHCvd4qRAZ7LYeiRBqOSdMY/4= -gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4= -gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +gitea.com/gitea/act v0.261.10 h1:ndwbtuMXXz1dpYF2iwY1/PkgKNETo4jmPXfinTZt8cs= +gitea.com/gitea/act v0.261.10/go.mod h1:oIkqQHvU0lfuIWwcpqa4FmU+t3prA89tgkuHUTsrI2c= gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso= gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw= gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g= @@ -49,10 +32,10 @@ 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= -github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs= -github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM= -github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920 h1:mWAVGlovzUfREJBhm0GwJnDNu21yRrL9QH9NIzAU3rg= -github.com/42wim/sshsig v0.0.0-20250502153856-5100632e8920/go.mod h1:zWxcT7BIWOe05xVJL0VMvO/PJ6RpoCux10heb77H6Q8= +github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU= +github.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps= +github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432 h1:3Fcz1QzlS7Jv4FT2KI3cHNSZL+KPN3dXxurn9f3YL/Y= +github.com/42wim/sshsig v0.0.0-20260317195500-b9f38cf0d432/go.mod h1:BLWe6Nol65Xxncvaw07yYMxiyk02We1lBrbRYsMYsjE= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 h1:ci6Yd6nysBRLEodoziB6ah1+YOzZbZk+NYneoA6q+6E= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= @@ -67,12 +50,11 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuo github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 h1:FwladfywkNirM+FZYLBR2kBz5C8Tg0fw5w5Y7meRXWI= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2/go.mod h1:vv5Ad0RrIoT1lJFdWBZwt4mB1+j+V8DUroixmKDTCdk= -github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= -github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A= +github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74= @@ -81,14 +63,14 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347 h1:3JhDl+JysaO8nhNU1XMaw35VSGjV4IEQAefaG4Lyok4= github.com/Necoro/html2text v0.0.0-20250804200300-7bf1ce1c7347/go.mod h1:2ErI0aycD43Ufr6CFK5lT/NrHGmoZuVbn1nlPThw69o= -github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= -github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= -github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw= -github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ= +github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= +github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= +github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo= +github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ= 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/v2 v2.10.0 h1:HbJ8Cs71lfCJyvmSptxeMX2PtvOC8yonlU0GQcy2Ak0= -github.com/RoaringBitmap/roaring/v2 v2.10.0/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0= +github.com/RoaringBitmap/roaring/v2 v2.16.0 h1:Kys1UNf49d5W8Tq3bpuAhIr/Z8/yPB+59CO8A6c/BbE= +github.com/RoaringBitmap/roaring/v2 v2.16.0/go.mod h1:eq4wdNXxtJIS/oikeCzdX1rBzek7ANzbth041hrU8Q4= github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.8.0 h1:tgjwQrDH5m6jIYB7kac5IQZmfUzQNseac/e3H4VoCNE= @@ -103,8 +85,8 @@ github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= -github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= -github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= +github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -112,49 +94,48 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= -github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= -github.com/aws/aws-sdk-go-v2/credentials v1.19.7 h1:tHK47VqqtJxOymRrNtUXN5SP/zUTvZKeLx4tH6PGQc8= -github.com/aws/aws-sdk-go-v2/credentials v1.19.7/go.mod h1:qOZk8sPDrxhf+4Wf4oT2urYJrYt3RejHSzgAquYeppw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= -github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.8 h1:KxKGfYvkVOe/U/Z4yAd0ZySRJHavuL31VOC+fn7WEAs= -github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.8/go.mod h1:cznnFD3BzYY+NB+4WoQ7SxdTACOsMqGCbQ5QaByPz4w= -github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= -github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= +github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/credentials v1.19.14 h1:n+UcGWAIZHkXzYt87uMFBv/l8THYELoX6gVcUvgl6fI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.14/go.mod h1:cJKuyWB59Mqi0jM3nFYQRmnHVQIcgoxjEMAbLkpr62w= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= +github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.12 h1:yv3mfWt/eiDTTry6fkN5hh8wHJfU5ygnw+DJp10C0/c= +github.com/aws/aws-sdk-go-v2/service/codecommit v1.33.12/go.mod h1:voO3LP/dZ4CTERiNWCz3DFLbK/8hbfeC1OJkLW+sang= +github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= +github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= 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/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.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJizujEZvmAM= -github.com/bits-and-blooms/bitset v1.24.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= +github.com/bits-and-blooms/bitset v1.24.4/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.5.7 h1:2d9YrL5zrX5EBBW++GOaEKjE+NPWeZGaX77IM26m1Z8= github.com/blevesearch/bleve/v2 v2.5.7/go.mod h1:yj0NlS7ocGC4VOSAedqDDMktdh2935v2CSWOCDMHdSA= github.com/blevesearch/bleve_index_api v1.0.0/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4= -github.com/blevesearch/bleve_index_api v1.2.11 h1:bXQ54kVuwP8hdrXUSOnvTQfgK0KI1+f9A0ITJT8tX1s= -github.com/blevesearch/bleve_index_api v1.2.11/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0= -github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk= -github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8= -github.com/blevesearch/go-faiss v1.0.26 h1:4dRLolFgjPyjkaXwff4NfbZFdE/dfywbzDqporeQvXI= -github.com/blevesearch/go-faiss v1.0.26/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= +github.com/blevesearch/bleve_index_api v1.3.7 h1:x3C4pl6o3MlSJ3rNKLQpZMRF0uR9+BDvmF3t8LYCOpk= +github.com/blevesearch/bleve_index_api v1.3.7/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko= +github.com/blevesearch/geo v0.2.5 h1:yJg9FX1oRwLnjXSXF+ECHfXFTF4diF02Ca/qUGVjJhE= +github.com/blevesearch/geo v0.2.5/go.mod h1:Jhq7WE2K6mJTx1xS44M2pUO6Io+wjCSHh1+co3YOgH4= +github.com/blevesearch/go-faiss v1.0.30 h1:pWX3/Si4Z7GlwsD2eRXoF3SfVaDkg8plBlPdUKuhGts= +github.com/blevesearch/go-faiss v1.0.30/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= 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= github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk= github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA= -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/mmap-go v1.2.0 h1:l33nNKPFcBjJUMwem6sAYJPUzhUCABoK9FxZDGiFNBI= +github.com/blevesearch/mmap-go v1.2.0/go.mod h1:Vd6+20GBhEdwJnU1Xohgt88XCD/CTWcqbCNxkZpyBo0= github.com/blevesearch/scorch_segment_api/v2 v2.0.1/go.mod h1:lq7yK2jQy1yQjtjTfU931aVqz7pYxEudHaDwOt1tXfU= -github.com/blevesearch/scorch_segment_api/v2 v2.3.13 h1:ZPjv/4VwWvHJZKeMSgScCapOy8+DdmsmRyLmSB88UoY= -github.com/blevesearch/scorch_segment_api/v2 v2.3.13/go.mod h1:ENk2LClTehOuMS8XzN3UxBEErYmtwkE7MAArFTXs9Vc= +github.com/blevesearch/scorch_segment_api/v2 v2.4.5 h1:Q7Bzpyk86xS22TgTd4VQfSvzZAybDEJ90hNOGqyNqlI= +github.com/blevesearch/scorch_segment_api/v2 v2.4.5/go.mod h1:xWYn3EwRM7zBFAPt/J136OugUNzftpYLvPBBx31IpCw= 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= @@ -165,27 +146,27 @@ github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMG github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ= github.com/blevesearch/vellum v1.0.3/go.mod h1:2u5ax02KeDuNWu4/C+hVQMD6uLN4txH1JbtpaDNLJRo= github.com/blevesearch/vellum v1.0.4/go.mod h1:cMhywHI0de50f7Nj42YgvyD6bFJ2WkNRvNBlNMrEVgY= -github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w= -github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y= +github.com/blevesearch/vellum v1.2.0 h1:xkDiOEsHc2t3Cp0NsNZZ36pvc130sCzcGKOPMzXe+e0= +github.com/blevesearch/vellum v1.2.0/go.mod h1:uEcfBJz7mAOf0Kvq6qoEKQQkLODBF46SINYNkZNae4k= github.com/blevesearch/zapx/v11 v11.2.0/go.mod h1:gN/a0alGw1FZt/YGTo1G6Z6XpDkeOfujX5exY9sCQQM= -github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs= -github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc= +github.com/blevesearch/zapx/v11 v11.4.3 h1:PTZOO5loKpHC/x/GzmPZNa9cw7GZIQxd5qRjwij9tHY= +github.com/blevesearch/zapx/v11 v11.4.3/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc= github.com/blevesearch/zapx/v12 v12.2.0/go.mod h1:fdjwvCwWWwJW/EYTYGtAp3gBA0geCYGLcVTtJEZnY6A= -github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE= -github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58= +github.com/blevesearch/zapx/v12 v12.4.3 h1:eElXvAaAX4m04t//CGBQAtHNPA+Q6A1hHZVrN3LSFYo= +github.com/blevesearch/zapx/v12 v12.4.3/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58= github.com/blevesearch/zapx/v13 v13.2.0/go.mod h1:o5rAy/lRS5JpAbITdrOHBS/TugWYbkcYZTz6VfEinAQ= -github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks= -github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk= +github.com/blevesearch/zapx/v13 v13.4.3 h1:qsdhRhaSpVnqDFlRiH9vG5+KJ+dE7KAW9WyZz/KXAiE= +github.com/blevesearch/zapx/v13 v13.4.3/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk= github.com/blevesearch/zapx/v14 v14.2.0/go.mod h1:GNgZusc1p4ot040cBQMRGEZobvwjCquiEKYh1xLFK9g= -github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0= -github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8= +github.com/blevesearch/zapx/v14 v14.4.3 h1:GY4Hecx0C6UTmiNC2pKdeA2rOKiLR5/rwpU9WR51dgM= +github.com/blevesearch/zapx/v14 v14.4.3/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8= github.com/blevesearch/zapx/v15 v15.2.0/go.mod h1:MmQceLpWfME4n1WrBFIwplhWmaQbQqLQARpaKUEOs/A= -github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k= -github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw= -github.com/blevesearch/zapx/v16 v16.2.8 h1:SlnzF0YGtSlrsOE3oE7EgEX6BIepGpeqxs1IjMbHLQI= -github.com/blevesearch/zapx/v16 v16.2.8/go.mod h1:murSoCJPCk25MqURrcJaBQ1RekuqSCSfMjXH4rHyA14= -github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE= -github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/blevesearch/zapx/v15 v15.4.3 h1:iJiMJOHrz216jyO6lS0m9RTCEkprUnzvqAI2lc/0/CU= +github.com/blevesearch/zapx/v15 v15.4.3/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw= +github.com/blevesearch/zapx/v16 v16.3.2 h1:R8alnAeYxzKO4JjQY4FBqJduJwcPE3YjcgevWgI+XT0= +github.com/blevesearch/zapx/v16 v16.3.2/go.mod h1:zCFjv7McXWm1C8rROL+3mUoD5WYe2RKsZP3ufqcYpLY= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q= github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= @@ -206,11 +187,10 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/buildkite/terminal-to-html/v3 v3.16.8 h1:QN/daUob6cmK8GcdKnwn9+YTlPr1vNj+oeAIiJK6fPc= github.com/buildkite/terminal-to-html/v3 v3.16.8/go.mod h1:+k1KVKROZocrTLsEQ9PEf9A+8+X8uaVV5iO1ZIOwKYM= -github.com/caddyserver/certmagic v0.25.1 h1:4sIKKbOt5pg6+sL7tEwymE1x2bj6CHr80da1CRRIPbY= -github.com/caddyserver/certmagic v0.25.1/go.mod h1:VhyvndxtVton/Fo/wKhRoC46Rbw1fmjvQ3GjHYSQTEY= -github.com/caddyserver/zerossl v0.1.4 h1:CVJOE3MZeFisCERZjkxIcsqIH4fnFdlYWnPYeFtBHRw= -github.com/caddyserver/zerossl v0.1.4/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc= +github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg= +github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE= +github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI= github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -222,32 +202,32 @@ github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdi github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k= github.com/couchbase/go-couchbase v0.1.1 h1:ClFXELcKj/ojyoTYbsY34QUrrYCBi/1G749sXSCkdhk= github.com/couchbase/go-couchbase v0.1.1/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A= -github.com/couchbase/gomemcached v0.3.3 h1:D7qqXLO8wNa4pn5oE65lT3pA3IeStn4joT7/JgGXzKc= -github.com/couchbase/gomemcached v0.3.3/go.mod h1:pISAjweI42vljCumsJIo7CVhqIMIIP9g3Wfhl1JJw68= -github.com/couchbase/goutils v0.1.2 h1:gWr8B6XNWPIhfalHNog3qQKfGiYyh4K4VhO3P2o9BCs= +github.com/couchbase/gomemcached v0.3.4 h1:VGdrZUJbt5lLyI/MXnyVCZKHKYXg/vaud08lJIAeZps= +github.com/couchbase/gomemcached v0.3.4/go.mod h1:pISAjweI42vljCumsJIo7CVhqIMIIP9g3Wfhl1JJw68= github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE= +github.com/couchbase/goutils v0.3.0 h1:rsv72B6BDjW9jmwlfiDUrdu3EpNvPuo5WLULHzQ0DLE= +github.com/couchbase/goutils v0.3.0/go.mod h1:7Gm+D3vXfV4HS+hQWvKfy6e6ILCptGXNqBKvQXhplhk= 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.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= -github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -283,12 +263,10 @@ github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTe github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethantkoenig/rupture v1.0.1 h1:6aAXghmvtnngMgQzy7SMGdicMvkV86V4n9fT0meE5E4= github.com/ethantkoenig/rupture v1.0.1/go.mod h1:Sjqo/nbffZp1pVVXNGhpugIjsWmuS9KiIB4GtpEBur4= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -297,20 +275,14 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= +github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0= github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-ap/activitypub v0.0.0-20250810115208-cb73b20a1742 h1:X+SsQlZSgJO0A4d1+nI7+g4axZ8u3iUKPirYb5nB5ic= -github.com/go-ap/activitypub v0.0.0-20250810115208-cb73b20a1742/go.mod h1:0rgUaERG5qjYenwz4oN5OnUjvkdRuHRjb+2c8FRjz+w= -github.com/go-ap/errors v0.0.0-20250527110557-c8db454e53fd h1:fM5mNIWTPoxoOYoTLd6ifkKXSlXa830l5MYXsrt1UmE= -github.com/go-ap/errors v0.0.0-20250527110557-c8db454e53fd/go.mod h1:Vkh+Z3f24K8nMsJKXo1FHn5ebPsXvB/WDH5JRtYqdNo= -github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 h1:GMKIYXyXPGIp+hYiWOhfqK4A023HdgisDT4YGgf99mw= -github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= @@ -318,50 +290,52 @@ github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE= github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= -github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= -github.com/go-enry/go-enry/v2 v2.9.4 h1:DS4l06/NgMzYjsJ2J52wORo6UsfFDjDCwfAn7w3gG44= -github.com/go-enry/go-enry/v2 v2.9.4/go.mod h1:9yrj4ES1YrbNb1Wb7/PWYr2bpaCXUGRt0uafN0ISyG8= +github.com/go-co-op/gocron/v2 v2.20.0 h1:9IMrnnVSWjfSh3E54gWmWCHbloQJLh6f9+nwyKfLNpc= +github.com/go-co-op/gocron/v2 v2.20.0/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U= +github.com/go-enry/go-enry/v2 v2.9.6 h1:np63eOtMV56zfYDHnFVgpEVOk8fr2kmylcMnAZUDbSs= +github.com/go-enry/go-enry/v2 v2.9.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-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.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= -github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= +github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= +github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s= -github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M= -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-git/go-git/v5 v5.17.2 h1:B+nkdlxdYrvyFK4GPXVU8w1U+YkbsgciIR7f2sZJ104= +github.com/go-git/go-git/v5 v5.17.2/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= -github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ= +github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI= github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-redsync/redsync/v4 v4.15.0 h1:KH/XymuxSV7vyKs6z1Cxxj+N+N18JlPxgXeP6x4JY54= -github.com/go-redsync/redsync/v4 v4.15.0/go.mod h1:qNp+lLs3vkfZbtA/aM/OjlZHfEr5YTAYhRktFPKHC7s= +github.com/go-redsync/redsync/v4 v4.16.0 h1:bNcOzeHH9d3s6pghU9NJFMPrQa41f5Nx3L4YKr3BdEU= +github.com/go-redsync/redsync/v4 v4.16.0/go.mod h1:V4gagqgyASWBZuwx4xGzu72aZNb/6Mo05byUa3mVmKQ= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= -github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-webauthn/webauthn v0.13.4 h1:q68qusWPcqHbg9STSxBLBHnsKaLxNO0RnVKaAqMuAuQ= -github.com/go-webauthn/webauthn v0.13.4/go.mod h1:MglN6OH9ECxvhDqoq1wMoF6P6JRYDiQpC9nc5OomQmI= -github.com/go-webauthn/x v0.1.24 h1:6LaWf2zzWqbyKT8IyQkhje1/1KCGhlEkMz4V1tDnt/A= -github.com/go-webauthn/x v0.1.24/go.mod h1:2o5XKJ+X1AKqYKGgHdKflGnoQFQZ6flJ2IFCBKSbSOw= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-webauthn/webauthn v0.16.3 h1:RorP0c6VbaKP0i0Jxf/vAf7EFb2lmdLW8GLKITeaN5A= +github.com/go-webauthn/webauthn v0.16.3/go.mod h1:R2xjJxSPat5PYKg5r6cUmqXgbHtbv4GmF6uGkqFMLNI= +github.com/go-webauthn/x v0.2.2 h1:zIiipvMbr48CXi5RG0XdBJR94kd8I5LfzHPb/q+YYmk= +github.com/go-webauthn/x v0.2.2/go.mod h1:IpJ5qyWB9NRhLX3C7gIfjTU7RZLXEP6kzFkoVSE7Fz4= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs= github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14= github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0= @@ -372,20 +346,10 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -400,14 +364,11 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8= github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= -github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs= +github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -416,30 +377,24 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-github/v74 v74.0.0 h1:yZcddTUn8DPbj11GxnMrNiAnXH14gNs559AsUpNpPgM= -github.com/google/go-github/v74 v74.0.0/go.mod h1:ubn/YdyftV80VPSI26nSJvaEsTOnsjrxG3o9kJhcyak= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU= -github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/go-github/v84 v84.0.0 h1:I/0Xn5IuChMe8TdmI2bbim5nyhaRFJ7DEdzmD2w+yVA= +github.com/google/go-github/v84 v84.0.0/go.mod h1:WwYL1z1ajRdlaPszjVu/47x1L0PXukJBn73xsiYrRRQ= +github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= +github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= +github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= +github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba h1:qJEJcuLzH5KDR0gKc0zcktin6KSAwL7+jWKBYceddTc= +github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba/go.mod h1:EFYHy8/1y2KfgTAsx7Luu7NGhoxtuVHnNo8jE7FikKc= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/licenseclassifier/v2 v2.0.0 h1:1Y57HHILNf4m0ABuMVb6xk4vAJYEUO0gDxNpog0pyeA= github.com/google/licenseclassifier/v2 v2.0.0/go.mod h1:cOjbdH0kyC9R22sdQbYsFkto4NGCAc+ZSwbeThazEtM= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef h1:xpF9fUHpoIrrjX24DURVKiwHcFpw19ndIs+FwTSMbno= -github.com/google/pprof v0.0.0-20260202012954-cb029daf43ef/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -459,6 +414,8 @@ github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pw github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= +github.com/graph-gophers/graphql-go v1.9.0 h1:yu0ucKHLc5qGpRwLYKIWtr9bOoxovkWasuBrPQwlHls= +github.com/graph-gophers/graphql-go v1.9.0/go.mod h1:23olKZ7duEvHlF/2ELEoSZaY1aNPfShjP782SOoNTyM= 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= @@ -472,10 +429,8 @@ github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVU github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= -github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -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/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 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/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -484,8 +439,9 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= +github.com/inbucket/html2text v1.0.0 h1:N5kza++4uBBDJ2Z3KUnTRyPNoBcW+YfOgNiNmNB+sgs= +github.com/inbucket/html2text v1.0.0/go.mod h1:5TrhXQKGU+LXurODaSm55Y9eXoPBRnYiOz4x2XfUoJU= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= @@ -501,25 +457,24 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw= -github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs= +github.com/jhillyerd/enmime/v2 v2.3.0 h1:Y/pzQanyU8nkSgB2npXX8Dha5OItJE/QwbDJM4sf/kU= +github.com/jhillyerd/enmime/v2 v2.3.0/go.mod h1:mGKXAP45l6pF6HZiaLhgSYsgteJskaSIYmEZXpw6ZpI= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= -github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= +github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= -github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= 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.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= @@ -530,8 +485,6 @@ github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 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= @@ -541,54 +494,56 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= -github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI= -github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU= +github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk= +github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U= +github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M= +github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 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.82.0 h1:8j/c34AjBSTNzO7zTsOyP5IYCQCMBTRBHAbBt/PI0bQ= github.com/markbates/goth v1.82.0/go.mod h1:/DRlcq0pyqkKToyZjsL2KgiA1zbF1HIjE7u2uC79rUk= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs= +github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= +github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= -github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/meilisearch/meilisearch-go v0.36.0 h1:N1etykTektXt5KPcSbhBO0d5Xx5NaKj4pJWEM7WA5dI= -github.com/meilisearch/meilisearch-go v0.36.0/go.mod h1:HBfHzKMxcSbTOvqdfuRA/yf6Vk9IivcwKocWRuW7W78= -github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ= -github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= +github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo= +github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/meilisearch/meilisearch-go v0.36.1 h1:mJTCJE5g7tRvaqKco6DfqOuJEjX+rRltDEnkEC02Y0M= +github.com/meilisearch/meilisearch-go v0.36.1/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM= +github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk= +github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY= github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ= github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= -github.com/microsoft/go-mssqldb v1.9.6 h1:1MNQg5UiSsokiPz3++K2KPx4moKrwIqly1wv+RyCKTw= -github.com/microsoft/go-mssqldb v1.9.6/go.mod h1:yYMPDufyoF2vVuVCUGtZARr06DKFIhMrluTcgWlXpr4= -github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc= -github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g= +github.com/microsoft/go-mssqldb v1.9.7 h1:I+JEk79gYsc6bdVzDHFSSYE9dtNa7dxRwJ0WQbt6i8w= +github.com/microsoft/go-mssqldb v1.9.7/go.mod h1:yYMPDufyoF2vVuVCUGtZARr06DKFIhMrluTcgWlXpr4= +github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= +github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0= github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI= github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.98 h1:MeAVKjLVz+XJ28zFcuYyImNSAh8Mq725uNW4beRisi0= -github.com/minio/minio-go/v7 v7.0.98/go.mod h1:cY0Y+W7yozf0mdIclrttzo1Iiu7mEf9y7nk2uXqMOvM= -github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A= -github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= +github.com/minio/minio-go/v7 v7.0.100 h1:ShkWi8Tyj9RtU57OQB2HIXKz4bFgtVib0bbT1sbtLI8= +github.com/minio/minio-go/v7 v7.0.100/go.mod h1:EtGNKtlX20iL2yaYnxEigaIvj0G0GwSDnifnG8ClIdw= +github.com/minio/minlz v1.1.0 h1:rUOGu3EP4EqJC5k3qCsIwEnZiJULKqtRyDdqbhlvMmQ= +github.com/minio/minlz v1.1.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -599,27 +554,27 @@ 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.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE= -github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0= +github.com/msteinert/pam/v2 v2.1.0 h1:er5F9TKV5nGFuTt12ubtqPHEUdeBwReP7vd3wovidGY= +github.com/msteinert/pam/v2 v2.1.0/go.mod h1:KT28NNIcDFf3PcBmNI2mIGO4zZJ+9RSs/At2PB3IDVc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0= github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48= -github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A= -github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= +github.com/nwaples/rardecode/v2 v2.2.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3b3KcU= +github.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/olekukonko/cat v0.0.0-20250817074551-3280053e4e00 h1:ZCnkxe9GgWqqBxAk3cIKlQJuaqgOUF/nUtQs8flVTHM= -github.com/olekukonko/cat v0.0.0-20250817074551-3280053e4e00/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= -github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= -github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= -github.com/olekukonko/ll v0.1.0 h1:7nX5bgpvfyxsvI90IJpOIU5zd4MBV6nRkD49e/dEx98= -github.com/olekukonko/ll v0.1.0/go.mod h1:2dJo+hYZcJMLMbKwHEWvxCUbAOLc/CXWS9noET22Mdo= -github.com/olekukonko/tablewriter v1.0.9 h1:XGwRsYLC2bY7bNd93Dk51bcPZksWZmLYuaTHR0FqfL8= -github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= +github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= +github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8= +github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw= +github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= +github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E= github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -641,13 +596,12 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= -github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= -github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY= -github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= +github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -657,35 +611,28 @@ github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= -github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= 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.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4= -github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= -github.com/redis/rueidis v1.0.69 h1:WlUefRhuDekji5LsD387ys3UCJtSFeBVf0e5yI0B8b4= -github.com/redis/rueidis v1.0.69/go.mod h1:Lkhr2QTgcoYBhxARU7kJRO8SyVlgUuEkcJO1Y8MCluA= -github.com/redis/rueidis/rueidiscompat v1.0.69 h1:IWVYY9lXdjNO3do2VpJT7aDFi8zbCUuQxZB6E2Grahs= -github.com/redis/rueidis/rueidiscompat v1.0.69/go.mod h1:iC4Y8DoN0Uth0Uezg9e2trvNRC7QAgGeuP2OPLb5ccI= +github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= +github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= +github.com/redis/rueidis v1.0.71 h1:pODtnAR5GAB7j4ekhldZ29HKOxe4Hph0GTDGk1ayEQY= +github.com/redis/rueidis v1.0.71/go.mod h1:lfdcZzJ1oKGKL37vh9fO3ymwt+0TdjkkUCJxbgpmcgQ= +github.com/redis/rueidis/rueidiscompat v1.0.71 h1:wNZ//kEjMZgBM0KCk7ncOX8KmAgROU2kDdDNpwheG4w= +github.com/redis/rueidis/rueidiscompat v1.0.71/go.mod h1:esmCLJvaRzZoKlgB82G1bY7Iky5TnO9Rz+NlhbEccFI= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rhysd/actionlint v1.7.7 h1:0KgkoNTrYY7vmOCs9BW2AHxLvvpoY9nEUzgBHiPUr0k= -github.com/rhysd/actionlint v1.7.7/go.mod h1:AE6I6vJEkNaIfWqC2GNE5spIJNhxf8NCtLEKU4NnUXg= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rhysd/actionlint v1.7.12 h1:vQ4GeJN86C0QH+gTUQcs8McmK62OLT3kmakPMtEWYnY= +github.com/rhysd/actionlint v1.7.12/go.mod h1:krOUhujIsJusovkaYzQ/VNH8PFexjNKqU0q5XI/4w+g= github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -693,9 +640,8 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= 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/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -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/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= @@ -705,10 +651,10 @@ github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepq github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= -github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= 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= github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= @@ -742,7 +688,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -752,8 +697,8 @@ github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKN github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY= -github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= +github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s= +github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ= github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -762,12 +707,10 @@ github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= -github.com/urfave/cli-docs/v3 v3.0.0-alpha6 h1:w/l/N0xw1rO/aHRIGXJ0lDwwYFOzilup1qGvIytP3BI= -github.com/urfave/cli-docs/v3 v3.0.0-alpha6/go.mod h1:p7Z4lg8FSTrPB9GTaNyTrK3ygffHZcK3w0cU2VE+mzU= +github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw= +github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to= github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM= github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= -github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= -github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8= github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k= @@ -793,50 +736,45 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= -github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= +github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= -github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= -github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -gitlab.com/gitlab-org/api/client-go v0.142.4 h1:tTm+hUPrOcTavmKpM9YIP503IE0EdAkg4TG3t6QGbiw= -gitlab.com/gitlab-org/api/client-go v0.142.4/go.mod h1:Ru5IRauphXt9qwmTzJD7ou1dH7Gc6pnsdFWEiMMpmB0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +gitlab.com/gitlab-org/api/client-go v1.46.0 h1:YxBWFZIFYKcGESCb9fpkwzouo+apyB9pr/XTWzNoL24= +gitlab.com/gitlab-org/api/client-go v1.46.0/go.mod h1:FtgyU6g2HS5+fMhw6nLK96GBEEBx5MzntOiJWfIaiN8= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s= -go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= -go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= -go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= +go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go= +go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= +go4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH9QQjfb0Sw= +go4.org v0.0.0-20260112195520-a5071408f32f/go.mod h1:ZRJnO5ZI4zAwMFp+dS1+V6J6MSyAowhRqAE+DPa1Xp0= 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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= @@ -847,36 +785,13 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= -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.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I= -golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE= +golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -885,22 +800,11 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= -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/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -915,19 +819,11 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -939,30 +835,20 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= 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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -982,8 +868,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -994,12 +880,9 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -1009,36 +892,13 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -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.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -1046,49 +906,16 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401020348-3a24fdc17823 h1:YedBIttDguBl/zy2wJauEUm+DZZg4UXseWj0g/3N+yo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401020348-3a24fdc17823/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= 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= @@ -1098,11 +925,9 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -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/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k= gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= @@ -1115,17 +940,10 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= @@ -1150,11 +968,8 @@ mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI= mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk= pgregory.net/rapid v0.4.2 h1:lsi9jhvZTYvzVpeG93WWgimPRmiJQfGFRNTEZh1dtY0= pgregory.net/rapid v0.4.2/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -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= +strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab h1:3IZDVyI8uBmZko6pwm39f7mE0aTY7LViNdQHMeH7U60= +strk.kbt.io/projects/go/libravatar v0.0.0-20260301104140-add494e31dab/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY= xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo= xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/xorm v1.3.11 h1:i4tlVUASogb0ZZFJHA7dZqoRU2pUpUsutnNdaOlFyMI= diff --git a/models/actions/artifact.go b/models/actions/artifact.go index 757bd13acd..d61afb2aed 100644 --- a/models/actions/artifact.go +++ b/models/actions/artifact.go @@ -53,6 +53,11 @@ func init() { db.RegisterModel(new(ActionArtifact)) } +const ( + ContentEncodingV3Gzip = "gzip" + ContentTypeZip = "application/zip" +) + // ActionArtifact is a file that is stored in the artifact storage. type ActionArtifact struct { ID int64 `xorm:"pk autoincr"` @@ -61,16 +66,26 @@ type ActionArtifact struct { RepoID int64 `xorm:"index"` OwnerID int64 CommitSHA string - StoragePath string // The path to the artifact in the storage - FileSize int64 // The size of the artifact in bytes - FileCompressedSize int64 // The size of the artifact in bytes after gzip compression - ContentEncoding string // The content encoding of the artifact - ArtifactPath string `xorm:"index unique(runid_name_path)"` // The path to the artifact when runner uploads it - ArtifactName string `xorm:"index unique(runid_name_path)"` // The name of the artifact when runner uploads it - Status ArtifactStatus `xorm:"index"` // The status of the artifact, uploading, expired or need-delete - CreatedUnix timeutil.TimeStamp `xorm:"created"` - UpdatedUnix timeutil.TimeStamp `xorm:"updated index"` - ExpiredUnix timeutil.TimeStamp `xorm:"index"` // The time when the artifact will be expired + StoragePath string // The path to the artifact in the storage + FileSize int64 // The size of the artifact in bytes + FileCompressedSize int64 // The size of the artifact in bytes after gzip compression + + // The content encoding or content type of the artifact + // * empty or null: legacy (v3) uncompressed content + // * magic string "gzip" (ContentEncodingV3Gzip): v3 gzip compressed content + // * requires gzip decoding before storing in a zip for download + // * requires gzip content-encoding header when downloaded single files within a workflow + // * mime type for "Content-Type": + // * "application/zip" (ContentTypeZip), seems to be an abuse, fortunately there is no conflict, and it won't cause problems? + // * "application/pdf", "text/html", etc.: real content type of the artifact + ContentEncodingOrType string `xorm:"content_encoding"` + + ArtifactPath string `xorm:"index unique(runid_name_path)"` // The path to the artifact when runner uploads it + ArtifactName string `xorm:"index unique(runid_name_path)"` // The name of the artifact when runner uploads it + Status ArtifactStatus `xorm:"index"` // The status of the artifact, uploading, expired or need-delete + CreatedUnix timeutil.TimeStamp `xorm:"created"` + UpdatedUnix timeutil.TimeStamp `xorm:"updated index"` + ExpiredUnix timeutil.TimeStamp `xorm:"index"` // The time when the artifact will be expired } func CreateArtifact(ctx context.Context, t *ActionTask, artifactName, artifactPath string, expiredDays int64) (*ActionArtifact, error) { @@ -156,7 +171,8 @@ func (opts FindArtifactsOptions) ToConds() builder.Cond { } if opts.FinalizedArtifactsV4 { cond = cond.And(builder.Eq{"status": ArtifactStatusUploadConfirmed}.Or(builder.Eq{"status": ArtifactStatusExpired})) - cond = cond.And(builder.Eq{"content_encoding": "application/zip"}) + // see the comment of ActionArtifact.ContentEncodingOrType: "*/*" means the field is a content type + cond = cond.And(builder.Like{"content_encoding", "%/%"}) } return cond @@ -170,10 +186,10 @@ type ActionArtifactMeta struct { } // ListUploadedArtifactsMeta returns all uploaded artifacts meta of a run -func ListUploadedArtifactsMeta(ctx context.Context, runID int64) ([]*ActionArtifactMeta, error) { +func ListUploadedArtifactsMeta(ctx context.Context, repoID, runID int64) ([]*ActionArtifactMeta, error) { arts := make([]*ActionArtifactMeta, 0, 10) return arts, db.GetEngine(ctx).Table("action_artifact"). - Where("run_id=? AND (status=? OR status=?)", runID, ArtifactStatusUploadConfirmed, ArtifactStatusExpired). + Where("repo_id=? AND run_id=? AND (status=? OR status=?)", repoID, runID, ArtifactStatusUploadConfirmed, ArtifactStatusExpired). GroupBy("artifact_name"). Select("artifact_name, sum(file_size) as file_size, max(status) as status"). Find(&arts) diff --git a/models/actions/config.go b/models/actions/config.go new file mode 100644 index 0000000000..4f5357c560 --- /dev/null +++ b/models/actions/config.go @@ -0,0 +1,74 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "context" + + "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/util" + + "xorm.io/xorm/convert" +) + +// OwnerActionsConfig defines the Actions configuration for a user or organization +type OwnerActionsConfig struct { + // TokenPermissionMode defines the default permission mode (permissive, restricted) + TokenPermissionMode repo_model.ActionsTokenPermissionMode `json:"token_permission_mode,omitempty"` + + // MaxTokenPermissions defines the absolute maximum permissions any token can have in this context. + MaxTokenPermissions *repo_model.ActionsTokenPermissions `json:"max_token_permissions,omitempty"` + + // AllowedCrossRepoIDs is a list of specific repo IDs that can be accessed cross-repo + AllowedCrossRepoIDs []int64 `json:"allowed_cross_repo_ids,omitempty"` +} + +var _ convert.ConversionFrom = (*OwnerActionsConfig)(nil) + +func (cfg *OwnerActionsConfig) FromDB(bytes []byte) error { + _ = json.Unmarshal(bytes, cfg) + cfg.TokenPermissionMode, _ = util.EnumValue(cfg.TokenPermissionMode) + return nil +} + +// GetOwnerActionsConfig loads the OwnerActionsConfig for a user or organization from user settings +// It returns a default config if no setting is found +func GetOwnerActionsConfig(ctx context.Context, userID int64) (ret OwnerActionsConfig, err error) { + return user_model.GetUserSettingJSON(ctx, userID, user_model.SettingsKeyActionsConfig, ret) +} + +// SetOwnerActionsConfig saves the OwnerActionsConfig for a user or organization to user settings +func SetOwnerActionsConfig(ctx context.Context, userID int64, cfg OwnerActionsConfig) error { + return user_model.SetUserSettingJSON(ctx, userID, user_model.SettingsKeyActionsConfig, cfg) +} + +// GetDefaultTokenPermissions returns the default token permissions by its TokenPermissionMode. +func (cfg *OwnerActionsConfig) GetDefaultTokenPermissions() repo_model.ActionsTokenPermissions { + switch cfg.TokenPermissionMode { + case repo_model.ActionsTokenPermissionModeRestricted: + return repo_model.MakeRestrictedPermissions() + case repo_model.ActionsTokenPermissionModePermissive: + return repo_model.MakeActionsTokenPermissions(perm.AccessModeWrite) + default: + return repo_model.MakeActionsTokenPermissions(perm.AccessModeNone) + } +} + +// GetMaxTokenPermissions returns the maximum allowed permissions +func (cfg *OwnerActionsConfig) GetMaxTokenPermissions() repo_model.ActionsTokenPermissions { + if cfg.MaxTokenPermissions != nil { + return *cfg.MaxTokenPermissions + } + // Default max is write for everything + return repo_model.MakeActionsTokenPermissions(perm.AccessModeWrite) +} + +// ClampPermissions ensures that the given permissions don't exceed the maximum +func (cfg *OwnerActionsConfig) ClampPermissions(perms repo_model.ActionsTokenPermissions) repo_model.ActionsTokenPermissions { + maxPerms := cfg.GetMaxTokenPermissions() + return repo_model.ClampActionsTokenPermissions(perms, maxPerms) +} diff --git a/models/actions/run.go b/models/actions/run.go index 99e6267071..bce356c0e2 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -70,14 +70,14 @@ func (run *ActionRun) HTMLURL() string { if run.Repo == nil { return "" } - return fmt.Sprintf("%s/actions/runs/%d", run.Repo.HTMLURL(), run.Index) + return fmt.Sprintf("%s/actions/runs/%d", run.Repo.HTMLURL(), run.ID) } func (run *ActionRun) Link() string { if run.Repo == nil { return "" } - return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index) + return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.ID) } func (run *ActionRun) WorkflowLink() string { @@ -153,7 +153,11 @@ func (run *ActionRun) LoadRepo(ctx context.Context) error { } func (run *ActionRun) Duration() time.Duration { - return calculateDuration(run.Started, run.Stopped, run.Status) + run.PreviousDuration + d := calculateDuration(run.Started, run.Stopped, run.Status, run.Updated) + run.PreviousDuration + if d < 0 { + return 0 + } + return d } func (run *ActionRun) GetPushEventPayload() (*api.PushPayload, error) { @@ -299,7 +303,7 @@ func CancelJobs(ctx context.Context, jobs []*ActionRunJob) ([]*ActionRunJob, err if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil { return cancelledJobs, err } - updatedJob, err := GetRunJobByID(ctx, job.ID) + updatedJob, err := GetRunJobByRunAndID(ctx, job.RunID, job.ID) if err != nil { return cancelledJobs, fmt.Errorf("get job: %w", err) } @@ -322,16 +326,16 @@ func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, er return &run, nil } -func GetRunByIndex(ctx context.Context, repoID, index int64) (*ActionRun, error) { +func GetRunByRepoAndIndex(ctx context.Context, repoID, runIndex int64) (*ActionRun, error) { run := &ActionRun{ RepoID: repoID, - Index: index, + Index: runIndex, } has, err := db.GetEngine(ctx).Get(run) if err != nil { return nil, err } else if !has { - return nil, fmt.Errorf("run with index %d %d: %w", repoID, index, util.ErrNotExist) + return nil, fmt.Errorf("run with repo_id %d and index %d: %w", repoID, runIndex, util.ErrNotExist) } return run, nil diff --git a/models/actions/run_job.go b/models/actions/run_job.go index 22a696ea72..e70dbc3b44 100644 --- a/models/actions/run_job.go +++ b/models/actions/run_job.go @@ -18,6 +18,11 @@ import ( "xorm.io/builder" ) +// MaxJobNumPerRun is the maximum number of jobs in a single run. +// https://docs.github.com/en/actions/reference/limits#existing-system-limits +// TODO: check this limit when creating jobs +const MaxJobNumPerRun = 256 + // ActionRunJob represents a job of a run type ActionRunJob struct { ID int64 @@ -51,6 +56,11 @@ type ActionRunJob struct { ConcurrencyGroup string `xorm:"index(repo_concurrency) NOT NULL DEFAULT ''"` // evaluated concurrency.group ConcurrencyCancel bool `xorm:"NOT NULL DEFAULT FALSE"` // evaluated concurrency.cancel-in-progress + // TokenPermissions stores the explicit permissions from workflow/job YAML (no org/repo clamps applied). + // Org/repo clamps are enforced when the token is used at runtime. + // It is JSON-encoded repo_model.ActionsTokenPermissions and may be empty if not specified. + TokenPermissions *repo_model.ActionsTokenPermissions `xorm:"JSON TEXT"` + RawStrategy string `xorm:"TEXT"` // raw strategy from job YAML's "strategy" section (stored before matrix expansion for deferred evaluation) // IsMatrixEvaluated is only valid/needed when this job's RawStrategy is not empty and contains a matrix that depends on job outputs. @@ -69,7 +79,7 @@ func init() { } func (job *ActionRunJob) Duration() time.Duration { - return calculateDuration(job.Started, job.Stopped, job.Status) + return calculateDuration(job.Started, job.Stopped, job.Status, job.Updated) } func (job *ActionRunJob) LoadRun(ctx context.Context) error { @@ -125,13 +135,25 @@ func (job *ActionRunJob) ParseJob() (*jobparser.Job, error) { return workflowJob, nil } -func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) { +func GetRunJobByRepoAndID(ctx context.Context, repoID, jobID int64) (*ActionRunJob, error) { var job ActionRunJob - has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job) + has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", jobID, repoID).Get(&job) if err != nil { return nil, err } else if !has { - return nil, fmt.Errorf("run job with id %d: %w", id, util.ErrNotExist) + return nil, fmt.Errorf("run job with id %d: %w", jobID, util.ErrNotExist) + } + + return &job, nil +} + +func GetRunJobByRunAndID(ctx context.Context, runID, jobID int64) (*ActionRunJob, error) { + var job ActionRunJob + has, err := db.GetEngine(ctx).Where("id=? AND run_id=?", jobID, runID).Get(&job) + if err != nil { + return nil, err + } else if !has { + return nil, fmt.Errorf("run job with id %d: %w", jobID, util.ErrNotExist) } return &job, nil @@ -175,7 +197,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col if job.RunID == 0 { var err error - if job, err = GetRunJobByID(ctx, job.ID); err != nil { + if job, err = GetRunJobByRepoAndID(ctx, job.RepoID, job.ID); err != nil { return 0, err } } diff --git a/models/actions/run_test.go b/models/actions/run_test.go index bd2b92f4f6..e82cbe84b5 100644 --- a/models/actions/run_test.go +++ b/models/actions/run_test.go @@ -5,10 +5,12 @@ package actions import ( "testing" + "time" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" ) @@ -30,6 +32,16 @@ func TestUpdateRepoRunsNumbers(t *testing.T) { err = UpdateRepoRunsNumbers(t.Context(), repo) assert.NoError(t, err) repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - assert.Equal(t, 5, repo.NumActionRuns) + assert.Equal(t, 4, repo.NumActionRuns) assert.Equal(t, 3, repo.NumClosedActionRuns) } + +func TestActionRun_Duration_NonNegative(t *testing.T) { + run := &ActionRun{ + Started: timeutil.TimeStamp(100), + Stopped: timeutil.TimeStamp(200), + Status: StatusSuccess, + PreviousDuration: -time.Hour, + } + assert.Equal(t, time.Duration(0), run.Duration()) +} diff --git a/models/actions/runner.go b/models/actions/runner.go index 84398b143b..f5d40ca7d6 100644 --- a/models/actions/runner.go +++ b/models/actions/runner.go @@ -62,6 +62,8 @@ type ActionRunner struct { AgentLabels []string `xorm:"TEXT"` // Store if this is a runner that only ever get one single job assigned Ephemeral bool `xorm:"ephemeral NOT NULL DEFAULT false"` + // Store if this runner is disabled and should not pick up new jobs + IsDisabled bool `xorm:"is_disabled NOT NULL DEFAULT false"` Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` @@ -199,6 +201,7 @@ type FindRunnerOptions struct { Sort string Filter string IsOnline optional.Option[bool] + IsDisabled optional.Option[bool] WithAvailable bool // not only runners belong to, but also runners can be used } @@ -239,6 +242,10 @@ func (opts FindRunnerOptions) ToConds() builder.Cond { cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()}) } } + + if opts.IsDisabled.Has() { + cond = cond.And(builder.Eq{"is_disabled": opts.IsDisabled.Value()}) + } return cond } @@ -297,6 +304,20 @@ func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error { return err } +func SetRunnerDisabled(ctx context.Context, runner *ActionRunner, isDisabled bool) error { + if runner.IsDisabled == isDisabled { + return nil + } + + return db.WithTx(ctx, func(ctx context.Context) error { + runner.IsDisabled = isDisabled + if err := UpdateRunner(ctx, runner, "is_disabled"); err != nil { + return err + } + return IncreaseTaskVersion(ctx, runner.OwnerID, runner.RepoID) + }) +} + // DeleteRunner deletes a runner by given ID. func DeleteRunner(ctx context.Context, id int64) error { if _, err := GetRunnerByID(ctx, id); err != nil { diff --git a/models/actions/task.go b/models/actions/task.go index c35395ceed..77139ddcea 100644 --- a/models/actions/task.go +++ b/models/actions/task.go @@ -77,7 +77,7 @@ func init() { } func (task *ActionTask) Duration() time.Duration { - return calculateDuration(task.Started, task.Stopped, task.Status) + return calculateDuration(task.Started, task.Stopped, task.Status, task.Updated) } func (task *ActionTask) IsStopped() bool { @@ -114,7 +114,7 @@ func (task *ActionTask) GetRepoLink() string { func (task *ActionTask) LoadJob(ctx context.Context) error { if task.Job == nil { - job, err := GetRunJobByID(ctx, task.JobID) + job, err := GetRunJobByRepoAndID(ctx, task.RepoID, task.JobID) if err != nil { return err } @@ -388,6 +388,7 @@ func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.Task } if _, err := UpdateRunJob(ctx, &ActionRunJob{ ID: task.JobID, + RepoID: task.RepoID, Status: task.Status, Stopped: task.Stopped, }, nil); err != nil { @@ -449,6 +450,7 @@ func StopTask(ctx context.Context, taskID int64, status Status) error { task.Stopped = now if _, err := UpdateRunJob(ctx, &ActionRunJob{ ID: task.JobID, + RepoID: task.RepoID, Status: task.Status, Stopped: task.Stopped, }, nil); err != nil { diff --git a/models/actions/task_step.go b/models/actions/task_step.go index 03ffbf1931..3b477d8483 100644 --- a/models/actions/task_step.go +++ b/models/actions/task_step.go @@ -28,7 +28,7 @@ type ActionTaskStep struct { } func (step *ActionTaskStep) Duration() time.Duration { - return calculateDuration(step.Started, step.Stopped, step.Status) + return calculateDuration(step.Started, step.Stopped, step.Status, step.Updated) } func init() { diff --git a/models/actions/token_permissions.go b/models/actions/token_permissions.go new file mode 100644 index 0000000000..985f6cc97b --- /dev/null +++ b/models/actions/token_permissions.go @@ -0,0 +1,60 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "context" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" +) + +// ComputeTaskTokenPermissions computes the effective permissions for a job token against the target repository. +// It uses the job's stored permissions (if any), then applies org/repo clamps and fork/cross-repo restrictions. +// Note: target repository access policy checks are enforced in GetActionsUserRepoPermission; this function only computes the job token's effective permission ceiling. +func ComputeTaskTokenPermissions(ctx context.Context, task *ActionTask, targetRepo *repo_model.Repository) (ret repo_model.ActionsTokenPermissions, err error) { + if err := task.LoadJob(ctx); err != nil { + return ret, err + } + if err := task.Job.LoadRepo(ctx); err != nil { + return ret, err + } + runRepo := task.Job.Repo + + if err := runRepo.LoadOwner(ctx); err != nil { + return ret, err + } + + repoActionsCfg := runRepo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() + ownerActionsCfg, err := GetOwnerActionsConfig(ctx, runRepo.OwnerID) + if err != nil { + return ret, err + } + + var jobDeclaredPerms repo_model.ActionsTokenPermissions + if task.Job.TokenPermissions != nil { + jobDeclaredPerms = *task.Job.TokenPermissions + } else if repoActionsCfg.OverrideOwnerConfig { + jobDeclaredPerms = repoActionsCfg.GetDefaultTokenPermissions() + } else { + jobDeclaredPerms = ownerActionsCfg.GetDefaultTokenPermissions() + } + + var effectivePerms repo_model.ActionsTokenPermissions + if repoActionsCfg.OverrideOwnerConfig { + effectivePerms = repoActionsCfg.ClampPermissions(jobDeclaredPerms) + } else { + effectivePerms = ownerActionsCfg.ClampPermissions(jobDeclaredPerms) + } + + // Cross-repository access and fork pull requests are strictly read-only for security. + // This ensures a "task repo" cannot gain write access to other repositories via CrossRepoAccess settings. + isSameRepo := task.Job.RepoID == targetRepo.ID + restrictCrossRepoAccess := task.IsForkPullRequest || !isSameRepo + if restrictCrossRepoAccess { + effectivePerms = repo_model.ClampActionsTokenPermissions(effectivePerms, repo_model.MakeRestrictedPermissions()) + } + + return effectivePerms, nil +} diff --git a/models/actions/utils.go b/models/actions/utils.go index f6ba661ae3..1101a36cfc 100644 --- a/models/actions/utils.go +++ b/models/actions/utils.go @@ -13,6 +13,7 @@ import ( "time" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" ) @@ -72,13 +73,25 @@ func (indexes *LogIndexes) ToDB() ([]byte, error) { var timeSince = time.Since -func calculateDuration(started, stopped timeutil.TimeStamp, status Status) time.Duration { +// calculateDuration computes wall time for a run, job, task, or step. When status is terminal +// but stopped is missing or inconsistent with started, fallbackEnd (typically the row Updated +// time) is used so duration still reflects approximate elapsed time instead of 0 or a negative. +func calculateDuration(started, stopped timeutil.TimeStamp, status Status, fallbackEnd timeutil.TimeStamp) time.Duration { if started == 0 { return 0 } s := started.AsTime() if status.IsDone() { - return stopped.AsTime().Sub(s) + end := stopped + if stopped.IsZero() || stopped < started { + if !fallbackEnd.IsZero() && fallbackEnd >= started { + end = fallbackEnd + } else { + log.Trace("actions: invalid duration timestamps (started=%d, stopped=%d, fallbackEnd=%d, status=%s)", started, stopped, fallbackEnd, status) + return 0 + } + } + return end.AsTime().Sub(s) } return timeSince(s).Truncate(time.Second) } diff --git a/models/actions/utils_test.go b/models/actions/utils_test.go index 98c048d4ef..2f7e7da360 100644 --- a/models/actions/utils_test.go +++ b/models/actions/utils_test.go @@ -45,9 +45,10 @@ func Test_calculateDuration(t *testing.T) { return timeutil.TimeStamp(1000).AsTime().Sub(t) } type args struct { - started timeutil.TimeStamp - stopped timeutil.TimeStamp - status Status + started timeutil.TimeStamp + stopped timeutil.TimeStamp + status Status + fallbackEnd timeutil.TimeStamp } tests := []struct { name string @@ -81,10 +82,48 @@ func Test_calculateDuration(t *testing.T) { }, want: 100 * time.Second, }, + { + name: "done_stopped_zero_no_fallback", + args: args{ + started: 500, + stopped: 0, + status: StatusSuccess, + }, + want: 0, + }, + { + name: "done_stopped_zero_uses_fallback", + args: args{ + started: 500, + stopped: 0, + status: StatusSuccess, + fallbackEnd: 600, + }, + want: 100 * time.Second, + }, + { + name: "done_stopped_before_started_no_fallback", + args: args{ + started: 600, + stopped: 550, + status: StatusSuccess, + }, + want: 0, + }, + { + name: "done_stopped_before_started_uses_fallback", + args: args{ + started: 600, + stopped: 550, + status: StatusSuccess, + fallbackEnd: 650, + }, + want: 50 * time.Second, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, calculateDuration(tt.args.started, tt.args.stopped, tt.args.status), "calculateDuration(%v, %v, %v)", tt.args.started, tt.args.stopped, tt.args.status) + assert.Equalf(t, tt.want, calculateDuration(tt.args.started, tt.args.stopped, tt.args.status, tt.args.fallbackEnd), "calculateDuration(%v, %v, %v, %v)", tt.args.started, tt.args.stopped, tt.args.status, tt.args.fallbackEnd) }) } } diff --git a/models/activities/notification_list.go b/models/activities/notification_list.go index 6539e14ea2..d38758bc41 100644 --- a/models/activities/notification_list.go +++ b/models/activities/notification_list.go @@ -113,7 +113,7 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n } toNotify.AddMultiple(issueParticipants...) - // dont notify user who cause notification + // don't notify user who cause notification delete(toNotify, notificationAuthorID) // explicit unwatch on issue issueUnWatches, err := issues_model.GetIssueWatchersIDs(ctx, issueID, false) diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index 2f5aff0933..e2bb72b722 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -14,6 +14,7 @@ import ( "net/url" "slices" "strings" + "time" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/container" @@ -27,6 +28,11 @@ import ( "xorm.io/xorm" ) +// Authorization codes should expire within 10 minutes per https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2 +const oauth2AuthorizationCodeValidity = 10 * time.Minute + +var ErrOAuth2AuthorizationCodeInvalidated = errors.New("oauth2 authorization code already invalidated") + // OAuth2Application represents an OAuth2 client (RFC 6749) type OAuth2Application struct { ID int64 `xorm:"pk autoincr"` @@ -386,6 +392,14 @@ func (code *OAuth2AuthorizationCode) TableName() string { return "oauth2_authorization_code" } +// IsExpired reports whether the authorization code is expired. +func (code *OAuth2AuthorizationCode) IsExpired() bool { + if code.ValidUntil.IsZero() { + return true + } + return code.ValidUntil <= timeutil.TimeStampNow() +} + // GenerateRedirectURI generates a redirect URI for a successful authorization request. State will be used if not empty. func (code *OAuth2AuthorizationCode) GenerateRedirectURI(state string) (*url.URL, error) { redirect, err := url.Parse(code.RedirectURI) @@ -403,8 +417,14 @@ func (code *OAuth2AuthorizationCode) GenerateRedirectURI(state string) (*url.URL // Invalidate deletes the auth code from the database to invalidate this code func (code *OAuth2AuthorizationCode) Invalidate(ctx context.Context) error { - _, err := db.GetEngine(ctx).ID(code.ID).NoAutoCondition().Delete(code) - return err + affected, err := db.GetEngine(ctx).ID(code.ID).NoAutoCondition().Delete(code) + if err != nil { + return err + } + if affected == 0 { + return ErrOAuth2AuthorizationCodeInvalidated + } + return nil } // ValidateCodeChallenge validates the given verifier against the saved code challenge. This is part of the PKCE implementation. @@ -472,6 +492,7 @@ func (grant *OAuth2Grant) GenerateNewAuthorizationCode(ctx context.Context, redi // for code scanners to grab sensitive tokens. codeSecret := "gta_" + base32Lower.EncodeToString(rBytes) + validUntil := time.Now().Add(oauth2AuthorizationCodeValidity) code = &OAuth2AuthorizationCode{ Grant: grant, GrantID: grant.ID, @@ -479,6 +500,7 @@ func (grant *OAuth2Grant) GenerateNewAuthorizationCode(ctx context.Context, redi Code: codeSecret, CodeChallenge: codeChallenge, CodeChallengeMethod: codeChallengeMethod, + ValidUntil: timeutil.TimeStamp(validUntil.Unix()), } if err := db.Insert(ctx, code); err != nil { return nil, err diff --git a/models/auth/oauth2_test.go b/models/auth/oauth2_test.go index 97f750755a..88ae065652 100644 --- a/models/auth/oauth2_test.go +++ b/models/auth/oauth2_test.go @@ -5,13 +5,45 @@ package auth_test import ( "testing" + "time" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" ) +func TestOAuth2AuthorizationCodeValidity(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + t.Run("GenerateSetsValidUntil", func(t *testing.T) { + grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1}) + expectedValidUntil := timeutil.TimeStamp(time.Now().Unix() + 600) + code, err := grant.GenerateNewAuthorizationCode(t.Context(), "http://127.0.0.1/", "", "") + assert.NoError(t, err) + assert.Equal(t, expectedValidUntil, code.ValidUntil) + assert.False(t, code.IsExpired()) + assert.NoError(t, code.Invalidate(t.Context())) + }) + + t.Run("Expired", func(t *testing.T) { + defer timeutil.MockSet(time.Unix(2, 0).UTC())() + + code := &auth_model.OAuth2AuthorizationCode{ValidUntil: timeutil.TimeStamp(1)} + assert.True(t, code.IsExpired()) + }) + + t.Run("InvalidateTwice", func(t *testing.T) { + code, err := auth_model.GetOAuth2AuthorizationByCode(t.Context(), "authcode") + assert.NoError(t, err) + if assert.NotNil(t, code) { + assert.NoError(t, code.Invalidate(t.Context())) + assert.ErrorIs(t, code.Invalidate(t.Context()), auth_model.ErrOAuth2AuthorizationCodeInvalidated) + } + }) +} + func TestOAuth2Application_GenerateClientSecret(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) diff --git a/models/auth/source.go b/models/auth/source.go index 08cfc9615b..7a008f08a8 100644 --- a/models/auth/source.go +++ b/models/auth/source.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -117,7 +118,7 @@ func RegisterTypeConfig(typ Type, exemplar Config) { type Source struct { ID int64 `xorm:"pk autoincr"` Type Type - Name string `xorm:"UNIQUE"` + Name string `xorm:"UNIQUE"` // it can be the OIDC's provider name, see services/auth/source/oauth2/source_register.go: RegisterSource IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"` IsSyncEnabled bool `xorm:"INDEX NOT NULL DEFAULT false"` TwoFactorPolicy string `xorm:"two_factor_policy NOT NULL DEFAULT ''"` @@ -139,7 +140,10 @@ func init() { // BeforeSet is invoked from XORM before setting the value of a field of this object. func (source *Source) BeforeSet(colName string, val xorm.Cell) { if colName == "type" { - typ := Type(db.Cell2Int64(val)) + typ, _, err := db.CellToInt(val, NoType) + if err != nil { + setting.PanicInDevOrTesting("Unable to convert login source (id=%d) type: %v", source.ID, err) + } constructor, ok := registeredConfigs[typ] if !ok { return diff --git a/models/auth/source_test.go b/models/auth/source_test.go index ebc462c581..fbd663a64f 100644 --- a/models/auth/source_test.go +++ b/models/auth/source_test.go @@ -17,13 +17,9 @@ import ( ) type TestSource struct { - auth_model.ConfigBase + auth_model.ConfigBase `json:"-"` - Provider string - ClientID string - ClientSecret string - OpenIDConnectAutoDiscoveryURL string - IconURL string + TestField string } // FromDB fills up a LDAPConfig from serialized format. @@ -37,27 +33,23 @@ func (source *TestSource) ToDB() ([]byte, error) { } func TestDumpAuthSource(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + require.NoError(t, unittest.PrepareTestDatabase()) authSourceSchema, err := unittest.GetXORMEngine().TableInfo(new(auth_model.Source)) - assert.NoError(t, err) + require.NoError(t, err) auth_model.RegisterTypeConfig(auth_model.OAuth2, new(TestSource)) + source := &auth_model.Source{ + Type: auth_model.OAuth2, + Name: "TestSource", + Cfg: &TestSource{TestField: "TestValue"}, + } + require.NoError(t, auth_model.CreateSource(t.Context(), source)) - auth_model.CreateSource(t.Context(), &auth_model.Source{ - Type: auth_model.OAuth2, - Name: "TestSource", - IsActive: false, - Cfg: &TestSource{ - Provider: "ConvertibleSourceName", - ClientID: "42", - }, - }) - - sb := new(strings.Builder) - - // TODO: this test is quite hacky, it should use a low-level "select" (without model processors) but not a database dump - engine := unittest.GetXORMEngine() - require.NoError(t, engine.DumpTables([]*schemas.Table{authSourceSchema}, sb)) - assert.Contains(t, sb.String(), `"Provider":"ConvertibleSourceName"`) + // intentionally test the "dump" to make sure the dumped JSON is correct: https://github.com/go-gitea/gitea/pull/16847 + sb := &strings.Builder{} + require.NoError(t, unittest.GetXORMEngine().DumpTables([]*schemas.Table{authSourceSchema}, sb)) + // the dumped SQL is something like: + // INSERT INTO `login_source` (`id`, `type`, `name`, `is_active`, `is_sync_enabled`, `two_factor_policy`, `cfg`, `created_unix`, `updated_unix`) VALUES (1,6,'TestSource',0,0,'','{"TestField":"TestValue"}',1774179784,1774179784); + assert.Contains(t, sb.String(), `'{"TestField":"TestValue"}'`) } diff --git a/models/db/convert.go b/models/db/convert.go index 80b0f7b04b..374dbfd8a8 100644 --- a/models/db/convert.go +++ b/models/db/convert.go @@ -5,12 +5,11 @@ package db import ( "fmt" - "strconv" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "xorm.io/xorm" + "xorm.io/xorm/convert" "xorm.io/xorm/schemas" ) @@ -74,15 +73,14 @@ WHERE ST.name ='varchar'`) return err } -// Cell2Int64 converts a xorm.Cell type to int64, -// and handles possible irregular cases. -func Cell2Int64(val xorm.Cell) int64 { - switch (*val).(type) { - case []uint8: - log.Trace("Cell2Int64 ([]uint8): %v", *val) - - v, _ := strconv.ParseInt(string((*val).([]uint8)), 10, 64) - return v +// CellToInt converts a xorm.Cell field value to an int value +func CellToInt[T ~int | int64](cell xorm.Cell, def T) (ret T, has bool, err error) { + if *cell == nil { + return def, false, nil } - return (*val).(int64) + val, err := convert.AsInt64(*cell) + if err != nil { + return def, false, err + } + return T(val), true, err } diff --git a/models/dbfs/dbfile.go b/models/dbfs/dbfile.go index ccb13583e1..a6981cb7d6 100644 --- a/models/dbfs/dbfile.go +++ b/models/dbfs/dbfile.go @@ -75,7 +75,7 @@ func (f *file) readAt(fileMeta *dbfsMeta, offset int64, p []byte) (n int, err er } func (f *file) Read(p []byte) (n int, err error) { - if f.metaID == 0 || !f.allowRead { + if !f.allowRead { return 0, os.ErrInvalid } @@ -89,7 +89,7 @@ func (f *file) Read(p []byte) (n int, err error) { } func (f *file) Write(p []byte) (n int, err error) { - if f.metaID == 0 || !f.allowWrite { + if !f.allowWrite { return 0, os.ErrInvalid } @@ -184,10 +184,6 @@ func (f *file) Close() error { } func (f *file) Stat() (os.FileInfo, error) { - if f.metaID == 0 { - return nil, os.ErrInvalid - } - fileMeta, err := findFileMetaByID(f.ctx, f.metaID) if err != nil { return nil, err @@ -232,15 +228,17 @@ func (f *file) open(flag int) (err error) { if f.metaID != 0 { return os.ErrExist } - } else { - // create a new file if none exists. - if f.metaID == 0 { - if err = f.createEmpty(); err != nil { - return err - } + } + // create a new file if not exists. + if f.metaID == 0 { + if err = f.createEmpty(); err != nil { + return err } } } + if f.metaID == 0 { + return os.ErrNotExist + } if flag&os.O_TRUNC != 0 { if err = f.truncate(); err != nil { return err @@ -252,7 +250,7 @@ func (f *file) open(flag int) (err error) { } } return nil - } + } // end if: allowWrite // read only mode if f.metaID == 0 { @@ -322,9 +320,6 @@ func (f *file) delete() error { } func (f *file) size() (int64, error) { - if f.metaID == 0 { - return 0, os.ErrNotExist - } fileMeta, err := findFileMetaByID(f.ctx, f.metaID) if err != nil { return 0, err @@ -339,7 +334,7 @@ func findFileMetaByID(ctx context.Context, metaID int64) (*dbfsMeta, error) { } else if ok { return &fileMeta, nil } - return nil, nil //nolint:nilnil // return nil to indicate that the object does not exist + return nil, os.ErrNotExist } func buildPath(path string) string { diff --git a/models/dbfs/dbfs.go b/models/dbfs/dbfs.go index f68b4a2b70..3f768b5339 100644 --- a/models/dbfs/dbfs.go +++ b/models/dbfs/dbfs.go @@ -40,6 +40,9 @@ The DBFS solution: * In the future, when Gitea action needs to limit the log size (other CI/CD services also do so), it's easier to calculate the log file size. * Even sometimes the UI needs to render the tailing lines, the tailing lines can be found be counting the "\n" from the end of the file by seek. The seeking and finding is not the fastest way, but it's still acceptable and won't affect the performance too much. + +Limitations of the DBFS solution: +* Not fully POSIX-compliant, some behaviors may be different from the real filesystem, especially for concurrent read/write */ type dbfsMeta struct { diff --git a/models/dbfs/dbfs_test.go b/models/dbfs/dbfs_test.go index e1ecd871e4..ca57ebe171 100644 --- a/models/dbfs/dbfs_test.go +++ b/models/dbfs/dbfs_test.go @@ -9,19 +9,14 @@ import ( "os" "testing" + "code.gitea.io/gitea/modules/test" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func changeDefaultFileBlockSize(n int64) (restore func()) { - old := defaultFileBlockSize - defaultFileBlockSize = n - return func() { - defaultFileBlockSize = old - } -} - func TestDbfsBasic(t *testing.T) { - defer changeDefaultFileBlockSize(4)() + defer test.MockVariableValue(&defaultFileBlockSize, 4)() // test basic write/read f, err := OpenFile(t.Context(), "test.txt", os.O_RDWR|os.O_CREATE) @@ -122,10 +117,55 @@ func TestDbfsBasic(t *testing.T) { stat, err = f.Stat() assert.NoError(t, err) assert.EqualValues(t, 10, stat.Size()) + + t.Run("NonExisting", func(t *testing.T) { + f, err := OpenFile(t.Context(), "non-existing.txt", os.O_RDONLY) + assert.ErrorIs(t, err, os.ErrNotExist) + assert.Nil(t, f) + + f, err = OpenFile(t.Context(), "non-existing.txt", os.O_WRONLY) + assert.ErrorIs(t, err, os.ErrNotExist) + assert.Nil(t, f) + + f, err = OpenFile(t.Context(), "non-existing.txt", os.O_WRONLY|os.O_APPEND|os.O_TRUNC) + assert.ErrorIs(t, err, os.ErrNotExist) + assert.Nil(t, f) + }) + + t.Run("Existing", func(t *testing.T) { + assertFileContent := func(f File, expected string) { + _, err := f.Seek(0, io.SeekStart) + require.NoError(t, err) + buf, err := io.ReadAll(f) + require.NoError(t, err) + assert.Equal(t, expected, string(buf)) + } + + f, err := OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_CREATE) + require.NoError(t, err) + _, _ = f.Write([]byte("test")) + assertFileContent(f, "test") + assert.NoError(t, f.Close()) + + f, err = OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND) + require.NoError(t, err) + _, _ = f.Write([]byte("\nnew")) + assertFileContent(f, "test\nnew") + assert.NoError(t, f.Close()) + + f, err = OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_TRUNC) + require.NoError(t, err) + assertFileContent(f, "") + assert.NoError(t, f.Close()) + + f, err = OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_CREATE|os.O_EXCL) + assert.ErrorIs(t, err, os.ErrExist) + assert.Nil(t, f) + }) } func TestDbfsReadWrite(t *testing.T) { - defer changeDefaultFileBlockSize(4)() + defer test.MockVariableValue(&defaultFileBlockSize, 4)() f1, err := OpenFile(t.Context(), "test.log", os.O_RDWR|os.O_CREATE) assert.NoError(t, err) @@ -157,30 +197,32 @@ func TestDbfsReadWrite(t *testing.T) { } func TestDbfsSeekWrite(t *testing.T) { - defer changeDefaultFileBlockSize(4)() + defer test.MockVariableValue(&defaultFileBlockSize, 4)() - f, err := OpenFile(t.Context(), "test2.log", os.O_RDWR|os.O_CREATE) - assert.NoError(t, err) - defer f.Close() + // write something + fw, err := OpenFile(t.Context(), "test2.log", os.O_RDWR|os.O_CREATE) + require.NoError(t, err) + defer fw.Close() - n, err := f.Write([]byte("111")) + n, err := fw.Write([]byte("111")) assert.NoError(t, err) - _, err = f.Seek(int64(n), io.SeekStart) + _, err = fw.Seek(int64(n), io.SeekStart) assert.NoError(t, err) - _, err = f.Write([]byte("222")) + _, err = fw.Write([]byte("222")) assert.NoError(t, err) - _, err = f.Seek(int64(n), io.SeekStart) + _, err = fw.Seek(int64(n), io.SeekStart) assert.NoError(t, err) - _, err = f.Write([]byte("333")) + _, err = fw.Write([]byte("333")) assert.NoError(t, err) + // then read it fr, err := OpenFile(t.Context(), "test2.log", os.O_RDONLY) - assert.NoError(t, err) - defer f.Close() + require.NoError(t, err) + defer fr.Close() buf, err := io.ReadAll(fr) assert.NoError(t, err) diff --git a/models/fixtures/access.yml b/models/fixtures/access.yml index 596046e950..c0aa06c86d 100644 --- a/models/fixtures/access.yml +++ b/models/fixtures/access.yml @@ -177,3 +177,5 @@ user_id: 40 repo_id: 1 mode: 2 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/access_token.yml b/models/fixtures/access_token.yml index 0744255f66..d85d785da5 100644 --- a/models/fixtures/access_token.yml +++ b/models/fixtures/access_token.yml @@ -31,3 +31,5 @@ created_unix: 946687980 updated_unix: 946687980 # commented out tokens so you can see what they are in plaintext + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/action.yml b/models/fixtures/action.yml index af9ce93ba5..32f2ae8764 100644 --- a/models/fixtures/action.yml +++ b/models/fixtures/action.yml @@ -73,3 +73,5 @@ is_private: false created_unix: 1680454039 content: '4|' # issueId 5 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/action_artifact.yml b/models/fixtures/action_artifact.yml index ee8ef0d5ce..5fcc70aa53 100644 --- a/models/fixtures/action_artifact.yml +++ b/models/fixtures/action_artifact.yml @@ -141,3 +141,41 @@ created_unix: 1730330775 updated_unix: 1730330775 expired_unix: 1738106775 + +- + id: 26 + run_id: 792 + runner_id: 1 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + storage_path: "27/5/1730330775594233150.chunk" + file_size: 1024 + file_compressed_size: 1024 + content_encoding: "application/pdf" + artifact_path: "report.pdf" + artifact_name: "report.pdf" + status: 2 + created_unix: 1730330775 + updated_unix: 1730330775 + expired_unix: 1738106775 + +- + id: 27 + run_id: 792 + runner_id: 1 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + storage_path: "27/5/1730330775594233150.chunk" + file_size: 1024 + file_compressed_size: 1024 + content_encoding: "application/html" + artifact_path: "report.html" + artifact_name: "report.html" + status: 2 + created_unix: 1730330775 + updated_unix: 1730330775 + expired_unix: 1738106775 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/action_run.yml b/models/fixtures/action_run.yml index ac5e8303c3..b7d1201189 100644 --- a/models/fixtures/action_run.yml +++ b/models/fixtures/action_run.yml @@ -140,23 +140,4 @@ need_approval: 0 approved_by: 0 -- - id: 805 - title: "update actions" - repo_id: 4 - owner_id: 1 - workflow_id: "artifact.yaml" - index: 191 - trigger_user_id: 1 - ref: "refs/heads/master" - commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0" - event: "push" - trigger_event: "push" - is_fork_pull_request: 0 - status: 5 - started: 1683636528 - stopped: 1683636626 - created: 1683636108 - updated: 1683636626 - need_approval: 0 - approved_by: 0 +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/action_run_job.yml b/models/fixtures/action_run_job.yml index 04799b73ca..2a4e64285f 100644 --- a/models/fixtures/action_run_job.yml +++ b/models/fixtures/action_run_job.yml @@ -130,17 +130,4 @@ started: 1683636528 stopped: 1683636626 -- - id: 206 - run_id: 805 - repo_id: 4 - owner_id: 1 - commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 - is_fork_pull_request: 0 - name: job_2 - attempt: 1 - job_id: job_2 - task_id: 56 - status: 3 - started: 1683636528 - stopped: 1683636626 +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/action_runner.yml b/models/fixtures/action_runner.yml index ecb7214006..110ff627a2 100644 --- a/models/fixtures/action_runner.yml +++ b/models/fixtures/action_runner.yml @@ -49,3 +49,5 @@ repo_id: 0 description: "This runner is going to be deleted" agent_labels: '["runner_to_be_deleted","linux"]' + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/action_runner_token.yml b/models/fixtures/action_runner_token.yml index 6520b7f6fb..3af8a28c9c 100644 --- a/models/fixtures/action_runner_token.yml +++ b/models/fixtures/action_runner_token.yml @@ -33,3 +33,5 @@ is_active: 1 created: 1695617751 updated: 1695617751 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/action_task.yml b/models/fixtures/action_task.yml index e1bc588dc5..13efe378c4 100644 --- a/models/fixtures/action_task.yml +++ b/models/fixtures/action_task.yml @@ -178,22 +178,4 @@ log_size: 0 log_expired: 0 -- - id: 56 - attempt: 1 - runner_id: 1 - status: 3 # 3 is the status code for "cancelled" - started: 1683636528 - stopped: 1683636626 - repo_id: 4 - owner_id: 1 - commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 - is_fork_pull_request: 0 - token_hash: 6d8ef48297195edcc8e22c70b3020eaa06c52976db67d39b4240c64a69a2cc1508825121b7b8394e48e00b1bf3718b2aaaab - token_salt: eeeeeeee - token_last_eight: eeeeeeee - log_filename: artifact-test2/2f/47.log - log_in_storage: 1 - log_length: 707 - log_size: 90179 - log_expired: 0 +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/action_task_output.yml b/models/fixtures/action_task_output.yml index 314e9f7115..741b193b13 100644 --- a/models/fixtures/action_task_output.yml +++ b/models/fixtures/action_task_output.yml @@ -18,3 +18,5 @@ task_id: 50 output_key: output_b output_value: bbb + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/attachment.yml b/models/fixtures/attachment.yml index 570d4a27da..0870895a71 100644 --- a/models/fixtures/attachment.yml +++ b/models/fixtures/attachment.yml @@ -166,3 +166,5 @@ download_count: 0 size: 0 created_unix: 946684800 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/badge.yml b/models/fixtures/badge.yml new file mode 100644 index 0000000000..72550be79a --- /dev/null +++ b/models/fixtures/badge.yml @@ -0,0 +1,7 @@ +- + id: 1 + slug: badge1 + description: just a test badge + image_url: badge1.png + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/branch.yml b/models/fixtures/branch.yml index a17999091e..e09022a614 100644 --- a/models/fixtures/branch.yml +++ b/models/fixtures/branch.yml @@ -249,3 +249,5 @@ is_deleted: false deleted_by_id: 0 deleted_unix: 0 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/collaboration.yml b/models/fixtures/collaboration.yml index 4c3ac367f6..2de3448809 100644 --- a/models/fixtures/collaboration.yml +++ b/models/fixtures/collaboration.yml @@ -63,3 +63,5 @@ repo_id: 32 user_id: 10 mode: 2 # write + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/comment.yml b/models/fixtures/comment.yml index 8fde386e22..6930dbb58e 100644 --- a/models/fixtures/comment.yml +++ b/models/fixtures/comment.yml @@ -102,3 +102,5 @@ review_id: 22 assignee_id: 5 created_unix: 946684817 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/commit_status.yml b/models/fixtures/commit_status.yml index 87c652e53a..df3bc42505 100644 --- a/models/fixtures/commit_status.yml +++ b/models/fixtures/commit_status.yml @@ -57,3 +57,5 @@ context: deploy/awesomeness context_hash: ae9547713a6665fc4261d0756904932085a41cf2 creator_id: 2 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/commit_status_index.yml b/models/fixtures/commit_status_index.yml index f63343b042..9157911bac 100644 --- a/models/fixtures/commit_status_index.yml +++ b/models/fixtures/commit_status_index.yml @@ -3,3 +3,5 @@ repo_id: 1 sha: "1234123412341234123412341234123412341234" max_index: 5 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/deploy_key.yml b/models/fixtures/deploy_key.yml index ca780a73aa..0d1b2f0098 100644 --- a/models/fixtures/deploy_key.yml +++ b/models/fixtures/deploy_key.yml @@ -1 +1,3 @@ [] # empty + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/email_address.yml b/models/fixtures/email_address.yml index 0f6bd9ee6d..b3c78120af 100644 --- a/models/fixtures/email_address.yml +++ b/models/fixtures/email_address.yml @@ -317,3 +317,5 @@ lower_email: user40@example.com is_activated: true is_primary: true + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/external_login_user.yml b/models/fixtures/external_login_user.yml index ca780a73aa..0d1b2f0098 100644 --- a/models/fixtures/external_login_user.yml +++ b/models/fixtures/external_login_user.yml @@ -1 +1,3 @@ [] # empty + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/follow.yml b/models/fixtures/follow.yml index b8d35828bf..f8de0e039d 100644 --- a/models/fixtures/follow.yml +++ b/models/fixtures/follow.yml @@ -17,3 +17,5 @@ id: 4 user_id: 31 follow_id: 33 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/gpg_key.yml b/models/fixtures/gpg_key.yml index 2d54313fdf..3d2895dc1c 100644 --- a/models/fixtures/gpg_key.yml +++ b/models/fixtures/gpg_key.yml @@ -21,3 +21,5 @@ can_encrypt_comms: true can_encrypt_storage: true can_certify: true + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/gpg_key_import.yml b/models/fixtures/gpg_key_import.yml index ca780a73aa..0d1b2f0098 100644 --- a/models/fixtures/gpg_key_import.yml +++ b/models/fixtures/gpg_key_import.yml @@ -1 +1,3 @@ [] # empty + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/hook_task.yml b/models/fixtures/hook_task.yml index 6023719b1e..e19eeb0368 100644 --- a/models/fixtures/hook_task.yml +++ b/models/fixtures/hook_task.yml @@ -35,3 +35,5 @@ "X-Head": "42" } } + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml index ca5b1c6cd1..6da3c9e279 100644 --- a/models/fixtures/issue.yml +++ b/models/fixtures/issue.yml @@ -372,3 +372,5 @@ created_unix: 1707270422 updated_unix: 1707270422 is_locked: false + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/issue_assignees.yml b/models/fixtures/issue_assignees.yml index c40ecad676..a0bf422dc9 100644 --- a/models/fixtures/issue_assignees.yml +++ b/models/fixtures/issue_assignees.yml @@ -18,3 +18,5 @@ id: 5 assignee_id: 10 issue_id: 6 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/issue_index.yml b/models/fixtures/issue_index.yml index 5aabc08e38..5110068447 100644 --- a/models/fixtures/issue_index.yml +++ b/models/fixtures/issue_index.yml @@ -33,3 +33,5 @@ - group_id: 51 max_index: 1 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/issue_label.yml b/models/fixtures/issue_label.yml index f4ecb1f923..3754bd7828 100644 --- a/models/fixtures/issue_label.yml +++ b/models/fixtures/issue_label.yml @@ -17,3 +17,5 @@ id: 4 issue_id: 2 label_id: 4 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/issue_pin.yml b/models/fixtures/issue_pin.yml index 14b7a72d84..dc3d1c60d9 100644 --- a/models/fixtures/issue_pin.yml +++ b/models/fixtures/issue_pin.yml @@ -4,3 +4,5 @@ issue_id: 4 is_pull: false pin_order: 1 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/issue_user.yml b/models/fixtures/issue_user.yml index 64824316ea..756cb7be4b 100644 --- a/models/fixtures/issue_user.yml +++ b/models/fixtures/issue_user.yml @@ -18,3 +18,5 @@ issue_id: 1 is_read: false is_mentioned: true + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/issue_watch.yml b/models/fixtures/issue_watch.yml index 4bc3ff1b8b..edc1041abc 100644 --- a/models/fixtures/issue_watch.yml +++ b/models/fixtures/issue_watch.yml @@ -29,3 +29,5 @@ is_watching: false created_unix: 946684800 updated_unix: 946684800 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/label.yml b/models/fixtures/label.yml index acfac74968..064f790a77 100644 --- a/models/fixtures/label.yml +++ b/models/fixtures/label.yml @@ -107,3 +107,5 @@ num_issues: 0 num_closed_issues: 0 archived_unix: 0 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/lfs_meta_object.yml b/models/fixtures/lfs_meta_object.yml index ae5ae56542..0fe430f147 100644 --- a/models/fixtures/lfs_meta_object.yml +++ b/models/fixtures/lfs_meta_object.yml @@ -30,3 +30,5 @@ size: 25 repository_id: 54 created_unix: 1671607299 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/login_source.yml b/models/fixtures/login_source.yml index ca780a73aa..0d1b2f0098 100644 --- a/models/fixtures/login_source.yml +++ b/models/fixtures/login_source.yml @@ -1 +1,3 @@ [] # empty + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/milestone.yml b/models/fixtures/milestone.yml index 87c30cc96c..c4ed2aea78 100644 --- a/models/fixtures/milestone.yml +++ b/models/fixtures/milestone.yml @@ -52,3 +52,5 @@ num_closed_issues: 0 completeness: 0 deadline_unix: 253370764800 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/mirror.yml b/models/fixtures/mirror.yml index 97bc4ae60d..1f690654cb 100644 --- a/models/fixtures/mirror.yml +++ b/models/fixtures/mirror.yml @@ -47,3 +47,5 @@ next_update_unix: 0 lfs_enabled: false lfs_endpoint: "" + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/notice.yml b/models/fixtures/notice.yml index af08f07bfa..17e26d7634 100644 --- a/models/fixtures/notice.yml +++ b/models/fixtures/notice.yml @@ -12,3 +12,5 @@ id: 3 type: 1 # NoticeRepository description: description3 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/notification.yml b/models/fixtures/notification.yml index bd279d4bb2..dcfbeada39 100644 --- a/models/fixtures/notification.yml +++ b/models/fixtures/notification.yml @@ -52,3 +52,5 @@ issue_id: 4 created_unix: 946688800 updated_unix: 946688820 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/oauth2_application.yml b/models/fixtures/oauth2_application.yml index 2f38cb58b6..5b3b00b16e 100644 --- a/models/fixtures/oauth2_application.yml +++ b/models/fixtures/oauth2_application.yml @@ -18,3 +18,5 @@ created_unix: 1546869730 updated_unix: 1546869730 confidential_client: false + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/oauth2_authorization_code.yml b/models/fixtures/oauth2_authorization_code.yml index d29502164e..64d8b17507 100644 --- a/models/fixtures/oauth2_authorization_code.yml +++ b/models/fixtures/oauth2_authorization_code.yml @@ -13,3 +13,5 @@ code_challenge_method: "S256" redirect_uri: "http://127.0.0.1/" valid_until: 3546869730 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/oauth2_grant.yml b/models/fixtures/oauth2_grant.yml index e63286878b..54f4e45e62 100644 --- a/models/fixtures/oauth2_grant.yml +++ b/models/fixtures/oauth2_grant.yml @@ -29,3 +29,5 @@ scope: "whatever" created_unix: 1546869730 updated_unix: 1546869730 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/org_user.yml b/models/fixtures/org_user.yml index 73a3e9dba9..dc35701182 100644 --- a/models/fixtures/org_user.yml +++ b/models/fixtures/org_user.yml @@ -135,3 +135,5 @@ uid: 20 org_id: 17 is_public: false + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/project.yml b/models/fixtures/project.yml index 44d87bce04..e61781fd7f 100644 --- a/models/fixtures/project.yml +++ b/models/fixtures/project.yml @@ -69,3 +69,5 @@ type: 2 created_unix: 1688973000 updated_unix: 1688973000 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/project_board.yml b/models/fixtures/project_board.yml index 3293dea6ed..91d2198171 100644 --- a/models/fixtures/project_board.yml +++ b/models/fixtures/project_board.yml @@ -75,3 +75,5 @@ default: true created_unix: 1588117528 updated_unix: 1588117528 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/project_issue.yml b/models/fixtures/project_issue.yml index b1af05908a..7d9d511882 100644 --- a/models/fixtures/project_issue.yml +++ b/models/fixtures/project_issue.yml @@ -21,3 +21,5 @@ issue_id: 5 project_id: 1 project_board_id: 3 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/protected_branch.yml b/models/fixtures/protected_branch.yml index ca780a73aa..0d1b2f0098 100644 --- a/models/fixtures/protected_branch.yml +++ b/models/fixtures/protected_branch.yml @@ -1 +1,3 @@ [] # empty + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/protected_tag.yml b/models/fixtures/protected_tag.yml index 1944e7bd84..cb83439645 100644 --- a/models/fixtures/protected_tag.yml +++ b/models/fixtures/protected_tag.yml @@ -22,3 +22,5 @@ allowlist_team_i_ds: "[]" created_unix: 1715596037 updated_unix: 1715596037 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/public_key.yml b/models/fixtures/public_key.yml index 856b0e3fb2..756bca86b6 100644 --- a/models/fixtures/public_key.yml +++ b/models/fixtures/public_key.yml @@ -10,3 +10,5 @@ updated_unix: 1565224552 login_source_id: 0 verified: false + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/pull_request.yml b/models/fixtures/pull_request.yml index 9a16316e5a..b8da7fe081 100644 --- a/models/fixtures/pull_request.yml +++ b/models/fixtures/pull_request.yml @@ -117,3 +117,5 @@ index: 1 head_repo_id: 61 base_repo_id: 61 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/reaction.yml b/models/fixtures/reaction.yml index ee571a73a4..9effcc98f7 100644 --- a/models/fixtures/reaction.yml +++ b/models/fixtures/reaction.yml @@ -37,3 +37,5 @@ comment_id: 2 user_id: 1 created_unix: 1573248005 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/release.yml b/models/fixtures/release.yml index 372a79509f..be44e331ec 100644 --- a/models/fixtures/release.yml +++ b/models/fixtures/release.yml @@ -150,3 +150,5 @@ is_prerelease: false is_tag: false created_unix: 946684803 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/renamed_branch.yml b/models/fixtures/renamed_branch.yml index efa5130a2b..9055080ff8 100644 --- a/models/fixtures/renamed_branch.yml +++ b/models/fixtures/renamed_branch.yml @@ -3,3 +3,5 @@ repo_id: 1 from: dev to: master + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/repo_archiver.yml b/models/fixtures/repo_archiver.yml index ca780a73aa..0d1b2f0098 100644 --- a/models/fixtures/repo_archiver.yml +++ b/models/fixtures/repo_archiver.yml @@ -1 +1,3 @@ [] # empty + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/repo_indexer_status.yml b/models/fixtures/repo_indexer_status.yml index ca780a73aa..0d1b2f0098 100644 --- a/models/fixtures/repo_indexer_status.yml +++ b/models/fixtures/repo_indexer_status.yml @@ -1 +1,3 @@ [] # empty + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/repo_license.yml b/models/fixtures/repo_license.yml index ca780a73aa..0d1b2f0098 100644 --- a/models/fixtures/repo_license.yml +++ b/models/fixtures/repo_license.yml @@ -1 +1,3 @@ [] # empty + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/repo_redirect.yml b/models/fixtures/repo_redirect.yml index 8850c8d780..60459d638d 100644 --- a/models/fixtures/repo_redirect.yml +++ b/models/fixtures/repo_redirect.yml @@ -3,3 +3,5 @@ owner_id: 2 lower_name: oldrepo1 redirect_repo_id: 1 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/repo_topic.yml b/models/fixtures/repo_topic.yml index f166faccc1..3a4e7edaa9 100644 --- a/models/fixtures/repo_topic.yml +++ b/models/fixtures/repo_topic.yml @@ -25,3 +25,5 @@ - repo_id: 2 topic_id: 6 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/repo_transfer.yml b/models/fixtures/repo_transfer.yml index b12e6b207f..0a26eaec8e 100644 --- a/models/fixtures/repo_transfer.yml +++ b/models/fixtures/repo_transfer.yml @@ -29,3 +29,5 @@ repo_id: 5 created_unix: 1553610671 updated_unix: 1553610671 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml index 4c3e37500f..69f083ccd7 100644 --- a/models/fixtures/repo_unit.yml +++ b/models/fixtures/repo_unit.yml @@ -747,3 +747,5 @@ type: 10 config: "{}" created_unix: 946684810 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index dfa514db37..d8eb796207 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -1788,3 +1788,5 @@ size: 0 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/review.yml b/models/fixtures/review.yml index 5b8bbceca9..abcc9d3bb2 100644 --- a/models/fixtures/review.yml +++ b/models/fixtures/review.yml @@ -214,3 +214,5 @@ original_author_id: 0 updated_unix: 946684817 created_unix: 946684817 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/star.yml b/models/fixtures/star.yml index 39b51b3736..96db493448 100644 --- a/models/fixtures/star.yml +++ b/models/fixtures/star.yml @@ -17,3 +17,5 @@ id: 4 uid: 10 repo_id: 32 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/stopwatch.yml b/models/fixtures/stopwatch.yml index b7919d6fbb..bbb3852069 100644 --- a/models/fixtures/stopwatch.yml +++ b/models/fixtures/stopwatch.yml @@ -9,3 +9,5 @@ user_id: 2 issue_id: 2 created_unix: 1500988002 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/system_setting.yml b/models/fixtures/system_setting.yml index dcad176c89..ae612fa9f9 100644 --- a/models/fixtures/system_setting.yml +++ b/models/fixtures/system_setting.yml @@ -13,3 +13,5 @@ version: 1 created: 1653533198 updated: 1653533198 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/team.yml b/models/fixtures/team.yml index b549d0589b..3c2cb7802a 100644 --- a/models/fixtures/team.yml +++ b/models/fixtures/team.yml @@ -261,3 +261,5 @@ num_members: 1 includes_all_repositories: true can_create_org_repo: false + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/team_repo.yml b/models/fixtures/team_repo.yml index a29078107e..c91f74467a 100644 --- a/models/fixtures/team_repo.yml +++ b/models/fixtures/team_repo.yml @@ -75,3 +75,5 @@ org_id: 41 team_id: 22 repo_id: 61 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/team_unit.yml b/models/fixtures/team_unit.yml index 110019eee3..bb95087009 100644 --- a/models/fixtures/team_unit.yml +++ b/models/fixtures/team_unit.yml @@ -340,3 +340,5 @@ team_id: 24 type: 1 # code access_mode: 2 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/team_user.yml b/models/fixtures/team_user.yml index 6b2d153278..4cceffee6a 100644 --- a/models/fixtures/team_user.yml +++ b/models/fixtures/team_user.yml @@ -159,3 +159,5 @@ org_id: 35 team_id: 24 uid: 2 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/topic.yml b/models/fixtures/topic.yml index 055addf510..97ac821fc1 100644 --- a/models/fixtures/topic.yml +++ b/models/fixtures/topic.yml @@ -27,3 +27,5 @@ id: 6 name: topicname2 repo_count: 2 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/tracked_time.yml b/models/fixtures/tracked_time.yml index 768af38d9e..7c2145a6d8 100644 --- a/models/fixtures/tracked_time.yml +++ b/models/fixtures/tracked_time.yml @@ -69,3 +69,5 @@ time: 100000 created_unix: 947688815 deleted: true + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/two_factor.yml b/models/fixtures/two_factor.yml index d8cb85274b..13b421b7b4 100644 --- a/models/fixtures/two_factor.yml +++ b/models/fixtures/two_factor.yml @@ -7,3 +7,5 @@ last_used_passcode: created_unix: 1564253724 updated_unix: 1564253724 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index 976a236011..1a33947e04 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -1556,3 +1556,5 @@ repo_admin_change_team_access: false theme: "" keep_activity_private: false + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/user_blocking.yml b/models/fixtures/user_blocking.yml index 2ec9d99df5..c1714e40c8 100644 --- a/models/fixtures/user_blocking.yml +++ b/models/fixtures/user_blocking.yml @@ -17,3 +17,5 @@ id: 4 blocker_id: 50 blockee_id: 34 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/user_open_id.yml b/models/fixtures/user_open_id.yml index d3a367b99d..72fe7e34b8 100644 --- a/models/fixtures/user_open_id.yml +++ b/models/fixtures/user_open_id.yml @@ -15,3 +15,5 @@ uid: 2 uri: https://domain1.tld/user2/ show: true + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/user_redirect.yml b/models/fixtures/user_redirect.yml index c668cb6c3b..1b0f7a9494 100644 --- a/models/fixtures/user_redirect.yml +++ b/models/fixtures/user_redirect.yml @@ -6,3 +6,5 @@ id: 2 lower_name: olduser2 redirect_user_id: 2 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/watch.yml b/models/fixtures/watch.yml index 18bcd2ed2b..b7ee121f24 100644 --- a/models/fixtures/watch.yml +++ b/models/fixtures/watch.yml @@ -39,3 +39,5 @@ user_id: 10 repo_id: 32 mode: 1 # normal + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/webauthn_credential.yml b/models/fixtures/webauthn_credential.yml index bc43127fcd..d188a4d76a 100644 --- a/models/fixtures/webauthn_credential.yml +++ b/models/fixtures/webauthn_credential.yml @@ -7,3 +7,5 @@ clone_warning: false created_unix: 946684800 updated_unix: 946684800 + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/webhook.yml b/models/fixtures/webhook.yml index ec282914b8..f372aaaecb 100644 --- a/models/fixtures/webhook.yml +++ b/models/fixtures/webhook.yml @@ -50,3 +50,5 @@ events: '{"push_only":true,"branch_filter":"{master,feature*}"}' is_active: true is_system_webhook: false + +# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/git/branch.go b/models/git/branch.go index bc6e2a1748..698c43f147 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -247,7 +247,7 @@ func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64 return err } for _, branch := range branches { - if err := AddDeletedBranch(ctx, repoID, branch.Name, doerID); err != nil { + if err := MarkBranchAsDeleted(ctx, repoID, branch.Name, doerID); err != nil { return err } } @@ -268,8 +268,8 @@ func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string }) } -// AddDeletedBranch adds a deleted branch to the database -func AddDeletedBranch(ctx context.Context, repoID int64, branchName string, deletedByID int64) error { +// MarkBranchAsDeleted marks branch as deleted +func MarkBranchAsDeleted(ctx context.Context, repoID int64, branchName string, deletedByID int64) error { branch, err := GetBranch(ctx, repoID, branchName) if err != nil { return err @@ -583,3 +583,12 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o return newBranches, nil } + +// CountBranches returns the number of branches in the repository +func CountBranches(ctx context.Context, repoID int64, includeDeleted bool) (int64, error) { + sess := db.GetEngine(ctx).Where("repo_id=?", repoID) + if !includeDeleted { + sess.And("is_deleted=?", false) + } + return sess.Count(new(Branch)) +} diff --git a/models/git/branch_test.go b/models/git/branch_test.go index 9e6148946d..3832df9350 100644 --- a/models/git/branch_test.go +++ b/models/git/branch_test.go @@ -28,8 +28,8 @@ func TestAddDeletedBranch(t *testing.T) { firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) assert.True(t, firstBranch.IsDeleted) - assert.NoError(t, git_model.AddDeletedBranch(t.Context(), repo.ID, firstBranch.Name, firstBranch.DeletedByID)) - assert.NoError(t, git_model.AddDeletedBranch(t.Context(), repo.ID, "branch2", int64(1))) + assert.NoError(t, git_model.MarkBranchAsDeleted(t.Context(), repo.ID, firstBranch.Name, firstBranch.DeletedByID)) + assert.NoError(t, git_model.MarkBranchAsDeleted(t.Context(), repo.ID, "branch2", int64(1))) secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo.ID, Name: "branch2"}) assert.True(t, secondBranch.IsDeleted) @@ -263,3 +263,25 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, deletedBranch) } + +func TestCountBranches(t *testing.T) { + // 1. Setup - Exactly like TestAddDeletedBranch + assert.NoError(t, unittest.PrepareTestDatabase()) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + // 2. Execution - Using t.Context() to match the rest of the file + initialCount, err := git_model.CountBranches(t.Context(), repo.ID, false) + assert.NoError(t, err) + + // 3. Database Action - Using t.Context() + err = db.Insert(t.Context(), &git_model.Branch{ + RepoID: repo.ID, + Name: "test-branch-for-counting", + }) + assert.NoError(t, err) + + // 4. Verification + newCount, err := git_model.CountBranches(t.Context(), repo.ID, false) + assert.NoError(t, err) + assert.Equal(t, initialCount+1, newCount) +} diff --git a/models/git/commit_status_test.go b/models/git/commit_status_test.go index d1b9dfc3bf..f18d3470b0 100644 --- a/models/git/commit_status_test.go +++ b/models/git/commit_status_test.go @@ -139,7 +139,7 @@ func Test_CalcCommitStatus(t *testing.T) { }, }, expected: &git_model.CommitStatus{ - State: commitstatus.CommitStatusPending, + State: commitstatus.CommitStatusFailure, }, }, { @@ -243,7 +243,7 @@ func TestCommitStatusesHideActionsURL(t *testing.T) { statuses := []*git_model.CommitStatus{ { RepoID: repo.ID, - TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), run.Index), + TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), run.ID), }, { RepoID: repo.ID, diff --git a/models/git/protected_branch.go b/models/git/protected_branch.go index f4567a0aac..f242f94f7b 100644 --- a/models/git/protected_branch.go +++ b/models/git/protected_branch.go @@ -495,9 +495,9 @@ func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, curre if err != nil { return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err) } - perm, err := access_model.GetUserRepoPermission(ctx, repo, user) + perm, err := access_model.GetIndividualUserRepoPermission(ctx, repo, user) if err != nil { - return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err) + return nil, fmt.Errorf("GetIndividualUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err) } if !perm.CanWrite(unit.TypeCode) { diff --git a/models/issues/comment.go b/models/issues/comment.go index 25e74c01ea..34ce7f3500 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -7,6 +7,7 @@ package issues import ( "context" + "errors" "fmt" "html/template" "slices" @@ -21,6 +22,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/htmlutil" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/references" @@ -326,21 +328,34 @@ type Comment struct { RefIssue *Issue `xorm:"-"` RefComment *Comment `xorm:"-"` - Commits []*git_model.SignCommitWithStatuses `xorm:"-"` - OldCommit string `xorm:"-"` - NewCommit string `xorm:"-"` - CommitsNum int64 `xorm:"-"` - IsForcePush bool `xorm:"-"` + Commits []*git_model.SignCommitWithStatuses `xorm:"-"` + OldCommit string `xorm:"-"` + NewCommit string `xorm:"-"` + CommitsNum int64 `xorm:"-"` + + // Templates still use it. It is not persisted in database, it is only set when creating or loading + IsForcePush bool `xorm:"-"` } func init() { db.RegisterModel(new(Comment)) } -// PushActionContent is content of push pull comment +// PushActionContent is content of pull request's push comment type PushActionContent struct { - IsForcePush bool `json:"is_force_push"` - CommitIDs []string `json:"commit_ids"` + IsForcePush bool `json:"is_force_push"` + // if IsForcePush=true, CommitIDs contains the commit pair [old head, new head] + // if IsForcePush=false, CommitIDs contains the new commits newly pushed to the head branch + CommitIDs []string `json:"commit_ids"` +} + +func (c *Comment) GetPushActionContent() (*PushActionContent, error) { + if c.Type != CommentTypePullRequestPush { + return nil, errors.New("not a pull request push comment") + } + var data PushActionContent + _ = json.Unmarshal(util.UnsafeStringToBytes(c.Content), &data) + return &data, nil } // LoadIssue loads the issue reference for the comment diff --git a/models/issues/issue_label.go b/models/issues/issue_label.go index 151469a9b8..697448cf5e 100644 --- a/models/issues/issue_label.go +++ b/models/issues/issue_label.go @@ -356,7 +356,7 @@ func ClearIssueLabels(ctx context.Context, issue *Issue, doer *user_model.User) return err } - perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) + perm, err := access_model.GetDoerRepoPermission(ctx, issue.Repo, doer) if err != nil { return err } diff --git a/models/issues/issue_project.go b/models/issues/issue_project.go index 0185244783..3bb0936301 100644 --- a/models/issues/issue_project.go +++ b/models/issues/issue_project.go @@ -64,36 +64,6 @@ func LoadProjectIssueColumnMap(ctx context.Context, projectID, defaultColumnID i return result, nil } -// LoadIssuesFromColumn load issues assigned to this column -func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, opts *IssuesOptions) (IssueList, error) { - issueList, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) { - o.ProjectColumnID = b.ID - o.ProjectID = b.ProjectID - o.SortType = "project-column-sorting" - })) - if err != nil { - return nil, err - } - - if b.Default { - issues, err := Issues(ctx, opts.Copy(func(o *IssuesOptions) { - o.ProjectColumnID = db.NoConditionID - o.ProjectID = b.ProjectID - o.SortType = "project-column-sorting" - })) - if err != nil { - return nil, err - } - issueList = append(issueList, issues...) - } - - if err := issueList.LoadComments(ctx); err != nil { - return nil, err - } - - return issueList, nil -} - // IssueAssignOrRemoveProject changes the project associated with an issue // If newProjectID is 0, the issue is removed from the project func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error { diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go index 0a320ffc56..01a3eb9a2a 100644 --- a/models/issues/issue_update.go +++ b/models/issues/issue_update.go @@ -665,9 +665,9 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u continue } // Normal users must have read access to the referencing issue - perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, user) + perm, err := access_model.GetIndividualUserRepoPermission(ctx, issue.Repo, user) if err != nil { - return nil, fmt.Errorf("GetUserRepoPermission [%d]: %w", user.ID, err) + return nil, fmt.Errorf("GetIndividualUserRepoPermission [%d]: %w", user.ID, err) } if !perm.CanReadIssuesOrPulls(issue.IsPull) { continue diff --git a/models/issues/issue_xref.go b/models/issues/issue_xref.go index f8495929cf..41a867c892 100644 --- a/models/issues/issue_xref.go +++ b/models/issues/issue_xref.go @@ -207,7 +207,7 @@ func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossRefe // Check doer permissions; set action to None if the doer can't change the destination if refIssue.RepoID != ctx.OrigIssue.RepoID || ref.Action != references.XRefActionNone { - perm, err := access_model.GetUserRepoPermission(stdCtx, refIssue.Repo, ctx.Doer) + perm, err := access_model.GetDoerRepoPermission(stdCtx, refIssue.Repo, ctx.Doer) if err != nil { return nil, references.XRefActionNone, err } diff --git a/models/issues/label_test.go b/models/issues/label_test.go index 50393855d9..42f774e750 100644 --- a/models/issues/label_test.go +++ b/models/issues/label_test.go @@ -254,7 +254,7 @@ func TestGetLabelsByIssueID(t *testing.T) { func TestUpdateLabel(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) - // make sure update wont overwrite it + // make sure update won't overwrite it update := &issues_model.Label{ ID: label.ID, Color: "#ffff00", diff --git a/models/issues/pull.go b/models/issues/pull.go index 9f180f9ac9..c07044f301 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "io" - "regexp" "strings" "code.gitea.io/gitea/models/db" @@ -24,6 +23,7 @@ import ( "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + "github.com/dlclark/regexp2" "xorm.io/builder" ) @@ -861,7 +861,7 @@ func GetCodeOwnersFromContent(ctx context.Context, data string) ([]*CodeOwnerRul } type CodeOwnerRule struct { - Rule *regexp.Regexp + Rule *regexp2.Regexp // it supports negative lookahead, does better for end users Negative bool Users []*user_model.User Teams []*org_model.Team @@ -877,7 +877,8 @@ func ParseCodeOwnersLine(ctx context.Context, tokens []string) (*CodeOwnerRule, warnings := make([]string, 0) - rule.Rule, err = regexp.Compile(fmt.Sprintf("^%s$", strings.TrimPrefix(tokens[0], "!"))) + expr := fmt.Sprintf("^%s$", strings.TrimPrefix(tokens[0], "!")) + rule.Rule, err = regexp2.Compile(expr, regexp2.None) if err != nil { warnings = append(warnings, fmt.Sprintf("incorrect codeowner regexp: %s", err)) return nil, warnings diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go index 19d727ecbd..bf8f9b7d91 100644 --- a/models/issues/pull_list.go +++ b/models/issues/pull_list.go @@ -93,7 +93,7 @@ func CanMaintainerWriteToBranch(ctx context.Context, p access_model.Permission, if err != nil { continue } - prPerm, err := access_model.GetUserRepoPermission(ctx, pr.BaseRepo, user) + prPerm, err := access_model.GetIndividualUserRepoPermission(ctx, pr.BaseRepo, user) if err != nil { continue } diff --git a/models/issues/review.go b/models/issues/review.go index 22e2e186d9..af2600876e 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -919,7 +919,7 @@ func CanMarkConversation(ctx context.Context, issue *Issue, doer *user_model.Use return false, nil } if doer.ID != issue.PosterID { - p, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) + p, err := access_model.GetDoerRepoPermission(ctx, issue.Repo, doer) if err != nil { return false, err } diff --git a/models/issues/review_list.go b/models/issues/review_list.go index 86b1a2e76e..d0430cd06d 100644 --- a/models/issues/review_list.go +++ b/models/issues/review_list.go @@ -173,9 +173,9 @@ func GetReviewsByIssueID(ctx context.Context, issueID int64) (latestReviews, mig reviewersMap := make(map[int64][]*Review) // key is reviewer id originalReviewersMap := make(map[int64][]*Review) // key is original author id reviewTeamsMap := make(map[int64][]*Review) // key is reviewer team id - countedReivewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, ReviewTypeComment} + countedReviewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, ReviewTypeComment} for _, review := range reviews { - if review.ReviewerTeamID == 0 && slices.Contains(countedReivewTypes, review.Type) && !review.Dismissed { + if review.ReviewerTeamID == 0 && slices.Contains(countedReviewTypes, review.Type) && !review.Dismissed { if review.OriginalAuthorID != 0 { originalReviewersMap[review.OriginalAuthorID] = append(originalReviewersMap[review.OriginalAuthorID], review) } else { diff --git a/models/migrations/base/db.go b/models/migrations/base/db.go index 3b8f0e00a0..16048c2073 100644 --- a/models/migrations/base/db.go +++ b/models/migrations/base/db.go @@ -402,7 +402,7 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin cols += "DROP COLUMN `" + col + "` CASCADE" } if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil { - return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err) + return fmt.Errorf("drop table `%s` columns %v: %w", tableName, columnNames, err) } case setting.Database.Type.IsMySQL(): // Drop indexes on columns first @@ -430,7 +430,7 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin cols += "DROP COLUMN `" + col + "`" } if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil { - return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err) + return fmt.Errorf("drop table `%s` columns %v: %w", tableName, columnNames, err) } case setting.Database.Type.IsMSSQL(): cols := "" @@ -444,27 +444,27 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin tableName, strings.ReplaceAll(cols, "`", "'")) constraints := make([]string, 0) if err := sess.SQL(sql).Find(&constraints); err != nil { - return fmt.Errorf("Find constraints: %v", err) + return fmt.Errorf("find constraints: %w", err) } for _, constraint := range constraints { if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP CONSTRAINT `%s`", tableName, constraint)); err != nil { - return fmt.Errorf("Drop table `%s` default constraint `%s`: %v", tableName, constraint, err) + return fmt.Errorf("drop table `%s` default constraint `%s`: %w", tableName, constraint, err) } } sql = fmt.Sprintf("SELECT DISTINCT Name FROM sys.indexes INNER JOIN sys.index_columns ON indexes.index_id = index_columns.index_id AND indexes.object_id = index_columns.object_id WHERE indexes.object_id = OBJECT_ID('%[1]s') AND index_columns.column_id IN (SELECT column_id FROM sys.columns WHERE LOWER(name) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))", tableName, strings.ReplaceAll(cols, "`", "'")) constraints = make([]string, 0) if err := sess.SQL(sql).Find(&constraints); err != nil { - return fmt.Errorf("Find constraints: %v", err) + return fmt.Errorf("find constraints: %w", err) } for _, constraint := range constraints { if _, err := sess.Exec(fmt.Sprintf("DROP INDEX `%[2]s` ON `%[1]s`", tableName, constraint)); err != nil { - return fmt.Errorf("Drop index `%[2]s` on `%[1]s`: %v", tableName, constraint, err) + return fmt.Errorf("drop index `%[2]s` on `%[1]s`: %[3]w", tableName, constraint, err) } } if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP COLUMN %s", tableName, cols)); err != nil { - return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err) + return fmt.Errorf("drop table `%s` columns %v: %w", tableName, columnNames, err) } default: log.Fatal("Unrecognized DB") diff --git a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run.yml b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run.yml new file mode 100644 index 0000000000..49f71ce3fc --- /dev/null +++ b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run.yml @@ -0,0 +1,29 @@ +# type ActionRun struct { +# ID int64 `xorm:"pk autoincr"` +# RepoID int64 `xorm:"index"` +# Index int64 +# CommitSHA string `xorm:"commit_sha"` +# Event string +# TriggerEvent string +# EventPayload string `xorm:"LONGTEXT"` +# } +- + id: 990 + repo_id: 100 + index: 7 + commit_sha: merge-sha + event: pull_request + event_payload: '{"pull_request":{"head":{"sha":"sha-shared"}}}' +- + id: 991 + repo_id: 100 + index: 8 + commit_sha: sha-shared + event: push + event_payload: '{"head_commit":{"id":"sha-shared"}}' +- + id: 1991 + repo_id: 100 + index: 9 + commit_sha: sha-other + event: release diff --git a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run_job.yml b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run_job.yml new file mode 100644 index 0000000000..addf5e0682 --- /dev/null +++ b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run_job.yml @@ -0,0 +1,16 @@ +# type ActionRunJob struct { +# ID int64 `xorm:"pk autoincr"` +# RunID int64 `xorm:"index"` +# } +- + id: 997 + run_id: 990 +- + id: 998 + run_id: 990 +- + id: 1997 + run_id: 991 +- + id: 1998 + run_id: 1991 diff --git a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status.yml b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status.yml new file mode 100644 index 0000000000..6be1c7ca48 --- /dev/null +++ b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status.yml @@ -0,0 +1,51 @@ +# type CommitStatus struct { +# ID int64 `xorm:"pk autoincr"` +# RepoID int64 `xorm:"index"` +# SHA string +# TargetURL string +# } +- + id: 10010 + repo_id: 100 + sha: sha-shared + target_url: /testuser/repo1/actions/runs/7/jobs/0 +- + id: 10011 + repo_id: 100 + sha: sha-shared + target_url: /testuser/repo1/actions/runs/7/jobs/1 +- + id: 10012 + repo_id: 100 + sha: sha-shared + target_url: /testuser/repo1/actions/runs/8/jobs/0 +- + id: 10013 + repo_id: 100 + sha: sha-other + target_url: /testuser/repo1/actions/runs/9/jobs/0 +- + id: 10014 + repo_id: 100 + sha: sha-shared + target_url: /otheruser/badrepo/actions/runs/7/jobs/0 +- + id: 10015 + repo_id: 100 + sha: sha-shared + target_url: /testuser/repo1/actions/runs/10/jobs/0 +- + id: 10016 + repo_id: 100 + sha: sha-shared + target_url: /testuser/repo1/actions/runs/7/jobs/3 +- + id: 10017 + repo_id: 100 + sha: sha-shared + target_url: https://ci.example.com/build/123 +- + id: 10018 + repo_id: 100 + sha: sha-shared + target_url: /testuser/repo1/actions/runs/990/jobs/997 diff --git a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status_summary.yml b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status_summary.yml new file mode 100644 index 0000000000..3dd846bb36 --- /dev/null +++ b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status_summary.yml @@ -0,0 +1,19 @@ +# type CommitStatusSummary struct { +# ID int64 `xorm:"pk autoincr"` +# RepoID int64 `xorm:"index"` +# SHA string `xorm:"VARCHAR(64) NOT NULL"` +# State string `xorm:"VARCHAR(7) NOT NULL"` +# TargetURL string +# } +- + id: 10020 + repo_id: 100 + sha: sha-shared + state: pending + target_url: /testuser/repo1/actions/runs/7/jobs/0 +- + id: 10021 + repo_id: 100 + sha: sha-other + state: pending + target_url: /testuser/repo1/actions/runs/9/jobs/0 diff --git a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/repository.yml b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/repository.yml new file mode 100644 index 0000000000..46162e7803 --- /dev/null +++ b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/repository.yml @@ -0,0 +1,9 @@ +# type Repository struct { +# ID int64 `xorm:"pk autoincr"` +# OwnerName string +# Name string +# } +- + id: 100 + owner_name: testuser + name: repo1 diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index c589b2d354..2992e42007 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -400,7 +400,12 @@ func prepareMigrationTasks() []*migration { newMigration(323, "Add support for actions concurrency", v1_26.AddActionsConcurrency), newMigration(324, "Fix closed milestone completeness for milestones with no issues", v1_26.FixClosedMilestoneCompleteness), newMigration(325, "Fix missed repo_id when migrate attachments", v1_26.FixMissedRepoIDWhenMigrateAttachments), - newMigration(326, "Add support for matrix actions evaluation", v1_26.AddMatrixEvaluationColumnsToActionRunJob), + newMigration(326, "Partially migrate commit status target URL to use run ID and job ID", v1_26.FixCommitStatusTargetURLToUseRunAndJobID), + newMigration(327, "Add disabled state to action runners", v1_26.AddDisabledToActionRunner), + newMigration(328, "Add TokenPermissions column to ActionRunJob", v1_26.AddTokenPermissionsToActionRunJob), + newMigration(329, "Add unique constraint for user badge", v1_26.AddUniqueIndexForUserBadge), + newMigration(330, "Add name column to webhook", v1_26.AddNameToWebhook), + newMigration(331, "Add support for matrix actions evaluation", v1_26.AddMatrixEvaluationColumnsToActionRunJob), } return preparedMigrations } diff --git a/models/migrations/v1_26/v326_test.go b/models/migrations/v1_26/v326_test.go new file mode 100644 index 0000000000..b92eed35f6 --- /dev/null +++ b/models/migrations/v1_26/v326_test.go @@ -0,0 +1,113 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_26 + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/git" + _ "code.gitea.io/gitea/models/repo" + + "github.com/stretchr/testify/require" + "xorm.io/xorm" +) + +func Test_FixCommitStatusTargetURLToUseRunAndJobID(t *testing.T) { + defer test.MockVariableValue(&setting.AppSubURL, "")() + + type Repository struct { + ID int64 `xorm:"pk autoincr"` + OwnerName string + Name string + } + + type ActionRun struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"index"` + Index int64 + CommitSHA string `xorm:"commit_sha"` + Event string + TriggerEvent string + EventPayload string `xorm:"LONGTEXT"` + } + + type ActionRunJob struct { + ID int64 `xorm:"pk autoincr"` + RunID int64 `xorm:"index"` + } + + type CommitStatus struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"index"` + SHA string + TargetURL string + } + + type CommitStatusSummary struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"index"` + SHA string `xorm:"VARCHAR(64) NOT NULL"` + State string `xorm:"VARCHAR(7) NOT NULL"` + TargetURL string + } + + x, deferable := base.PrepareTestEnv(t, 0, + new(Repository), + new(ActionRun), + new(ActionRunJob), + new(CommitStatus), + new(CommitStatusSummary), + ) + defer deferable() + + require.NoError(t, FixCommitStatusTargetURLToUseRunAndJobID(x)) + + cases := []struct { + table string + id int64 + want string + }{ + // Legacy URLs for runs whose resolved run IDs are below the threshold should be rewritten. + {table: "commit_status", id: 10010, want: "/testuser/repo1/actions/runs/990/jobs/997"}, + {table: "commit_status", id: 10011, want: "/testuser/repo1/actions/runs/990/jobs/998"}, + {table: "commit_status", id: 10012, want: "/testuser/repo1/actions/runs/991/jobs/1997"}, + + // Runs whose resolved IDs are above the threshold are intentionally left unchanged. + {table: "commit_status", id: 10013, want: "/testuser/repo1/actions/runs/9/jobs/0"}, + + // URLs that do not resolve cleanly as legacy Actions URLs should remain untouched. + {table: "commit_status", id: 10014, want: "/otheruser/badrepo/actions/runs/7/jobs/0"}, + {table: "commit_status", id: 10015, want: "/testuser/repo1/actions/runs/10/jobs/0"}, + {table: "commit_status", id: 10016, want: "/testuser/repo1/actions/runs/7/jobs/3"}, + {table: "commit_status", id: 10017, want: "https://ci.example.com/build/123"}, + + // Already ID-based URLs are valid inputs and should not be rewritten again. + {table: "commit_status", id: 10018, want: "/testuser/repo1/actions/runs/990/jobs/997"}, + + // The same rewrite rules apply to commit_status_summary rows. + {table: "commit_status_summary", id: 10020, want: "/testuser/repo1/actions/runs/990/jobs/997"}, + {table: "commit_status_summary", id: 10021, want: "/testuser/repo1/actions/runs/9/jobs/0"}, + } + + for _, tc := range cases { + assertTargetURL(t, x, tc.table, tc.id, tc.want) + } +} + +func assertTargetURL(t *testing.T, x *xorm.Engine, table string, id int64, want string) { + t.Helper() + + var row struct { + TargetURL string + } + has, err := x.Table(table).Where("id=?", id).Cols("target_url").Get(&row) + require.NoError(t, err) + require.Truef(t, has, "row not found: table=%s id=%d", table, id) + require.Equal(t, want, row.TargetURL) +} diff --git a/models/migrations/v1_26/v327.go b/models/migrations/v1_26/v327.go new file mode 100644 index 0000000000..51af567650 --- /dev/null +++ b/models/migrations/v1_26/v327.go @@ -0,0 +1,17 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_26 + +import "xorm.io/xorm" + +func AddDisabledToActionRunner(x *xorm.Engine) error { + type ActionRunner struct { + IsDisabled bool `xorm:"is_disabled NOT NULL DEFAULT false"` + } + + _, err := x.SyncWithOptions(xorm.SyncOptions{ + IgnoreDropIndices: true, + }, new(ActionRunner)) + return err +} diff --git a/models/migrations/v1_26/v327_test.go b/models/migrations/v1_26/v327_test.go new file mode 100644 index 0000000000..971707be4f --- /dev/null +++ b/models/migrations/v1_26/v327_test.go @@ -0,0 +1,33 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_26 + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" + + "github.com/stretchr/testify/require" +) + +func Test_AddDisabledToActionRunner(t *testing.T) { + type ActionRunner struct { + ID int64 `xorm:"pk autoincr"` + Name string + } + + x, deferable := base.PrepareTestEnv(t, 0, new(ActionRunner)) + defer deferable() + + _, err := x.Insert(&ActionRunner{Name: "runner"}) + require.NoError(t, err) + + require.NoError(t, AddDisabledToActionRunner(x)) + + var isDisabled bool + has, err := x.SQL("SELECT is_disabled FROM action_runner WHERE id = ?", 1).Get(&isDisabled) + require.NoError(t, err) + require.True(t, has) + require.False(t, isDisabled) +} diff --git a/models/migrations/v1_26/v328.go b/models/migrations/v1_26/v328.go new file mode 100644 index 0000000000..8104730528 --- /dev/null +++ b/models/migrations/v1_26/v328.go @@ -0,0 +1,16 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_26 + +import ( + "xorm.io/xorm" +) + +func AddTokenPermissionsToActionRunJob(x *xorm.Engine) error { + type ActionRunJob struct { + TokenPermissions string `xorm:"JSON TEXT"` + } + _, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(ActionRunJob)) + return err +} diff --git a/models/migrations/v1_26/v329.go b/models/migrations/v1_26/v329.go new file mode 100644 index 0000000000..1afd155f05 --- /dev/null +++ b/models/migrations/v1_26/v329.go @@ -0,0 +1,62 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_26 + +import ( + "fmt" + + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +type UserBadge struct { //revive:disable-line:exported + ID int64 `xorm:"pk autoincr"` + BadgeID int64 + UserID int64 +} + +// TableIndices implements xorm's TableIndices interface +func (n *UserBadge) TableIndices() []*schemas.Index { + indices := make([]*schemas.Index, 0, 1) + ubUnique := schemas.NewIndex("unique_user_badge", schemas.UniqueType) + ubUnique.AddColumn("user_id", "badge_id") + indices = append(indices, ubUnique) + return indices +} + +// AddUniqueIndexForUserBadge adds a compound unique indexes for user badge table +// and it replaces an old index on user_id +func AddUniqueIndexForUserBadge(x *xorm.Engine) error { + // remove possible duplicated records in table user_badge + type result struct { + UserID int64 + BadgeID int64 + Cnt int + } + var results []result + if err := x.Select("user_id, badge_id, count(*) as cnt"). + Table("user_badge"). + GroupBy("user_id, badge_id"). + Having("count(*) > 1"). + Find(&results); err != nil { + return err + } + for _, r := range results { + if x.Dialect().URI().DBType == schemas.MSSQL { + if _, err := x.Exec(fmt.Sprintf("delete from user_badge where id in (SELECT top %d id FROM user_badge WHERE user_id = ? and badge_id = ?)", r.Cnt-1), r.UserID, r.BadgeID); err != nil { + return err + } + } else { + var ids []int64 + if err := x.SQL("SELECT id FROM user_badge WHERE user_id = ? and badge_id = ? limit ?", r.UserID, r.BadgeID, r.Cnt-1).Find(&ids); err != nil { + return err + } + if _, err := x.Table("user_badge").In("id", ids).Delete(); err != nil { + return err + } + } + } + + return x.Sync(new(UserBadge)) +} diff --git a/models/migrations/v1_26/v329_test.go b/models/migrations/v1_26/v329_test.go new file mode 100644 index 0000000000..cab8e79906 --- /dev/null +++ b/models/migrations/v1_26/v329_test.go @@ -0,0 +1,85 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_26 + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" + + "github.com/stretchr/testify/assert" +) + +type UserBadgeBefore struct { + ID int64 `xorm:"pk autoincr"` + BadgeID int64 + UserID int64 `xorm:"INDEX"` +} + +func (UserBadgeBefore) TableName() string { + return "user_badge" +} + +func Test_AddUniqueIndexForUserBadge(t *testing.T) { + x, deferable := base.PrepareTestEnv(t, 0, new(UserBadgeBefore)) + defer deferable() + if x == nil || t.Failed() { + return + } + + testData := []*UserBadgeBefore{ + {UserID: 1, BadgeID: 1}, + {UserID: 1, BadgeID: 1}, // duplicate + {UserID: 2, BadgeID: 1}, + {UserID: 1, BadgeID: 2}, + {UserID: 3, BadgeID: 3}, + {UserID: 3, BadgeID: 3}, // duplicate + } + + for _, data := range testData { + _, err := x.Insert(data) + assert.NoError(t, err) + } + + // check that we have duplicates + count, err := x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{}) + assert.NoError(t, err) + assert.Equal(t, int64(2), count) + + count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{}) + assert.NoError(t, err) + assert.Equal(t, int64(2), count) + + totalCount, err := x.Count(&UserBadgeBefore{}) + assert.NoError(t, err) + assert.Equal(t, int64(6), totalCount) + + // run the migration + if err := AddUniqueIndexForUserBadge(x); err != nil { + assert.NoError(t, err) + return + } + + // verify the duplicates were removed + count, err = x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{}) + assert.NoError(t, err) + assert.Equal(t, int64(1), count) + + count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{}) + assert.NoError(t, err) + assert.Equal(t, int64(1), count) + + // check total count + totalCount, err = x.Count(&UserBadgeBefore{}) + assert.NoError(t, err) + assert.Equal(t, int64(4), totalCount) + + // fail to insert a duplicate + _, err = x.Insert(&UserBadge{UserID: 1, BadgeID: 1}) + assert.Error(t, err) + + // succeed adding a non-duplicate + _, err = x.Insert(&UserBadge{UserID: 4, BadgeID: 1}) + assert.NoError(t, err) +} diff --git a/models/migrations/v1_26/v330.go b/models/migrations/v1_26/v330.go new file mode 100644 index 0000000000..9f77331090 --- /dev/null +++ b/models/migrations/v1_26/v330.go @@ -0,0 +1,16 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_26 + +import ( + "xorm.io/xorm" +) + +func AddNameToWebhook(x *xorm.Engine) error { + type Webhook struct { + Name string `xorm:"VARCHAR(255) NOT NULL DEFAULT ''"` + } + _, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(Webhook)) + return err +} diff --git a/models/migrations/v1_26/v331.go b/models/migrations/v1_26/v331.go new file mode 100644 index 0000000000..76532e2f85 --- /dev/null +++ b/models/migrations/v1_26/v331.go @@ -0,0 +1,315 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_26 + +import ( + "errors" + "fmt" + "net/url" + "strconv" + "strings" + + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + webhook_module "code.gitea.io/gitea/modules/webhook" + + "xorm.io/xorm" +) + +const ( + actionsRunPath = "/actions/runs/" + + // Only commit status target URLs whose resolved run ID is smaller than this threshold are rewritten by this partial migration. + // The fixed value 1000 is a conservative cutoff chosen to cover the smaller legacy run indexes that are most likely to be confused with ID-based URLs at runtime. + // Larger legacy {run} or {job} numbers are usually easier to disambiguate. For example: + // * /actions/runs/1200/jobs/1420 is most likely an ID-based URL, because a run should not contain more than 256 jobs. + // * /actions/runs/1500/jobs/3 is most likely an index-based URL, because a job ID cannot be smaller than its run ID. + // But URLs with small numbers, such as /actions/runs/5/jobs/6, are much harder to distinguish reliably. + // This migration therefore prioritizes rewriting target URLs for runs in that lower range. + legacyURLIDThreshold int64 = 1000 +) + +type migrationRepository struct { + ID int64 + OwnerName string + Name string +} + +type migrationActionRun struct { + ID int64 + RepoID int64 + Index int64 + CommitSHA string `xorm:"commit_sha"` + Event webhook_module.HookEventType + TriggerEvent string + EventPayload string +} + +type migrationActionRunJob struct { + ID int64 + RunID int64 +} + +type migrationCommitStatus struct { + ID int64 + RepoID int64 + TargetURL string +} + +type commitSHAAndRuns struct { + commitSHA string + runs map[int64]*migrationActionRun +} + +// FixCommitStatusTargetURLToUseRunAndJobID partially migrates legacy Actions +// commit status target URLs to the new run/job ID-based form. +// +// Only rows whose resolved run ID is below legacyURLIDThreshold are rewritten. +// This is because smaller legacy run indexes are more likely to collide with run ID URLs during runtime resolution, +// so this migration prioritizes that lower range and leaves the remaining legacy target URLs to the web compatibility logic. +func FixCommitStatusTargetURLToUseRunAndJobID(x *xorm.Engine) error { + jobsByRunIDCache := make(map[int64][]int64) + repoLinkCache := make(map[int64]string) + groups, err := loadLegacyMigrationRunGroups(x) + if err != nil { + return err + } + + for repoID, groupsBySHA := range groups { + for _, group := range groupsBySHA { + if err := migrateCommitStatusTargetURLForGroup(x, "commit_status", repoID, group.commitSHA, group.runs, jobsByRunIDCache, repoLinkCache); err != nil { + return err + } + if err := migrateCommitStatusTargetURLForGroup(x, "commit_status_summary", repoID, group.commitSHA, group.runs, jobsByRunIDCache, repoLinkCache); err != nil { + return err + } + } + } + return nil +} + +func loadLegacyMigrationRunGroups(x *xorm.Engine) (map[int64]map[string]*commitSHAAndRuns, error) { + var runs []migrationActionRun + if err := x.Table("action_run"). + Where("id < ?", legacyURLIDThreshold). + Cols("id", "repo_id", "`index`", "commit_sha", "event", "trigger_event", "event_payload"). + Find(&runs); err != nil { + return nil, fmt.Errorf("query action_run: %w", err) + } + + groups := make(map[int64]map[string]*commitSHAAndRuns) + for i := range runs { + run := runs[i] + commitID, err := getCommitStatusCommitID(&run) + if err != nil { + log.Warn("skip action_run id=%d when resolving commit status commit SHA: %v", run.ID, err) + continue + } + if commitID == "" { + // empty commitID means the run didn't create any commit status records, just skip + continue + } + if groups[run.RepoID] == nil { + groups[run.RepoID] = make(map[string]*commitSHAAndRuns) + } + if groups[run.RepoID][commitID] == nil { + groups[run.RepoID][commitID] = &commitSHAAndRuns{ + commitSHA: commitID, + runs: make(map[int64]*migrationActionRun), + } + } + groups[run.RepoID][commitID].runs[run.Index] = &run + } + return groups, nil +} + +func migrateCommitStatusTargetURLForGroup( + x *xorm.Engine, + table string, + repoID int64, + sha string, + runs map[int64]*migrationActionRun, + jobsByRunIDCache map[int64][]int64, + repoLinkCache map[int64]string, +) error { + var rows []migrationCommitStatus + if err := x.Table(table). + Where("repo_id = ?", repoID). + And("sha = ?", sha). + Cols("id", "repo_id", "target_url"). + Find(&rows); err != nil { + return fmt.Errorf("query %s for repo_id=%d sha=%s: %w", table, repoID, sha, err) + } + + for _, row := range rows { + repoLink, err := getRepoLinkCached(x, repoLinkCache, row.RepoID) + if err != nil || repoLink == "" { + if err != nil { + log.Warn("convert %s id=%d getRepoLinkCached: %v", table, row.ID, err) + } else { + log.Warn("convert %s id=%d: repo=%d not found", table, row.ID, row.RepoID) + } + continue + } + + runNum, jobNum, ok := parseTargetURL(row.TargetURL, repoLink) + if !ok { + continue + } + + run, ok := runs[runNum] + if !ok { + continue + } + + jobID, ok, err := getJobIDByIndexCached(x, jobsByRunIDCache, run.ID, jobNum) + if err != nil || !ok { + if err != nil { + log.Warn("convert %s id=%d getJobIDByIndexCached: %v", table, row.ID, err) + } else { + log.Warn("convert %s id=%d: job not found for run_id=%d job_index=%d", table, row.ID, run.ID, jobNum) + } + continue + } + + oldURL := row.TargetURL + newURL := fmt.Sprintf("%s%s%d/jobs/%d", repoLink, actionsRunPath, run.ID, jobID) + if oldURL == newURL { + continue + } + + if _, err := x.Table(table).ID(row.ID).Cols("target_url").Update(&migrationCommitStatus{TargetURL: newURL}); err != nil { + return fmt.Errorf("update %s id=%d target_url from %s to %s: %w", table, row.ID, oldURL, newURL, err) + } + } + return nil +} + +func getRepoLinkCached(x *xorm.Engine, cache map[int64]string, repoID int64) (string, error) { + if link, ok := cache[repoID]; ok { + return link, nil + } + repo := &migrationRepository{} + has, err := x.Table("repository").Where("id=?", repoID).Get(repo) + if err != nil { + return "", err + } + if !has { + cache[repoID] = "" + return "", nil + } + link := setting.AppSubURL + "/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name) + cache[repoID] = link + return link, nil +} + +func getJobIDByIndexCached(x *xorm.Engine, cache map[int64][]int64, runID, jobIndex int64) (int64, bool, error) { + jobIDs, ok := cache[runID] + if !ok { + var jobs []migrationActionRunJob + if err := x.Table("action_run_job").Where("run_id=?", runID).Asc("id").Cols("id").Find(&jobs); err != nil { + return 0, false, err + } + jobIDs = make([]int64, 0, len(jobs)) + for _, job := range jobs { + jobIDs = append(jobIDs, job.ID) + } + cache[runID] = jobIDs + } + if jobIndex < 0 || jobIndex >= int64(len(jobIDs)) { + return 0, false, nil + } + return jobIDs[jobIndex], true, nil +} + +func parseTargetURL(targetURL, repoLink string) (runNum, jobNum int64, ok bool) { + prefix := repoLink + actionsRunPath + if !strings.HasPrefix(targetURL, prefix) { + return 0, 0, false + } + rest := targetURL[len(prefix):] + + parts := strings.Split(rest, "/") + if len(parts) == 3 && parts[1] == "jobs" { + runNum, err1 := strconv.ParseInt(parts[0], 10, 64) + jobNum, err2 := strconv.ParseInt(parts[2], 10, 64) + if err1 != nil || err2 != nil { + return 0, 0, false + } + return runNum, jobNum, true + } + + return 0, 0, false +} + +func getCommitStatusCommitID(run *migrationActionRun) (string, error) { + switch run.Event { + case webhook_module.HookEventPush: + payload, err := getPushEventPayload(run) + if err != nil { + return "", fmt.Errorf("getPushEventPayload: %w", err) + } + if payload.HeadCommit == nil { + return "", errors.New("head commit is missing in event payload") + } + return payload.HeadCommit.ID, nil + case webhook_module.HookEventPullRequest, + webhook_module.HookEventPullRequestSync, + webhook_module.HookEventPullRequestAssign, + webhook_module.HookEventPullRequestLabel, + webhook_module.HookEventPullRequestReviewRequest, + webhook_module.HookEventPullRequestMilestone: + payload, err := getPullRequestEventPayload(run) + if err != nil { + return "", fmt.Errorf("getPullRequestEventPayload: %w", err) + } + if payload.PullRequest == nil { + return "", errors.New("pull request is missing in event payload") + } else if payload.PullRequest.Head == nil { + return "", errors.New("head of pull request is missing in event payload") + } + return payload.PullRequest.Head.Sha, nil + case webhook_module.HookEventPullRequestReviewApproved, + webhook_module.HookEventPullRequestReviewRejected, + webhook_module.HookEventPullRequestReviewComment: + payload, err := getPullRequestEventPayload(run) + if err != nil { + return "", fmt.Errorf("getPullRequestEventPayload: %w", err) + } + if payload.PullRequest == nil { + return "", errors.New("pull request is missing in event payload") + } else if payload.PullRequest.Head == nil { + return "", errors.New("head of pull request is missing in event payload") + } + return payload.PullRequest.Head.Sha, nil + case webhook_module.HookEventRelease: + return run.CommitSHA, nil + default: + return "", nil + } +} + +func getPushEventPayload(run *migrationActionRun) (*api.PushPayload, error) { + if run.Event != webhook_module.HookEventPush { + return nil, fmt.Errorf("event %s is not a push event", run.Event) + } + var payload api.PushPayload + if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil { + return nil, err + } + return &payload, nil +} + +func getPullRequestEventPayload(run *migrationActionRun) (*api.PullRequestPayload, error) { + if !run.Event.IsPullRequest() && !run.Event.IsPullRequestReview() { + return nil, fmt.Errorf("event %s is not a pull request event", run.Event) + } + var payload api.PullRequestPayload + if err := json.Unmarshal([]byte(run.EventPayload), &payload); err != nil { + return nil, err + } + return &payload, nil +} diff --git a/models/organization/org.go b/models/organization/org.go index b4d28f5405..10a2c330e5 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -178,7 +178,7 @@ func (org *Organization) HomeLink() string { return org.AsUser().HomeLink() } -// FindOrgMembersOpts represensts find org members conditions +// FindOrgMembersOpts represents find org members conditions type FindOrgMembersOpts struct { db.ListOptions Doer *user_model.User diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go index ea0e0d5e73..2ef27051ee 100644 --- a/models/packages/descriptor.go +++ b/models/packages/descriptor.go @@ -9,6 +9,7 @@ import ( "fmt" "net/url" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/cache" @@ -53,8 +54,11 @@ func (l PackagePropertyList) GetByName(name string) string { // PackageDescriptor describes a package type PackageDescriptor struct { - Package *Package - Owner *user_model.User + // basic package info + Package *Package + Owner *user_model.User + + // package version info Repository *repo_model.Repository Version *PackageVersion SemVer *version.Version @@ -77,6 +81,11 @@ func (pd *PackageDescriptor) PackageWebLink() string { return fmt.Sprintf("%s/-/packages/%s/%s", pd.Owner.HomeLink(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName)) } +// PackageSettingsLink returns the relative package settings link +func (pd *PackageDescriptor) PackageSettingsLink() string { + return fmt.Sprintf("%s/-/packages/settings/%s/%s", pd.Owner.HomeLink(), string(pd.Package.Type), url.PathEscape(pd.Package.LowerName)) +} + // 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)) @@ -203,6 +212,8 @@ func GetPackageDescriptorWithCache(ctx context.Context, pv *PackageVersion, c *c metadata = &rubygems.Metadata{} case TypeSwift: metadata = &swift.Metadata{} + case TypeTerraformState: + // terraform packages have no metadata case TypeVagrant: metadata = &vagrant.Metadata{} default: @@ -267,6 +278,15 @@ func GetPackageDescriptors(ctx context.Context, pvs []*PackageVersion) ([]*Packa return getPackageDescriptors(ctx, pvs, cache.NewEphemeralCache()) } +// GetAllPackageDescriptors gets all package descriptors for a package +func GetAllPackageDescriptors(ctx context.Context, p *Package) ([]*PackageDescriptor, error) { + pvs := make([]*PackageVersion, 0, 10) + if err := db.GetEngine(ctx).Where("package_id = ?", p.ID).Find(&pvs); err != nil { + return nil, err + } + return getPackageDescriptors(ctx, pvs, cache.NewEphemeralCache()) +} + func getPackageDescriptors(ctx context.Context, pvs []*PackageVersion, c *cache.EphemeralCache) ([]*PackageDescriptor, error) { pds := make([]*PackageDescriptor, 0, len(pvs)) for _, pv := range pvs { diff --git a/models/packages/package.go b/models/packages/package.go index 38d1cdcf66..17e5d4eee3 100644 --- a/models/packages/package.go +++ b/models/packages/package.go @@ -30,28 +30,29 @@ type Type string // List of supported packages const ( - TypeAlpine Type = "alpine" - TypeArch Type = "arch" - TypeCargo Type = "cargo" - TypeChef Type = "chef" - TypeComposer Type = "composer" - TypeConan Type = "conan" - TypeConda Type = "conda" - TypeContainer Type = "container" - TypeCran Type = "cran" - TypeDebian Type = "debian" - TypeGeneric Type = "generic" - TypeGo Type = "go" - TypeHelm Type = "helm" - TypeMaven Type = "maven" - TypeNpm Type = "npm" - TypeNuGet Type = "nuget" - TypePub Type = "pub" - TypePyPI Type = "pypi" - TypeRpm Type = "rpm" - TypeRubyGems Type = "rubygems" - TypeSwift Type = "swift" - TypeVagrant Type = "vagrant" + TypeAlpine Type = "alpine" + TypeArch Type = "arch" + TypeCargo Type = "cargo" + TypeChef Type = "chef" + TypeComposer Type = "composer" + TypeConan Type = "conan" + TypeConda Type = "conda" + TypeContainer Type = "container" + TypeCran Type = "cran" + TypeDebian Type = "debian" + TypeGeneric Type = "generic" + TypeGo Type = "go" + TypeHelm Type = "helm" + TypeMaven Type = "maven" + TypeNpm Type = "npm" + TypeNuGet Type = "nuget" + TypePub Type = "pub" + TypePyPI Type = "pypi" + TypeRpm Type = "rpm" + TypeRubyGems Type = "rubygems" + TypeSwift Type = "swift" + TypeTerraformState Type = "terraform" + TypeVagrant Type = "vagrant" ) var TypeList = []Type{ @@ -76,6 +77,7 @@ var TypeList = []Type{ TypeRpm, TypeRubyGems, TypeSwift, + TypeTerraformState, TypeVagrant, } @@ -124,6 +126,8 @@ func (pt Type) Name() string { return "RubyGems" case TypeSwift: return "Swift" + case TypeTerraformState: + return "Terraform State" case TypeVagrant: return "Vagrant" } @@ -175,6 +179,8 @@ func (pt Type) SVGName() string { return "gitea-rubygems" case TypeSwift: return "gitea-swift" + case TypeTerraformState: + return "gitea-terraform" case TypeVagrant: return "gitea-vagrant" } diff --git a/models/packages/package_file.go b/models/packages/package_file.go index bf877485d6..69401eee3e 100644 --- a/models/packages/package_file.go +++ b/models/packages/package_file.go @@ -115,6 +115,20 @@ func DeleteFileByID(ctx context.Context, fileID int64) error { return err } +// DeleteFilesByPackageID deletes all files of a specific package +// Versions must not be deleted prior to this call +func DeleteFilesByPackageID(ctx context.Context, packageID int64) error { + deleteStmt := builder.Delete(builder.In("version_id", builder.Select("package_version.id").From("package_version").Where(builder.Eq{"package_id": packageID}))).From("package_file") + _, err := db.GetEngine(ctx).Exec(deleteStmt) + return err +} + +// DeleteFilesByVersionID deletes all files of a specific version +func DeleteFilesByVersionID(ctx context.Context, versionID int64) error { + _, err := db.GetEngine(ctx).Where("version_id = ?", versionID).Delete(&PackageFile{}) + return err +} + func UpdateFile(ctx context.Context, pf *PackageFile, cols []string) error { _, err := db.GetEngine(ctx).ID(pf.ID).Cols(cols...).Update(pf) return err diff --git a/models/packages/package_property.go b/models/packages/package_property.go index acc05d8d5a..c297fd8901 100644 --- a/models/packages/package_property.go +++ b/models/packages/package_property.go @@ -5,6 +5,7 @@ package packages import ( "context" + "errors" "code.gitea.io/gitea/models/db" @@ -86,6 +87,46 @@ func DeleteAllProperties(ctx context.Context, refType PropertyType, refID int64) return err } +// DeletePropertiesByPackageID deletes properties of a typed linked to the package +// Use to avoid for loops in mass deletion of properties +func DeletePropertiesByPackageID(ctx context.Context, refType PropertyType, packageID int64) error { + var deleteStmt *builder.Builder + + switch refType { + case PropertyTypeFile: + deleteStmt = builder.Delete( + // Delete all properties that are attached to a file and are in ids from a subquery + // which returns ids from the package_file table joined on package_version to link it with package id + builder.Eq{"ref_type": PropertyTypeFile}, builder.In("ref_id", + builder.Select("package_file.id").From("package_file"). + LeftJoin("package_version", "package_file.version_id = package_version.id"). + Where(builder.Eq{"package_version.package_id": packageID}))).From("package_property") + case PropertyTypeVersion: + // Delete all properties that are attached to a version and are in ids from subquery to the package_version filtered by package id + deleteStmt = builder.Delete( + builder.Eq{"ref_type": PropertyTypeVersion}, builder.In("ref_id", + builder.Select("package_version.id").From("package_version"). + Where(builder.Eq{"package_version.package_id": packageID}))).From("package_property") + case PropertyTypePackage: + // Delete all properties that are attached to a package and their reference links to the given package ID + deleteStmt = builder.Delete( + builder.Eq{"ref_type": PropertyTypePackage}, builder.Eq{"ref_id": packageID}). + From("package_property") + default: + return errors.New("invalid ref type") + } + + _, err := db.GetEngine(ctx).Exec(deleteStmt) + return err +} + +// DeleteFilePropertiesByVersionID deletes all file properties linked to specific version +func DeleteFilePropertiesByVersionID(ctx context.Context, versionID int64) error { + deleteStmt := builder.Delete(builder.Eq{"ref_type": PropertyTypeFile}, builder.In("ref_id", builder.Select("id").From("package_file").Where(builder.Eq{"version_id": versionID}))).From("package_property") + _, err := db.GetEngine(ctx).Exec(deleteStmt) + return err +} + // DeletePropertyByID deletes a property func DeletePropertyByID(ctx context.Context, propertyID int64) error { _, err := db.GetEngine(ctx).ID(propertyID).Delete(&PackageProperty{}) diff --git a/models/packages/package_version.go b/models/packages/package_version.go index 0a478c0323..3e0e1899ea 100644 --- a/models/packages/package_version.go +++ b/models/packages/package_version.go @@ -157,6 +157,12 @@ func DeleteVersionByID(ctx context.Context, versionID int64) error { return err } +// DeleteVersionsByPackageID deletes all versions of a specific package +func DeleteVersionsByPackageID(ctx context.Context, packageID int64) error { + _, err := db.GetEngine(ctx).Where(builder.Eq{"package_id": packageID}).Delete(&PackageVersion{}) + return err +} + // HasVersionFileReferences checks if there are associated files func HasVersionFileReferences(ctx context.Context, versionID int64) (bool, error) { return db.GetEngine(ctx).Get(&PackageFile{ diff --git a/models/perm/access/access.go b/models/perm/access/access.go index 6433c4675c..acc34c434e 100644 --- a/models/perm/access/access.go +++ b/models/perm/access/access.go @@ -91,43 +91,80 @@ func updateUserAccess(accessMap map[int64]*userAccess, user *user_model.User, mo } } -// FIXME: do cross-comparison so reduce deletions and additions to the minimum? +// refreshAccesses updates the repository's access records in the database by comparing the provided accessMap +// with existing records. It minimizes DB operations by performing selective inserts, updates, and deletes +// instead of removing all existing records and re-adding them. func refreshAccesses(ctx context.Context, repo *repo_model.Repository, accessMap map[int64]*userAccess) (err error) { - minMode := perm.AccessModeRead + minModeToKeep := perm.AccessModeRead if err := repo.LoadOwner(ctx); err != nil { return fmt.Errorf("LoadOwner: %w", err) } - // If the repo isn't private and isn't owned by a organization, + // If the repo isn't private and isn't owned by an organization, // increase the minMode to Write. if !repo.IsPrivate && !repo.Owner.IsOrganization() { - minMode = perm.AccessModeWrite + minModeToKeep = perm.AccessModeWrite } - newAccesses := make([]Access, 0, len(accessMap)) + // Query existing accesses for cross-comparison + var existingAccesses []Access + if err := db.GetEngine(ctx).Where(builder.Eq{"repo_id": repo.ID}).Find(&existingAccesses); err != nil { + return fmt.Errorf("find existing accesses: %w", err) + } + existingMap := make(map[int64]perm.AccessMode, len(existingAccesses)) + for _, a := range existingAccesses { + existingMap[a.UserID] = a.Mode + } + + var toDelete []int64 + var toInsert, toUpdate []Access + + // Determine changes for userID, ua := range accessMap { - if ua.Mode < minMode && !ua.User.IsRestricted { - continue + if ua.Mode < minModeToKeep && !ua.User.IsRestricted { + // No explicit access record needed (handled by default permissions, e.g., public repo access) + if _, exists := existingMap[userID]; exists { + toDelete = append(toDelete, userID) + } + } else { + desiredMode := ua.Mode + if existingMode, exists := existingMap[userID]; exists { + if existingMode != desiredMode { + toUpdate = append(toUpdate, Access{UserID: userID, RepoID: repo.ID, Mode: desiredMode}) + } + } else { + toInsert = append(toInsert, Access{UserID: userID, RepoID: repo.ID, Mode: desiredMode}) + } } - - newAccesses = append(newAccesses, Access{ - UserID: userID, - RepoID: repo.ID, - Mode: ua.Mode, - }) + delete(existingMap, userID) } - // Delete old accesses and insert new ones for repository. - if _, err = db.DeleteByBean(ctx, &Access{RepoID: repo.ID}); err != nil { - return fmt.Errorf("delete old accesses: %w", err) - } - if len(newAccesses) == 0 { - return nil + // Remaining in existingMap should be deleted + for userID := range existingMap { + toDelete = append(toDelete, userID) } - if err = db.Insert(ctx, newAccesses); err != nil { - return fmt.Errorf("insert new accesses: %w", err) + // Execute deletions + if len(toDelete) > 0 { + if _, err = db.GetEngine(ctx).In("user_id", toDelete).And("repo_id = ?", repo.ID).Delete(&Access{}); err != nil { + return fmt.Errorf("delete accesses: %w", err) + } } + + // Execute updates + for _, u := range toUpdate { + if _, err = db.GetEngine(ctx).Where("user_id = ? AND repo_id = ?", u.UserID, repo.ID).Cols("mode").Update(&Access{Mode: u.Mode}); err != nil { + return fmt.Errorf("update access for user %d: %w", u.UserID, err) + } + } + + // Execute insertions + if len(toInsert) > 0 { + if err = db.Insert(ctx, toInsert); err != nil { + return fmt.Errorf("insert new accesses: %w", err) + } + } + return nil } diff --git a/models/perm/access/access_test.go b/models/perm/access/access_test.go index 15d18b368c..148a02efa3 100644 --- a/models/perm/access/access_test.go +++ b/models/perm/access/access_test.go @@ -15,11 +15,20 @@ import ( "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestAccessLevel(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) +func TestAccess(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + t.Run("AccessLevel", testAccessLevel) + t.Run("HasAccess", testHasAccess) + t.Run("RecalculateAccesses", testRecalculateAccesses) + t.Run("RecalculateAccesses2", testRecalculateAccesses2) + t.Run("RecalculateAccessesUpdateMode", testRecalculateAccessesUpdateMode) + t.Run("RecalculateAccessesRemoveAccess", testRecalculateAccessesRemoveAccess) +} +func testAccessLevel(t *testing.T) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}) @@ -75,9 +84,7 @@ func TestAccessLevel(t *testing.T) { assert.Equal(t, perm_model.AccessModeRead, level) } -func TestHasAccess(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - +func testHasAccess(t *testing.T) { user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) // A public repository owned by User 2 @@ -101,9 +108,8 @@ func TestHasAccess(t *testing.T) { assert.NoError(t, err) } -func TestRepository_RecalculateAccesses(t *testing.T) { +func testRecalculateAccesses(t *testing.T) { // test with organization repo - assert.NoError(t, unittest.PrepareTestDatabase()) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) assert.NoError(t, repo1.LoadOwner(t.Context())) @@ -118,9 +124,8 @@ func TestRepository_RecalculateAccesses(t *testing.T) { assert.Equal(t, perm_model.AccessModeOwner, access.Mode) } -func TestRepository_RecalculateAccesses2(t *testing.T) { +func testRecalculateAccesses2(t *testing.T) { // test with non-organization repo - assert.NoError(t, unittest.PrepareTestDatabase()) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) assert.NoError(t, repo1.LoadOwner(t.Context())) @@ -132,3 +137,67 @@ func TestRepository_RecalculateAccesses2(t *testing.T) { assert.NoError(t, err) assert.False(t, has) } + +func testRecalculateAccessesUpdateMode(t *testing.T) { + // Test the update path in refreshAccesses optimization + // Scenario: User's access mode changes from Read to Write + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + assert.NoError(t, repo.LoadOwner(t.Context())) + + // Verify initial access mode + _ = db.Insert(t.Context(), &repo_model.Collaboration{UserID: 4, RepoID: 4, Mode: perm_model.AccessModeWrite}) + _ = db.Insert(t.Context(), &access_model.Access{UserID: 4, RepoID: 4, Mode: perm_model.AccessModeWrite}) + initialAccess := &access_model.Access{UserID: 4, RepoID: 4} + has, err := db.GetEngine(t.Context()).Get(initialAccess) + assert.NoError(t, err) + assert.True(t, has) + initialMode := initialAccess.Mode + + // Change collaboration mode to trigger update path + newMode := perm_model.AccessModeAdmin + assert.NotEqual(t, initialMode, newMode, "New mode should differ from initial mode") + + _, err = db.GetEngine(t.Context()). + Where("user_id = ? AND repo_id = ?", 4, 4). + Cols("mode"). + Update(&repo_model.Collaboration{Mode: newMode}) + assert.NoError(t, err) + + // Recalculate accesses - should UPDATE existing access, not delete+insert + assert.NoError(t, access_model.RecalculateAccesses(t.Context(), repo)) + + // Verify access was updated, not deleted and re-inserted + updatedAccess := &access_model.Access{UserID: 4, RepoID: 4} + has, err = db.GetEngine(t.Context()).Get(updatedAccess) + assert.NoError(t, err) + assert.True(t, has, "Access should still exist") + assert.Equal(t, newMode, updatedAccess.Mode, "Access mode should be updated to new collaboration mode") +} + +func testRecalculateAccessesRemoveAccess(t *testing.T) { + // Test the delete path in refreshAccesses optimization + // Scenario: Remove a user's collaboration, access should be deleted + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + assert.NoError(t, repo.LoadOwner(t.Context())) + + // Verify initial access exists + initialAccess := &access_model.Access{UserID: 4, RepoID: 4} + has, err := db.GetEngine(t.Context()).Get(initialAccess) + assert.NoError(t, err) + assert.True(t, has, "Access should exist initially") + + // Remove the collaboration to trigger delete path + _, err = db.GetEngine(t.Context()). + Where("user_id = ? AND repo_id = ?", 4, 4). + Delete(&repo_model.Collaboration{}) + assert.NoError(t, err) + + // Recalculate accesses - should DELETE the access record + assert.NoError(t, access_model.RecalculateAccesses(t.Context(), repo)) + + // Verify access was deleted + removedAccess := &access_model.Access{UserID: 4, RepoID: 4} + has, err = db.GetEngine(t.Context()).Get(removedAccess) + assert.NoError(t, err) + assert.False(t, has, "Access should be deleted after removing collaboration") +} diff --git a/models/perm/access/actions_repo_permission_test.go b/models/perm/access/actions_repo_permission_test.go new file mode 100644 index 0000000000..442f6cf2fc --- /dev/null +++ b/models/perm/access/actions_repo_permission_test.go @@ -0,0 +1,155 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package access + +import ( + "testing" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + perm_model "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetActionsUserRepoPermission(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + ctx := t.Context() + + // Use fixtures for repos and users + repo4 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) // Public, Owner 5, has Actions unit + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) // Private, Owner 2, no Actions unit in fixtures + repo15 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15}) // Private, Owner 2, no Actions unit in fixtures + owner2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + actionsUser := user_model.NewActionsUser() + + // Ensure repo2 and repo15 have Actions units for testing configuration + for _, r := range []*repo_model.Repository{repo2, repo15} { + require.NoError(t, db.Insert(ctx, &repo_model.RepoUnit{ + RepoID: r.ID, + Type: unit.TypeActions, + Config: &repo_model.ActionsConfig{}, + })) + } + + t.Run("SameRepo_Public", func(t *testing.T) { + task47 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 47}) + require.Equal(t, repo4.ID, task47.RepoID) + + perm, err := GetActionsUserRepoPermission(ctx, repo4, actionsUser, task47.ID) + require.NoError(t, err) + + // Public repo, bot should have Read access even if not collaborator + assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode) + assert.True(t, perm.CanRead(unit.TypeCode)) + }) + + t.Run("SameRepo_Private", func(t *testing.T) { + // Use Task 53 which is already in Repo 2 (Private) + task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53}) + require.Equal(t, repo2.ID, task53.RepoID) + + perm, err := GetActionsUserRepoPermission(ctx, repo2, actionsUser, task53.ID) + require.NoError(t, err) + + // Private repo, bot has no base access, but gets Write from effective tokens perms (Permissive by default) + assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode) + assert.True(t, perm.CanWrite(unit.TypeCode)) + }) + + t.Run("CrossRepo_Denied_None", func(t *testing.T) { + task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53}) + + // Set owner policy to nil allowed repos (None) + cfg := actions_model.OwnerActionsConfig{} + require.NoError(t, actions_model.SetOwnerActionsConfig(ctx, owner2.ID, cfg)) + + perm, err := GetActionsUserRepoPermission(ctx, repo15, actionsUser, task53.ID) + require.NoError(t, err) + + // Should NOT have access to the private repo. + assert.False(t, perm.CanRead(unit.TypeCode)) + }) + + t.Run("ForkPR_NoCrossRepo", func(t *testing.T) { + task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53}) + task53.IsForkPullRequest = true + require.NoError(t, actions_model.UpdateTask(ctx, task53, "is_fork_pull_request")) + + // Policy contains repo15 + cfg := actions_model.OwnerActionsConfig{ + AllowedCrossRepoIDs: []int64{repo15.ID}, + } + require.NoError(t, actions_model.SetOwnerActionsConfig(ctx, owner2.ID, cfg)) + + perm, err := GetActionsUserRepoPermission(ctx, repo15, actionsUser, task53.ID) + require.NoError(t, err) + + // Fork PR never gets cross-repo access to other private repos + assert.False(t, perm.CanRead(unit.TypeCode)) + }) + + t.Run("Inheritance_And_Clamping", func(t *testing.T) { + task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53}) + task53.IsForkPullRequest = false + require.NoError(t, actions_model.UpdateTask(ctx, task53, "is_fork_pull_request")) + + // Owner policy: Restricted mode (Read-only Code) + ownerCfg := actions_model.OwnerActionsConfig{ + TokenPermissionMode: repo_model.ActionsTokenPermissionModeRestricted, + MaxTokenPermissions: &repo_model.ActionsTokenPermissions{ + UnitAccessModes: map[unit.Type]perm_model.AccessMode{ + unit.TypeCode: perm_model.AccessModeRead, + }, + }, + } + require.NoError(t, actions_model.SetOwnerActionsConfig(ctx, owner2.ID, ownerCfg)) + + // Repo policy: OverrideOwnerConfig = false (should inherit owner's restricted mode) + repo2ActionsUnit := repo2.MustGetUnit(ctx, unit.TypeActions) + repo2ActionsCfg := repo2ActionsUnit.ActionsConfig() + repo2ActionsCfg.OverrideOwnerConfig = false + require.NoError(t, repo_model.UpdateRepoUnitConfig(ctx, repo2ActionsUnit)) + + perm, err := GetActionsUserRepoPermission(ctx, repo2, actionsUser, task53.ID) + require.NoError(t, err) + + // Should be clamped to Read-only + assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeCode)) + assert.False(t, perm.CanWrite(unit.TypeCode)) + }) + + t.Run("RepoOverride_Clamping", func(t *testing.T) { + task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53}) + + // Owner policy: Permissive (Write access) + ownerCfg := actions_model.OwnerActionsConfig{ + TokenPermissionMode: repo_model.ActionsTokenPermissionModePermissive, + } + require.NoError(t, actions_model.SetOwnerActionsConfig(ctx, owner2.ID, ownerCfg)) + + // Repo policy: OverrideOwnerConfig = true, MaxTokenPermissions = Read + repo2ActionsUnit := repo2.MustGetUnit(ctx, unit.TypeActions) + repo2ActionsCfg := repo2ActionsUnit.ActionsConfig() + repo2ActionsCfg.OverrideOwnerConfig = true + repo2ActionsCfg.TokenPermissionMode = repo_model.ActionsTokenPermissionModeRestricted + repo2ActionsCfg.MaxTokenPermissions = &repo_model.ActionsTokenPermissions{ + UnitAccessModes: map[unit.Type]perm_model.AccessMode{ + unit.TypeCode: perm_model.AccessModeRead, + }, + } + require.NoError(t, repo_model.UpdateRepoUnitConfig(ctx, repo2ActionsUnit)) + + perm, err := GetActionsUserRepoPermission(ctx, repo2, actionsUser, task53.ID) + require.NoError(t, err) + + // Should be clamped to Read-only + assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeCode)) + }) +} diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 3235d83203..21821f1746 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "maps" "slices" "strings" @@ -258,6 +259,23 @@ func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) { } } +func checkSameOwnerCrossRepoAccess(ctx context.Context, taskRepo, targetRepo *repo_model.Repository, isForkPR bool) bool { + if isForkPR { + // Fork PRs are never allowed cross-repo access to other private repositories of the owner. + return false + } + if taskRepo.OwnerID != targetRepo.OwnerID { + return false + } + ownerCfg, err := actions_model.GetOwnerActionsConfig(ctx, targetRepo.OwnerID) + if err != nil { + log.Error("GetOwnerActionsConfig: %v", err) + return false + } + + return slices.Contains(ownerCfg.AllowedCrossRepoIDs, targetRepo.ID) +} + // GetActionsUserRepoPermission returns the actions user permissions to the repository func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Repository, actionsUser *user_model.User, taskID int64) (perm Permission, err error) { if actionsUser.ID != user_model.ActionsUserID { @@ -268,42 +286,112 @@ func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Reposito return perm, err } - var accessMode perm_model.AccessMode + if err := task.LoadJob(ctx); err != nil { + return perm, err + } + + var taskRepo *repo_model.Repository if task.RepoID != repo.ID { - taskRepo, exist, err := db.GetByID[repo_model.Repository](ctx, task.RepoID) - if err != nil || !exist { + if err := task.Job.LoadRepo(ctx); err != nil { return perm, err } - actionsCfg := repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() - if !actionsCfg.IsCollaborativeOwner(taskRepo.OwnerID) || !taskRepo.IsPrivate { - // The task repo can access the current repo only if the task repo is private and - // the owner of the task repo is a collaborative owner of the current repo. - // FIXME should owner's visibility also be considered here? + taskRepo = task.Job.Repo + } else { + taskRepo = repo + } - // check permission like simple user but limit to read-only - perm, err = GetUserRepoPermission(ctx, repo, user_model.NewActionsUser()) + // Compute effective permissions for this task against the target repo + effectivePerms, err := actions_model.ComputeTaskTokenPermissions(ctx, task, repo) + if err != nil { + return perm, err + } + if task.RepoID != repo.ID { + // Cross-repo access must also respect the target repo's permission ceiling. + targetRepoActionsCfg := repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() + if targetRepoActionsCfg.OverrideOwnerConfig { + effectivePerms = targetRepoActionsCfg.ClampPermissions(effectivePerms) + } else { + targetRepoOwnerActionsCfg, err := actions_model.GetOwnerActionsConfig(ctx, repo.OwnerID) if err != nil { return perm, err } - perm.AccessMode = min(perm.AccessMode, perm_model.AccessModeRead) - return perm, nil + effectivePerms = targetRepoOwnerActionsCfg.ClampPermissions(effectivePerms) } - accessMode = perm_model.AccessModeRead - } else if task.IsForkPullRequest { - accessMode = perm_model.AccessModeRead - } else { - accessMode = perm_model.AccessModeWrite } if err := repo.LoadUnits(ctx); err != nil { return perm, err } - perm.SetUnitsWithDefaultAccessMode(repo.Units, accessMode) + + var maxPerm Permission + + // Set up per-unit access modes based on configured permissions + maxPerm.units = repo.Units + maxPerm.unitsMode = maps.Clone(effectivePerms.UnitAccessModes) + + // Check permission like simple user but limit to read-only (PR #36095) + // Enhanced to also grant read-only access if isSameRepo is true and target repository is public + botPerm, err := GetIndividualUserRepoPermission(ctx, repo, user_model.NewActionsUser()) + if err != nil { + return perm, err + } + if botPerm.AccessMode >= perm_model.AccessModeRead { + // Public repo allows read access, increase permissions to at least read + // Otherwise you cannot access your own repository if your permissions are set to none but the repository is public + for _, u := range repo.Units { + if botPerm.CanRead(u.Type) { + maxPerm.unitsMode[u.Type] = max(maxPerm.unitsMode[u.Type], perm_model.AccessModeRead) + } + } + } + + if task.RepoID == repo.ID { + return maxPerm, nil + } + + if checkSameOwnerCrossRepoAccess(ctx, taskRepo, repo, task.IsForkPullRequest) { + // Access allowed by owner policy (grants access to private repos). + // Note: maxPerm has already been restricted to Read-Only in ComputeTaskTokenPermissions + // because isSameRepo is false. + return maxPerm, nil + } + + // Fall through to allow public repository read access via botPerm check below + + // Check if the repo is public or the Bot has explicit access + if botPerm.AccessMode >= perm_model.AccessModeRead { + return maxPerm, nil + } + + // Check Collaborative Owner and explicit Bot permissions + // We allow access if: + // 1. It's a collaborative owner relationship + // 2. The Actions Bot user has been explicitly granted access and repository is private + // 3. The repository is public (handled by botPerm above) + + if taskRepo.IsPrivate { + actionsUnit := repo.MustGetUnit(ctx, unit.TypeActions) + if actionsUnit.ActionsConfig().IsCollaborativeOwner(taskRepo.OwnerID) { + return maxPerm, nil + } + } + return perm, nil } -// GetUserRepoPermission returns the user permissions to the repository -func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) { +// GetDoerRepoPermission returns the repository permission for the current actor, +// dispatching to GetActionsUserRepoPermission when the actor is an Actions token user. +func GetDoerRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (Permission, error) { + if taskID, ok := user_model.GetActionsUserTaskID(user); ok { + return GetActionsUserRepoPermission(ctx, repo, user, taskID) + } + return GetIndividualUserRepoPermission(ctx, repo, user) +} + +// GetIndividualUserRepoPermission returns the permissions for an explicit user identity. +// In most request paths, callers should use GetDoerRepoPermission instead. +// Unlike GetDoerRepoPermission, this helper does not resolve Actions task users. +func GetIndividualUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) { defer func() { if err == nil { finalProcessRepoUnitPermission(user, &perm) @@ -462,8 +550,9 @@ func AccessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Re // AccessLevelUnit returns the Access a user has to a repository's. Will return NoneAccess if the // user does not have access. +// This helper only supports explicit user identities and does not resolve Actions task users. func AccessLevelUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) { //nolint:revive // export stutter - perm, err := GetUserRepoPermission(ctx, repo, user) + perm, err := GetIndividualUserRepoPermission(ctx, repo, user) if err != nil { return perm_model.AccessModeNone, err } @@ -482,7 +571,7 @@ func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model. if user.IsOrganization() { return false, fmt.Errorf("organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID) } - perm, err := GetUserRepoPermission(ctx, repo, user) + perm, err := GetIndividualUserRepoPermission(ctx, repo, user) if err != nil { return false, err } @@ -491,6 +580,7 @@ func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model. } // HasAnyUnitAccess see the comment of "perm.HasAnyUnitAccess" +// This helper only supports explicit user identities and does not resolve Actions task users. func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Repository) (bool, error) { var user *user_model.User var err error @@ -500,7 +590,7 @@ func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Reposi return false, err } } - perm, err := GetUserRepoPermission(ctx, repo, user) + perm, err := GetIndividualUserRepoPermission(ctx, repo, user) if err != nil { return false, err } @@ -548,13 +638,14 @@ func GetUserIDsWithUnitAccess(ctx context.Context, repo *repo_model.Repository, } // CheckRepoUnitUser check whether user could visit the unit of this repository +// This helper only supports explicit user identities and does not resolve Actions task users. func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool { if user != nil && user.IsAdmin { return true } - perm, err := GetUserRepoPermission(ctx, repo, user) + perm, err := GetIndividualUserRepoPermission(ctx, repo, user) if err != nil { - log.Error("GetUserRepoPermission: %w", err) + log.Error("GetIndividualUserRepoPermission: %w", err) return false } diff --git a/models/perm/access/repo_permission_test.go b/models/perm/access/repo_permission_test.go index a36be213ec..29a8dac71d 100644 --- a/models/perm/access/repo_permission_test.go +++ b/models/perm/access/repo_permission_test.go @@ -6,6 +6,7 @@ package access import ( "testing" + actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" perm_model "code.gitea.io/gitea/models/perm" @@ -157,8 +158,13 @@ func TestUnitAccessMode(t *testing.T) { assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "has unit, and map, use map") } -func TestGetUserRepoPermission(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) +func TestGetRepoPermission(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + t.Run("GetIndividualUserRepoPermission", testGetIndividualUserRepoPermission) + t.Run("GetDoerRepoPermission", testGetDoerRepoPermission) +} + +func testGetIndividualUserRepoPermission(t *testing.T) { ctx := t.Context() repo32 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 32}) // org public repo require.NoError(t, repo32.LoadOwner(ctx)) @@ -172,7 +178,7 @@ func TestGetUserRepoPermission(t *testing.T) { require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID})) t.Run("DoerInTeamWithNoRepo", func(t *testing.T) { - perm, err := GetUserRepoPermission(ctx, repo32, user) + perm, err := GetIndividualUserRepoPermission(ctx, repo32, user) require.NoError(t, err) assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode) assert.Nil(t, perm.unitsMode) // doer in the team, but has no access to the repo @@ -181,7 +187,7 @@ func TestGetUserRepoPermission(t *testing.T) { require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: repo32.ID})) require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeNone})) t.Run("DoerWithTeamUnitAccessNone", func(t *testing.T) { - perm, err := GetUserRepoPermission(ctx, repo32, user) + perm, err := GetIndividualUserRepoPermission(ctx, repo32, user) require.NoError(t, err) assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode) assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeCode]) @@ -191,7 +197,7 @@ func TestGetUserRepoPermission(t *testing.T) { require.NoError(t, db.TruncateBeans(ctx, &organization.TeamUnit{})) require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeWrite})) t.Run("DoerWithTeamUnitAccessWrite", func(t *testing.T) { - perm, err := GetUserRepoPermission(ctx, repo32, user) + perm, err := GetIndividualUserRepoPermission(ctx, repo32, user) require.NoError(t, err) assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode) assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode]) @@ -204,7 +210,7 @@ func TestGetUserRepoPermission(t *testing.T) { require.NoError(t, db.TruncateBeans(ctx, &organization.TeamUnit{}, &Access{})) // The user has access set of that repo, remove it, it is useless for our test require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: repo3.ID})) t.Run("DoerWithNoopTeamOnPrivateRepo", func(t *testing.T) { - perm, err := GetUserRepoPermission(ctx, repo3, user) + perm, err := GetIndividualUserRepoPermission(ctx, repo3, user) require.NoError(t, err) assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode) assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode]) @@ -214,7 +220,7 @@ func TestGetUserRepoPermission(t *testing.T) { require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeNone})) require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeIssues, AccessMode: perm_model.AccessModeRead})) t.Run("DoerWithReadIssueTeamOnPrivateRepo", func(t *testing.T) { - perm, err := GetUserRepoPermission(ctx, repo3, user) + perm, err := GetIndividualUserRepoPermission(ctx, repo3, user) require.NoError(t, err) assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode) assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode]) @@ -233,7 +239,7 @@ func TestGetUserRepoPermission(t *testing.T) { require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite})) require.NoError(t, db.Insert(ctx, Access{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite})) t.Run("DoerWithReadIssueTeamAndWriteCollaboratorOnPrivateRepo", func(t *testing.T) { - perm, err := GetUserRepoPermission(ctx, repo3, user) + perm, err := GetIndividualUserRepoPermission(ctx, repo3, user) require.NoError(t, err) assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode) assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode]) @@ -245,3 +251,25 @@ func TestGetUserRepoPermission(t *testing.T) { assert.Equal(t, user.ID, users[0].ID) }) } + +func testGetDoerRepoPermission(t *testing.T) { + ctx := t.Context() + + repo4 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + task47 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 47}) + actionsDoer := user_model.NewActionsUserWithTaskID(task47.ID) + regularUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + actionsPerm, err := GetDoerRepoPermission(ctx, repo4, actionsDoer) + require.NoError(t, err) + directPerm, err := GetActionsUserRepoPermission(ctx, repo4, actionsDoer, task47.ID) + require.NoError(t, err) + assert.Equal(t, directPerm, actionsPerm) + + doerPerm, err := GetDoerRepoPermission(ctx, repo1, regularUser) + require.NoError(t, err) + individualPerm, err := GetIndividualUserRepoPermission(ctx, repo1, regularUser) + require.NoError(t, err) + assert.Equal(t, individualPerm, doerPerm) +} diff --git a/models/project/column.go b/models/project/column.go index 79f6dfe911..7365204f18 100644 --- a/models/project/column.go +++ b/models/project/column.go @@ -257,9 +257,12 @@ func (p *Project) GetColumns(ctx context.Context) (ColumnList, error) { return columns, nil } -// getDefaultColumn return default column and ensure only one exists -func (p *Project) getDefaultColumn(ctx context.Context) (*Column, error) { +// getDefaultColumnWithFallback return default column if one exists +// otherwise return the first column by sorting and set it as default column +func (p *Project) getDefaultColumnWithFallback(ctx context.Context) (*Column, error) { var column Column + + // try to find a column "default=true" has, err := db.GetEngine(ctx). Where("project_id=? AND `default` = ?", p.ID, true). Desc("id").Get(&column) @@ -270,23 +273,9 @@ func (p *Project) getDefaultColumn(ctx context.Context) (*Column, error) { if has { return &column, nil } - return nil, ErrProjectColumnNotExist{ColumnID: 0} -} -// MustDefaultColumn returns the default column for a project. -// If one exists, it is returned -// If none exists, the first column will be elevated to the default column of this project -func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) { - c, err := p.getDefaultColumn(ctx) - if err != nil && !IsErrProjectColumnNotExist(err) { - return nil, err - } - if c != nil { - return c, nil - } - - var column Column - has, err := db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Get(&column) + // try to find the first column by sorting + has, err = db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Get(&column) if err != nil { return nil, err } @@ -298,8 +287,24 @@ func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) { return &column, nil } + return nil, ErrProjectColumnNotExist{ColumnID: 0} +} + +// MustDefaultColumn returns the default column for a project. +// If one exists, it is returned +// If none exists, the first column will be elevated to the default column of this project +// If there is no column, it creates a default column and returns it +func (p *Project) MustDefaultColumn(ctx context.Context) (*Column, error) { + c, err := p.getDefaultColumnWithFallback(ctx) + if err != nil && !IsErrProjectColumnNotExist(err) { + return nil, err + } + if c != nil { + return c, nil + } + // create a default column if none is found - column = Column{ + column := Column{ ProjectID: p.ID, Default: true, Title: "Uncategorized", diff --git a/models/repo.go b/models/repo.go index 522debb9fe..c5c5364da0 100644 --- a/models/repo.go +++ b/models/repo.go @@ -7,6 +7,7 @@ package models import ( "context" "strconv" + "strings" _ "image/jpeg" // Needed for jpeg support @@ -86,11 +87,20 @@ func labelStatsCorrectNumClosedIssuesRepo(ctx context.Context, id int64) error { return err } -var milestoneStatsQueryNumIssues = "SELECT `milestone`.id FROM `milestone` WHERE `milestone`.num_closed_issues!=(SELECT COUNT(*) FROM `issue` WHERE `issue`.milestone_id=`milestone`.id AND `issue`.is_closed=?) OR `milestone`.num_issues!=(SELECT COUNT(*) FROM `issue` WHERE `issue`.milestone_id=`milestone`.id)" +func milestoneStatsQueryNumIssuesSQL() string { + sql := ` +SELECT "milestone".id FROM "milestone" +WHERE ( + "milestone".num_closed_issues != (SELECT COUNT(*) FROM "issue" WHERE "issue".milestone_id="milestone".id AND "issue".is_closed=?) + OR "milestone".num_issues != (SELECT COUNT(*) FROM "issue" WHERE "issue".milestone_id="milestone".id) +) +` + return strings.TrimSpace(strings.ReplaceAll(sql, "\"", "`")) +} func milestoneStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error { e := db.GetEngine(ctx) - results, err := e.Query(milestoneStatsQueryNumIssues+" AND `milestone`.repo_id = ?", true, id) + results, err := e.Query(milestoneStatsQueryNumIssuesSQL()+" AND `milestone`.repo_id = ?", true, id) if err != nil { return err } @@ -192,7 +202,7 @@ func CheckRepoStats(ctx context.Context) error { }, // Milestone.Num{,Closed}Issues { - statsQuery(milestoneStatsQueryNumIssues, true), + statsQuery(milestoneStatsQueryNumIssuesSQL(), true), issues_model.UpdateMilestoneCounters, "milestone count 'num_closed_issues' and 'num_issues'", }, diff --git a/models/repo/avatar.go b/models/repo/avatar.go index eff64bd239..bfb08d9982 100644 --- a/models/repo/avatar.go +++ b/models/repo/avatar.go @@ -41,20 +41,14 @@ func generateRandomAvatar(ctx context.Context, repo *Repository) error { idToString := strconv.FormatInt(repo.ID, 10) seed := idToString - img, err := avatar.RandomImage([]byte(seed)) - if err != nil { - return fmt.Errorf("RandomImage: %w", err) - } + img := avatar.RandomImageDefaultSize([]byte(seed)) repo.Avatar = idToString if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error { - if err := png.Encode(w, img); err != nil { - log.Error("Encode: %v", err) - } - return err + return png.Encode(w, img) }); err != nil { - return fmt.Errorf("Failed to create dir %s: %w", repo.CustomAvatarRelativePath(), err) + return fmt.Errorf("failed to create dir %s: %w", repo.CustomAvatarRelativePath(), err) } log.Info("New random avatar created for repository: %d", repo.ID) diff --git a/models/repo/org_repo.go b/models/repo/org_repo.go index 96f21ba2ac..d8c2c91fec 100644 --- a/models/repo/org_repo.go +++ b/models/repo/org_repo.go @@ -18,7 +18,14 @@ import ( // GetOrgRepositories get repos belonging to the given organization func GetOrgRepositories(ctx context.Context, orgID int64) (RepositoryList, error) { var orgRepos []*Repository - return orgRepos, db.GetEngine(ctx).Where("owner_id = ?", orgID).Find(&orgRepos) + err := db.GetEngine(ctx).Where("owner_id = ?", orgID).Find(&orgRepos) + return orgRepos, err +} + +// GetOrgRepositoryIDs get repo IDs belonging to the given organization +func GetOrgRepositoryIDs(ctx context.Context, orgID int64) (repoIDs []int64, _ error) { + err := db.GetEngine(ctx).Table("repository").Where("owner_id = ?", orgID).Cols("id").Find(&repoIDs) + return repoIDs, err } type SearchTeamRepoOptions struct { @@ -26,7 +33,7 @@ type SearchTeamRepoOptions struct { TeamID int64 } -// GetRepositories returns paginated repositories in team of organization. +// GetTeamRepositories returns paginated repositories in team of organization. func GetTeamRepositories(ctx context.Context, opts *SearchTeamRepoOptions) (RepositoryList, error) { sess := db.GetEngine(ctx) if opts.TeamID > 0 { diff --git a/models/repo/pull_request_default_test.go b/models/repo/pull_request_default_test.go index 1c4f585ed9..b1653f2f1a 100644 --- a/models/repo/pull_request_default_test.go +++ b/models/repo/pull_request_default_test.go @@ -26,7 +26,7 @@ func TestDefaultTargetBranchSelection(t *testing.T) { prConfig := prUnit.PullRequestsConfig() prConfig.DefaultTargetBranch = "branch2" prUnit.Config = prConfig - assert.NoError(t, UpdateRepoUnit(ctx, prUnit)) + assert.NoError(t, UpdateRepoUnitConfig(ctx, prUnit)) repo.Units = nil assert.Equal(t, "branch2", repo.GetPullRequestTargetBranch(ctx)) } diff --git a/models/repo/repo.go b/models/repo/repo.go index 07b9bf30cc..25207cc28b 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -281,7 +281,7 @@ func (repo *Repository) SizeDetailsString() string { var str strings.Builder sizeDetails := repo.SizeDetails() for _, detail := range sizeDetails { - str.WriteString(fmt.Sprintf("%s: %s, ", detail.Name, base.FileSize(detail.Size))) + fmt.Fprintf(&str, "%s: %s, ", detail.Name, base.FileSize(detail.Size)) } return strings.TrimSuffix(str.String(), ", ") } @@ -422,52 +422,37 @@ func (repo *Repository) UnitEnabled(ctx context.Context, tp unit.Type) bool { return false } -// MustGetUnit always returns a RepoUnit object +// MustGetUnit always returns a RepoUnit object even if the unit doesn't exist (not enabled) func (repo *Repository) MustGetUnit(ctx context.Context, tp unit.Type) *RepoUnit { ru, err := repo.GetUnit(ctx, tp) if err == nil { return ru } - + if !errors.Is(err, util.ErrNotExist) { + setting.PanicInDevOrTesting("Failed to get unit %v for repository %d: %v", tp, repo.ID, err) + } + ru = &RepoUnit{RepoID: repo.ID, Type: tp} switch tp { case unit.TypeExternalWiki: - return &RepoUnit{ - Type: tp, - Config: new(ExternalWikiConfig), - } + ru.Config = new(ExternalWikiConfig) case unit.TypeExternalTracker: - return &RepoUnit{ - Type: tp, - Config: new(ExternalTrackerConfig), - } + ru.Config = new(ExternalTrackerConfig) case unit.TypePullRequests: - return &RepoUnit{ - Type: tp, - Config: new(PullRequestsConfig), - } + ru.Config = new(PullRequestsConfig) case unit.TypeIssues: - return &RepoUnit{ - Type: tp, - Config: new(IssuesConfig), - } + ru.Config = new(IssuesConfig) case unit.TypeActions: - return &RepoUnit{ - Type: tp, - Config: new(ActionsConfig), - } + ru.Config = new(ActionsConfig) case unit.TypeProjects: - cfg := new(ProjectsConfig) - cfg.ProjectsMode = ProjectsModeNone - return &RepoUnit{ - Type: tp, - Config: cfg, + ru.Config = new(ProjectsConfig) + default: // other units don't have config + } + if ru.Config != nil { + if err = ru.Config.FromDB(nil); err != nil { + setting.PanicInDevOrTesting("Failed to load default config for unit %v of repository %d: %v", tp, repo.ID, err) } } - - return &RepoUnit{ - Type: tp, - Config: new(UnitConfig), - } + return ru } // GetUnit returns a RepoUnit object diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index 811f83c999..e927174a55 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -778,3 +778,11 @@ func GetUserRepositories(ctx context.Context, opts SearchRepoOptions) (Repositor repos := make(RepositoryList, 0, opts.PageSize) return repos, count, db.SetSessionPagination(sess, &opts).Find(&repos) } + +func GetOwnerRepositoriesByIDs(ctx context.Context, ownerID int64, repoIDs []int64) (RepositoryList, error) { + if len(repoIDs) == 0 { + return RepositoryList{}, nil + } + repos := make(RepositoryList, 0, len(repoIDs)) + return repos, db.GetEngine(ctx).Where(builder.Eq{"owner_id": ownerID}).In("id", repoIDs).Find(&repos) +} diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index d03d5e1e6a..c32adbbcd4 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -5,8 +5,6 @@ package repo import ( "context" - "slices" - "strings" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" @@ -134,10 +132,26 @@ type PullRequestsConfig struct { DefaultTargetBranch string } +func DefaultPullRequestsConfig() *PullRequestsConfig { + cfg := &PullRequestsConfig{ + AllowMerge: true, + AllowRebase: true, + AllowRebaseMerge: true, + AllowSquash: true, + AllowFastForwardOnly: true, + AllowRebaseUpdate: true, + DefaultAllowMaintainerEdit: true, + } + cfg.DefaultDeleteBranchAfterMerge = setting.Repository.PullRequest.DefaultDeleteBranchAfterMerge + cfg.DefaultMergeStyle = MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle) + cfg.DefaultMergeStyle = util.IfZero(cfg.DefaultMergeStyle, MergeStyleMerge) + return cfg +} + // FromDB fills up a PullRequestsConfig from serialized format. func (cfg *PullRequestsConfig) FromDB(bs []byte) error { - // AllowRebaseUpdate = true as default for existing PullRequestConfig in DB - cfg.AllowRebaseUpdate = true + // set default values for existing PullRequestConfig in DB + *cfg = *DefaultPullRequestsConfig() return json.UnmarshalHandleDoubleEncode(bs, &cfg) } @@ -156,68 +170,8 @@ func (cfg *PullRequestsConfig) IsMergeStyleAllowed(mergeStyle MergeStyle) bool { mergeStyle == MergeStyleManuallyMerged && cfg.AllowManualMerge } -// GetDefaultMergeStyle returns the default merge style for this pull request -func (cfg *PullRequestsConfig) GetDefaultMergeStyle() MergeStyle { - if len(cfg.DefaultMergeStyle) != 0 { - return cfg.DefaultMergeStyle - } - - if setting.Repository.PullRequest.DefaultMergeStyle != "" { - return MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle) - } - - return MergeStyleMerge -} - -type ActionsConfig struct { - DisabledWorkflows []string - // CollaborativeOwnerIDs is a list of owner IDs used to share actions from private repos. - // Only workflows from the private repos whose owners are in CollaborativeOwnerIDs can access the current repo's actions. - CollaborativeOwnerIDs []int64 -} - -func (cfg *ActionsConfig) EnableWorkflow(file string) { - cfg.DisabledWorkflows = util.SliceRemoveAll(cfg.DisabledWorkflows, file) -} - -func (cfg *ActionsConfig) ToString() string { - return strings.Join(cfg.DisabledWorkflows, ",") -} - -func (cfg *ActionsConfig) IsWorkflowDisabled(file string) bool { - return slices.Contains(cfg.DisabledWorkflows, file) -} - -func (cfg *ActionsConfig) DisableWorkflow(file string) { - if slices.Contains(cfg.DisabledWorkflows, file) { - return - } - - cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file) -} - -func (cfg *ActionsConfig) AddCollaborativeOwner(ownerID int64) { - if !slices.Contains(cfg.CollaborativeOwnerIDs, ownerID) { - cfg.CollaborativeOwnerIDs = append(cfg.CollaborativeOwnerIDs, ownerID) - } -} - -func (cfg *ActionsConfig) RemoveCollaborativeOwner(ownerID int64) { - cfg.CollaborativeOwnerIDs = util.SliceRemoveAll(cfg.CollaborativeOwnerIDs, ownerID) -} - -func (cfg *ActionsConfig) IsCollaborativeOwner(ownerID int64) bool { - return slices.Contains(cfg.CollaborativeOwnerIDs, ownerID) -} - -// FromDB fills up a ActionsConfig from serialized format. -func (cfg *ActionsConfig) FromDB(bs []byte) error { - return json.UnmarshalHandleDoubleEncode(bs, &cfg) -} - -// ToDB exports a ActionsConfig to a serialized format. -func (cfg *ActionsConfig) ToDB() ([]byte, error) { - return json.Marshal(cfg) +func DefaultPullRequestsUnit(repoID int64) RepoUnit { + return RepoUnit{RepoID: repoID, Type: unit.TypePullRequests, Config: DefaultPullRequestsConfig()} } // ProjectsMode represents the projects enabled for a repository @@ -241,6 +195,8 @@ type ProjectsConfig struct { // FromDB fills up a ProjectsConfig from serialized format. func (cfg *ProjectsConfig) FromDB(bs []byte) error { + // TODO: remove GetProjectsMode, only use ProjectsMode + cfg.ProjectsMode = ProjectsModeAll return json.UnmarshalHandleDoubleEncode(bs, &cfg) } @@ -271,7 +227,12 @@ func (cfg *ProjectsConfig) IsProjectsAllowed(m ProjectsMode) bool { func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { switch colName { case "type": - switch unit.Type(db.Cell2Int64(val)) { + var err error + r.Type, _, err = db.CellToInt(val, unit.TypeInvalid) + if err != nil { + setting.PanicInDevOrTesting("Unable to convert repo unit (id=%d) type: %v", r.ID, err) + } + switch r.Type { case unit.TypeExternalWiki: r.Config = new(ExternalWikiConfig) case unit.TypeExternalTracker: @@ -289,6 +250,11 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { default: r.Config = new(UnitConfig) } + case "config": + if *val == nil { + // XROM doesn't call FromDB if the value is nil, but we need to set default values for the config fields + _ = r.Config.FromDB(nil) + } } } @@ -352,9 +318,9 @@ func getUnitsByRepoID(ctx context.Context, repoID int64) (units []*RepoUnit, err return units, nil } -// UpdateRepoUnit updates the provided repo unit -func UpdateRepoUnit(ctx context.Context, unit *RepoUnit) error { - _, err := db.GetEngine(ctx).ID(unit.ID).Update(unit) +// UpdateRepoUnitConfig updates the config of the provided repo unit +func UpdateRepoUnitConfig(ctx context.Context, unit *RepoUnit) error { + _, err := db.GetEngine(ctx).ID(unit.ID).Cols("config").Update(unit) return err } diff --git a/models/repo/repo_unit_actions.go b/models/repo/repo_unit_actions.go new file mode 100644 index 0000000000..50e2925792 --- /dev/null +++ b/models/repo/repo_unit_actions.go @@ -0,0 +1,153 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "slices" + + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/util" +) + +// ActionsTokenPermissionMode defines the default permission mode for Actions tokens +type ActionsTokenPermissionMode string + +const ( + // ActionsTokenPermissionModePermissive - write access by default (current behavior, backwards compatible) + ActionsTokenPermissionModePermissive ActionsTokenPermissionMode = "permissive" + // ActionsTokenPermissionModeRestricted - read access by default + ActionsTokenPermissionModeRestricted ActionsTokenPermissionMode = "restricted" +) + +func (ActionsTokenPermissionMode) EnumValues() []ActionsTokenPermissionMode { + return []ActionsTokenPermissionMode{ActionsTokenPermissionModePermissive /* default */, ActionsTokenPermissionModeRestricted} +} + +// ActionsTokenPermissions defines the permissions for different repository units +type ActionsTokenPermissions struct { + UnitAccessModes map[unit.Type]perm.AccessMode `json:"unit_access_modes,omitempty"` +} + +var ActionsTokenUnitTypes = []unit.Type{ + unit.TypeCode, + unit.TypeIssues, + unit.TypePullRequests, + unit.TypePackages, + unit.TypeActions, + unit.TypeWiki, + unit.TypeReleases, + unit.TypeProjects, +} + +func MakeActionsTokenPermissions(unitAccessMode perm.AccessMode) (ret ActionsTokenPermissions) { + ret.UnitAccessModes = make(map[unit.Type]perm.AccessMode) + for _, u := range ActionsTokenUnitTypes { + ret.UnitAccessModes[u] = unitAccessMode + } + return ret +} + +// ClampActionsTokenPermissions ensures that the given permissions don't exceed the maximum +func ClampActionsTokenPermissions(p1, p2 ActionsTokenPermissions) (ret ActionsTokenPermissions) { + ret.UnitAccessModes = make(map[unit.Type]perm.AccessMode) + for _, ut := range ActionsTokenUnitTypes { + ret.UnitAccessModes[ut] = min(p1.UnitAccessModes[ut], p2.UnitAccessModes[ut]) + } + return ret +} + +// MakeRestrictedPermissions returns the restricted permissions +func MakeRestrictedPermissions() ActionsTokenPermissions { + ret := MakeActionsTokenPermissions(perm.AccessModeNone) + ret.UnitAccessModes[unit.TypeCode] = perm.AccessModeRead + ret.UnitAccessModes[unit.TypePackages] = perm.AccessModeRead + ret.UnitAccessModes[unit.TypeReleases] = perm.AccessModeRead + return ret +} + +type ActionsConfig struct { + DisabledWorkflows []string + // CollaborativeOwnerIDs is a list of owner IDs used to share actions from private repos. + // Only workflows from the private repos whose owners are in CollaborativeOwnerIDs can access the current repo's actions. + CollaborativeOwnerIDs []int64 + // TokenPermissionMode defines the default permission mode (permissive, restricted, or custom) + TokenPermissionMode ActionsTokenPermissionMode `json:"token_permission_mode,omitempty"` + // MaxTokenPermissions defines the absolute maximum permissions any token can have in this context. + // Workflow YAML "permissions" keywords can reduce permissions but never exceed this ceiling. + MaxTokenPermissions *ActionsTokenPermissions `json:"max_token_permissions,omitempty"` + // OverrideOwnerConfig indicates if this repository should override the owner-level configuration (User or Org) + OverrideOwnerConfig bool `json:"override_owner_config,omitempty"` +} + +func (cfg *ActionsConfig) EnableWorkflow(file string) { + cfg.DisabledWorkflows = util.SliceRemoveAll(cfg.DisabledWorkflows, file) +} + +func (cfg *ActionsConfig) IsWorkflowDisabled(file string) bool { + return slices.Contains(cfg.DisabledWorkflows, file) +} + +func (cfg *ActionsConfig) DisableWorkflow(file string) { + if slices.Contains(cfg.DisabledWorkflows, file) { + return + } + + cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file) +} + +func (cfg *ActionsConfig) AddCollaborativeOwner(ownerID int64) { + if !slices.Contains(cfg.CollaborativeOwnerIDs, ownerID) { + cfg.CollaborativeOwnerIDs = append(cfg.CollaborativeOwnerIDs, ownerID) + } +} + +func (cfg *ActionsConfig) RemoveCollaborativeOwner(ownerID int64) { + cfg.CollaborativeOwnerIDs = util.SliceRemoveAll(cfg.CollaborativeOwnerIDs, ownerID) +} + +func (cfg *ActionsConfig) IsCollaborativeOwner(ownerID int64) bool { + return slices.Contains(cfg.CollaborativeOwnerIDs, ownerID) +} + +// GetDefaultTokenPermissions returns the default token permissions by its TokenPermissionMode. +// It does not apply MaxTokenPermissions; callers must clamp if needed. +func (cfg *ActionsConfig) GetDefaultTokenPermissions() ActionsTokenPermissions { + switch cfg.TokenPermissionMode { + case ActionsTokenPermissionModeRestricted: + return MakeRestrictedPermissions() + case ActionsTokenPermissionModePermissive: + return MakeActionsTokenPermissions(perm.AccessModeWrite) + default: + return ActionsTokenPermissions{} + } +} + +// GetMaxTokenPermissions returns the maximum allowed permissions +func (cfg *ActionsConfig) GetMaxTokenPermissions() ActionsTokenPermissions { + if cfg.MaxTokenPermissions != nil { + return *cfg.MaxTokenPermissions + } + // Default max is write for everything + return MakeActionsTokenPermissions(perm.AccessModeWrite) +} + +// ClampPermissions ensures that the given permissions don't exceed the maximum +func (cfg *ActionsConfig) ClampPermissions(perms ActionsTokenPermissions) ActionsTokenPermissions { + maxPerms := cfg.GetMaxTokenPermissions() + return ClampActionsTokenPermissions(perms, maxPerms) +} + +// FromDB fills up a ActionsConfig from serialized format. +func (cfg *ActionsConfig) FromDB(bs []byte) error { + _ = json.UnmarshalHandleDoubleEncode(bs, &cfg) + cfg.TokenPermissionMode, _ = util.EnumValue(cfg.TokenPermissionMode) + return nil +} + +// ToDB exports a ActionsConfig to a serialized format. +func (cfg *ActionsConfig) ToDB() ([]byte, error) { + return json.Marshal(cfg) +} diff --git a/models/repo/repo_unit_test.go b/models/repo/repo_unit_test.go index 56dda5672d..08f9ac2cd4 100644 --- a/models/repo/repo_unit_test.go +++ b/models/repo/repo_unit_test.go @@ -4,8 +4,12 @@ package repo import ( + "strings" "testing" + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/unit" + "github.com/stretchr/testify/assert" ) @@ -26,5 +30,75 @@ func TestActionsConfig(t *testing.T) { cfg.DisableWorkflow("test1.yaml") cfg.DisableWorkflow("test2.yaml") cfg.DisableWorkflow("test3.yaml") - assert.Equal(t, "test1.yaml,test2.yaml,test3.yaml", cfg.ToString()) + assert.Equal(t, "test1.yaml,test2.yaml,test3.yaml", strings.Join(cfg.DisabledWorkflows, ",")) +} + +func TestActionsConfigTokenPermissions(t *testing.T) { + t.Run("Default Permission Mode", func(t *testing.T) { + cfg := &ActionsConfig{TokenPermissionMode: "invalid-value"} + _ = cfg.FromDB(nil) + assert.Equal(t, ActionsTokenPermissionModePermissive, cfg.TokenPermissionMode) + assert.Equal(t, perm.AccessModeWrite, cfg.GetDefaultTokenPermissions().UnitAccessModes[unit.TypeCode]) + }) + + t.Run("Explicit Permission Mode", func(t *testing.T) { + cfg := &ActionsConfig{ + TokenPermissionMode: ActionsTokenPermissionModeRestricted, + } + assert.Equal(t, ActionsTokenPermissionModeRestricted, cfg.TokenPermissionMode) + }) + + t.Run("Effective Permissions - Permissive Mode", func(t *testing.T) { + cfg := &ActionsConfig{ + TokenPermissionMode: ActionsTokenPermissionModePermissive, + } + defaultPerms := cfg.GetDefaultTokenPermissions() + perms := cfg.ClampPermissions(defaultPerms) + assert.Equal(t, perm.AccessModeWrite, perms.UnitAccessModes[unit.TypeCode]) + assert.Equal(t, perm.AccessModeWrite, perms.UnitAccessModes[unit.TypeIssues]) + assert.Equal(t, perm.AccessModeWrite, perms.UnitAccessModes[unit.TypePackages]) + }) + + t.Run("Effective Permissions - Restricted Mode", func(t *testing.T) { + cfg := &ActionsConfig{ + TokenPermissionMode: ActionsTokenPermissionModeRestricted, + } + defaultPerms := cfg.GetDefaultTokenPermissions() + perms := cfg.ClampPermissions(defaultPerms) + assert.Equal(t, perm.AccessModeRead, perms.UnitAccessModes[unit.TypeCode]) + assert.Equal(t, perm.AccessModeNone, perms.UnitAccessModes[unit.TypeIssues]) + assert.Equal(t, perm.AccessModeRead, perms.UnitAccessModes[unit.TypePackages]) + }) + + t.Run("Clamp Permissions", func(t *testing.T) { + cfg := &ActionsConfig{ + MaxTokenPermissions: &ActionsTokenPermissions{ + UnitAccessModes: map[unit.Type]perm.AccessMode{ + unit.TypeCode: perm.AccessModeRead, + unit.TypeIssues: perm.AccessModeWrite, + unit.TypePullRequests: perm.AccessModeRead, + unit.TypePackages: perm.AccessModeRead, + unit.TypeActions: perm.AccessModeNone, + unit.TypeWiki: perm.AccessModeWrite, + }, + }, + } + input := ActionsTokenPermissions{ + UnitAccessModes: map[unit.Type]perm.AccessMode{ + unit.TypeCode: perm.AccessModeWrite, // Should be clamped to Read + unit.TypeIssues: perm.AccessModeWrite, // Should stay Write + unit.TypePullRequests: perm.AccessModeWrite, // Should be clamped to Read + unit.TypePackages: perm.AccessModeWrite, // Should be clamped to Read + unit.TypeActions: perm.AccessModeRead, // Should be clamped to None + unit.TypeWiki: perm.AccessModeRead, // Should stay Read + }, + } + clamped := cfg.ClampPermissions(input) + assert.Equal(t, perm.AccessModeRead, clamped.UnitAccessModes[unit.TypeCode]) + assert.Equal(t, perm.AccessModeWrite, clamped.UnitAccessModes[unit.TypeIssues]) + assert.Equal(t, perm.AccessModeRead, clamped.UnitAccessModes[unit.TypePullRequests]) + assert.Equal(t, perm.AccessModeRead, clamped.UnitAccessModes[unit.TypePackages]) + assert.Equal(t, perm.AccessModeNone, clamped.UnitAccessModes[unit.TypeActions]) + assert.Equal(t, perm.AccessModeRead, clamped.UnitAccessModes[unit.TypeWiki]) + }) } diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go index 08cf964bc8..e15a64b01e 100644 --- a/models/repo/user_repo.go +++ b/models/repo/user_repo.go @@ -147,19 +147,21 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us } // GetIssuePostersWithSearch returns users with limit of 30 whose username started with prefix that have authored an issue/pull request for the given repository -// If isShowFullName is set to true, also include full name prefix search -func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) { +// It searches with the "user.name" and "user.full_name" fields case-insensitively. +func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string) ([]*user_model.User, error) { users := make([]*user_model.User, 0, 30) - var prefixCond builder.Cond = builder.Like{"lower_name", strings.ToLower(search) + "%"} - if search != "" && isShowFullName { - prefixCond = prefixCond.Or(db.BuildCaseInsensitiveLike("full_name", "%"+search+"%")) - } cond := builder.In("`user`.id", builder.Select("poster_id").From("issue").Where( builder.Eq{"repo_id": repo.ID}. And(builder.Eq{"is_pull": isPull}), - ).GroupBy("poster_id")).And(prefixCond) + ).GroupBy("poster_id")) + + if search != "" { + var prefixCond builder.Cond = builder.Like{"lower_name", strings.ToLower(search) + "%"} + prefixCond = prefixCond.Or(db.BuildCaseInsensitiveLike("full_name", "%"+search+"%")) + cond = cond.And(prefixCond) + } return users, db.GetEngine(ctx). Where(cond). diff --git a/models/repo/user_repo_test.go b/models/repo/user_repo_test.go index a53cf39dc4..cd8a0f1a1f 100644 --- a/models/repo/user_repo_test.go +++ b/models/repo/user_repo_test.go @@ -44,12 +44,12 @@ func TestGetIssuePostersWithSearch(t *testing.T) { repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - users, err := repo_model.GetIssuePostersWithSearch(t.Context(), repo2, false, "USER", false /* full name */) + users, err := repo_model.GetIssuePostersWithSearch(t.Context(), repo2, false, "USER") require.NoError(t, err) require.Len(t, users, 1) assert.Equal(t, "user2", users[0].Name) - users, err = repo_model.GetIssuePostersWithSearch(t.Context(), repo2, false, "TW%O", true /* full name */) + users, err = repo_model.GetIssuePostersWithSearch(t.Context(), repo2, false, "TW%O") require.NoError(t, err) require.Len(t, users, 1) assert.Equal(t, "user2", users[0].Name) diff --git a/models/user/avatar.go b/models/user/avatar.go index 542bd93b98..5811d85911 100644 --- a/models/user/avatar.go +++ b/models/user/avatar.go @@ -30,22 +30,16 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error { seed = u.Name } - img, err := avatar.RandomImage([]byte(seed)) - if err != nil { - return fmt.Errorf("RandomImage: %w", err) - } + img := avatar.RandomImageDefaultSize([]byte(seed)) u.Avatar = avatars.HashEmail(seed) - _, err = storage.Avatars.Stat(u.CustomAvatarRelativePath()) + _, err := storage.Avatars.Stat(u.CustomAvatarRelativePath()) if err != nil { // If unable to Stat the avatar file (usually it means non-existing), then try to save a new one // Don't share the images so that we can delete them easily if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error { - if err := png.Encode(w, img); err != nil { - log.Error("Encode: %v", err) - } - return nil + return png.Encode(w, img) }); err != nil { return fmt.Errorf("failed to save avatar %s: %w", u.CustomAvatarRelativePath(), err) } @@ -74,7 +68,7 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string { switch { case u.UseCustomAvatar: useLocalAvatar = true - case disableGravatar, setting.OfflineMode: + case disableGravatar: useLocalAvatar = true autoGenerateAvatar = true } diff --git a/models/user/badge.go b/models/user/badge.go index e475ceb748..fbba865926 100644 --- a/models/user/badge.go +++ b/models/user/badge.go @@ -5,9 +5,12 @@ package user import ( "context" - "fmt" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/util" + + "xorm.io/builder" + "xorm.io/xorm/schemas" ) // Badge represents a user badge @@ -22,7 +25,16 @@ type Badge struct { type UserBadge struct { //nolint:revive // export stutter ID int64 `xorm:"pk autoincr"` BadgeID int64 - UserID int64 `xorm:"INDEX"` + UserID int64 +} + +// TableIndices implements xorm's TableIndices interface +func (n *UserBadge) TableIndices() []*schemas.Index { + indices := make([]*schemas.Index, 0, 1) + ubUnique := schemas.NewIndex("unique_user_badge", schemas.UniqueType) + ubUnique.AddColumn("user_id", "badge_id") + indices = append(indices, ubUnique) + return indices } func init() { @@ -42,32 +54,85 @@ func GetUserBadges(ctx context.Context, u *User) ([]*Badge, int64, error) { return badges, count, err } -// CreateBadge creates a new badge. -func CreateBadge(ctx context.Context, badge *Badge) error { - _, err := db.GetEngine(ctx).Insert(badge) - return err +// GetBadgeUsersOptions contains options for getting users with a specific badge +type GetBadgeUsersOptions struct { + db.ListOptions + BadgeSlug string } -// GetBadge returns a badge +// GetBadgeUsers returns the users that have a specific badge with pagination support. +func GetBadgeUsers(ctx context.Context, opts *GetBadgeUsersOptions) ([]*User, int64, error) { + sess := db.GetEngine(ctx). + Select("`user`.*"). + Join("INNER", "user_badge", "`user_badge`.user_id=user.id"). + Join("INNER", "badge", "`user_badge`.badge_id=badge.id"). + Where("badge.slug=?", opts.BadgeSlug) + + if opts.Page > 0 { + sess = db.SetSessionPagination(sess, opts) + } + + users := make([]*User, 0, opts.PageSize) + count, err := sess.FindAndCount(&users) + return users, count, err +} + +// CreateBadge creates a new badge. +func CreateBadge(ctx context.Context, badge *Badge) error { + exists, err := db.GetEngine(ctx).Where("slug = ?", badge.Slug).Exist(new(Badge)) + if err != nil { + return err + } + if exists { + return util.NewAlreadyExistErrorf("badge already exists [slug: %s]", badge.Slug) + } + + if _, err := db.GetEngine(ctx).Insert(badge); err != nil { + // Handle race between existence check and insert. + exists, existErr := db.GetEngine(ctx).Where("slug = ?", badge.Slug).Exist(new(Badge)) + if existErr == nil && exists { + return util.NewAlreadyExistErrorf("badge already exists [slug: %s]", badge.Slug) + } + return err + } + return nil +} + +// GetBadge returns a specific badge func GetBadge(ctx context.Context, slug string) (*Badge, error) { badge := new(Badge) has, err := db.GetEngine(ctx).Where("slug=?", slug).Get(badge) - if !has { + if err != nil { return nil, err } - return badge, err + if !has { + return nil, util.NewNotExistErrorf("badge does not exist [slug: %s]", slug) + } + return badge, nil } // UpdateBadge updates a badge based on its slug. func UpdateBadge(ctx context.Context, badge *Badge) error { - _, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Update(badge) + _, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Cols("description", "image_url").Update(badge) return err } -// DeleteBadge deletes a badge. +// DeleteBadge deletes a badge and all associated user_badge entries. func DeleteBadge(ctx context.Context, badge *Badge) error { - _, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Delete(badge) - return err + return db.WithTx(ctx, func(ctx context.Context) error { + // First delete all user_badge entries for this badge + if _, err := db.GetEngine(ctx). + Where("badge_id = ?", badge.ID). + Delete(&UserBadge{}); err != nil { + return err + } + + // Then delete the badge itself + if _, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Delete(badge); err != nil { + return err + } + return nil + }) } // AddUserBadge adds a badge to a user. @@ -84,12 +149,25 @@ func AddUserBadges(ctx context.Context, u *User, badges []*Badge) error { if err != nil { return err } else if !has { - return fmt.Errorf("badge with slug %s doesn't exist", badge.Slug) + return util.NewNotExistErrorf("badge does not exist [slug: %s]", badge.Slug) } + + exists, err := db.GetEngine(ctx).Where("badge_id = ? AND user_id = ?", badge.ID, u.ID).Exist(new(UserBadge)) + if err != nil { + return err + } + if exists { + return util.NewAlreadyExistErrorf("user badge already exists [user_id: %d, badge_id: %d]", u.ID, badge.ID) + } + if err := db.Insert(ctx, &UserBadge{ BadgeID: badge.ID, UserID: u.ID, }); err != nil { + exists, existErr := db.GetEngine(ctx).Where("badge_id = ? AND user_id = ?", badge.ID, u.ID).Exist(new(UserBadge)) + if existErr == nil && exists { + return util.NewAlreadyExistErrorf("user badge already exists [user_id: %d, badge_id: %d]", u.ID, badge.ID) + } return err } } @@ -102,16 +180,33 @@ func RemoveUserBadge(ctx context.Context, u *User, badge *Badge) error { return RemoveUserBadges(ctx, u, []*Badge{badge}) } -// RemoveUserBadges removes badges from a user. +// RemoveUserBadges removes specific badges from a user. func RemoveUserBadges(ctx context.Context, u *User, badges []*Badge) error { return db.WithTx(ctx, func(ctx context.Context) error { + if len(badges) == 0 { + return nil + } + + badgeSlugs := make([]string, 0, len(badges)) for _, badge := range badges { - if _, err := db.GetEngine(ctx). - Join("INNER", "badge", "badge.id = `user_badge`.badge_id"). - Where("`user_badge`.user_id=? AND `badge`.slug=?", u.ID, badge.Slug). - Delete(&UserBadge{}); err != nil { - return err - } + badgeSlugs = append(badgeSlugs, badge.Slug) + } + var userBadges []UserBadge + if err := db.GetEngine(ctx).Table("user_badge"). + Join("INNER", "badge", "badge.id = `user_badge`.badge_id"). + Where("`user_badge`.user_id = ?", u.ID).In("`badge`.slug", badgeSlugs). + Find(&userBadges); err != nil { + return err + } + userBadgeIDs := make([]int64, 0, len(userBadges)) + for _, ub := range userBadges { + userBadgeIDs = append(userBadgeIDs, ub.ID) + } + if len(userBadgeIDs) == 0 { + return nil + } + if _, err := db.GetEngine(ctx).Table("user_badge").In("id", userBadgeIDs).Delete(); err != nil { + return err } return nil }) @@ -122,3 +217,57 @@ func RemoveAllUserBadges(ctx context.Context, u *User) error { _, err := db.GetEngine(ctx).Where("user_id=?", u.ID).Delete(&UserBadge{}) return err } + +// SearchBadgeOptions represents the options when finding badges +type SearchBadgeOptions struct { + db.ListOptions + + Keyword string + Slug string + ID int64 + OrderBy db.SearchOrderBy +} + +func (opts *SearchBadgeOptions) ToConds() builder.Cond { + cond := builder.NewCond() + + if opts.Keyword != "" { + keywordCond := builder.Or( + db.BuildCaseInsensitiveLike("badge.slug", opts.Keyword), + db.BuildCaseInsensitiveLike("badge.description", opts.Keyword), + ) + cond = cond.And(keywordCond) + } + + if opts.ID > 0 { + cond = cond.And(builder.Eq{"badge.id": opts.ID}) + } + + if len(opts.Slug) > 0 { + cond = cond.And(builder.Eq{"badge.slug": opts.Slug}) + } + + return cond +} + +func (opts *SearchBadgeOptions) ToOrders() string { + return opts.OrderBy.String() +} + +// SearchBadges returns badges based on the provided SearchBadgeOptions options +func SearchBadges(ctx context.Context, opts *SearchBadgeOptions) ([]*Badge, int64, error) { + return db.FindAndCount[Badge](ctx, opts) +} + +// GetBadgeByID returns a specific badge by ID +func GetBadgeByID(ctx context.Context, id int64) (*Badge, error) { + badge := new(Badge) + has, err := db.GetEngine(ctx).ID(id).Get(badge) + if err != nil { + return nil, err + } + if !has { + return nil, util.NewNotExistErrorf("badge does not exist [id: %d]", id) + } + return badge, nil +} diff --git a/models/user/badge_test.go b/models/user/badge_test.go new file mode 100644 index 0000000000..04979a9caf --- /dev/null +++ b/models/user/badge_test.go @@ -0,0 +1,185 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +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" + "code.gitea.io/gitea/modules/util" + + "github.com/stretchr/testify/assert" +) + +func TestBadge(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + t.Run("GetBadgeNotExist", testGetBadgeNotExist) + t.Run("CreateBadgeAlreadyExists", testCreateBadgeAlreadyExists) + t.Run("GetBadgeUsers", testGetBadgeUsers) + t.Run("AddAndRemoveUserBadges", testAddAndRemoveUserBadges) + t.Run("SearchBadgesOrderingAndKeyword", testSearchBadgesOrderingAndKeyword) +} + +func testGetBadgeNotExist(t *testing.T) { + badge, err := user_model.GetBadge(t.Context(), "does-not-exist") + assert.Nil(t, badge) + assert.Error(t, err) + assert.ErrorIs(t, err, util.ErrNotExist) +} + +func testCreateBadgeAlreadyExists(t *testing.T) { + badge := &user_model.Badge{ + Slug: "duplicate-badge-slug", + Description: "First", + } + assert.NoError(t, user_model.CreateBadge(t.Context(), badge)) + + err := user_model.CreateBadge(t.Context(), &user_model.Badge{ + Slug: "duplicate-badge-slug", + Description: "Second", + }) + assert.Error(t, err) + assert.ErrorIs(t, err, util.ErrAlreadyExist) +} + +func testGetBadgeUsers(t *testing.T) { + // Create a test badge + badge := &user_model.Badge{ + Slug: "test-badge-users", + Description: "Test Badge", + ImageURL: "test.png", + } + assert.NoError(t, user_model.CreateBadge(t.Context(), badge)) + + // Create test users and assign badges + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + assert.NoError(t, user_model.AddUserBadge(t.Context(), user1, badge)) + assert.NoError(t, user_model.AddUserBadge(t.Context(), user2, badge)) + defer func() { + assert.NoError(t, user_model.RemoveUserBadge(t.Context(), user1, badge)) + assert.NoError(t, user_model.RemoveUserBadge(t.Context(), user2, badge)) + }() + + // Test getting users with pagination + opts := &user_model.GetBadgeUsersOptions{ + BadgeSlug: badge.Slug, + ListOptions: db.ListOptions{ + Page: 1, + PageSize: 1, + }, + } + + users, count, err := user_model.GetBadgeUsers(t.Context(), opts) + assert.NoError(t, err) + assert.EqualValues(t, 2, count) + assert.Len(t, users, 1) + + // Test second page + opts.Page = 2 + users, count, err = user_model.GetBadgeUsers(t.Context(), opts) + assert.NoError(t, err) + assert.EqualValues(t, 2, count) + assert.Len(t, users, 1) + + // Test with non-existent badge + opts.BadgeSlug = "non-existent" + users, count, err = user_model.GetBadgeUsers(t.Context(), opts) + assert.NoError(t, err) + assert.EqualValues(t, 0, count) + assert.Empty(t, users) +} + +func testAddAndRemoveUserBadges(t *testing.T) { + badge1 := unittest.AssertExistsAndLoadBean(t, &user_model.Badge{ID: 1}) + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + // Add a badge to user and verify that it is returned in the list + assert.NoError(t, user_model.AddUserBadge(t.Context(), user1, badge1)) + badges, count, err := user_model.GetUserBadges(t.Context(), user1) + assert.Equal(t, int64(1), count) + assert.Equal(t, badge1.Slug, badges[0].Slug) + assert.NoError(t, err) + + // Confirm that it is impossible to duplicate the same badge + err = user_model.AddUserBadge(t.Context(), user1, badge1) + assert.Error(t, err) + assert.ErrorIs(t, err, util.ErrAlreadyExist) + + // Nothing happened to the existing badge + badges, count, err = user_model.GetUserBadges(t.Context(), user1) + assert.Equal(t, int64(1), count) + assert.Equal(t, badge1.Slug, badges[0].Slug) + assert.NoError(t, err) + + // Remove a badge from user and verify that it is no longer in the list + assert.NoError(t, user_model.RemoveUserBadge(t.Context(), user1, badge1)) + _, count, err = user_model.GetUserBadges(t.Context(), user1) + assert.Equal(t, int64(0), count) + assert.NoError(t, err) + + // Removing empty or missing badge selections should be a no-op. + assert.NoError(t, user_model.RemoveUserBadges(t.Context(), user1, nil)) + assert.NoError(t, user_model.RemoveUserBadges(t.Context(), user1, []*user_model.Badge{{Slug: "does-not-exist"}})) +} + +func testSearchBadgesOrderingAndKeyword(t *testing.T) { + createdBadges := []*user_model.Badge{ + {Slug: "badge-sort-b", Description: "Badge Sort B"}, + {Slug: "badge-sort-c", Description: "Badge Sort C"}, + {Slug: "badge-sort-a", Description: "Badge Sort A"}, + {Slug: "badge-sort-case", Description: "MiXeDCaSeKeyword"}, + } + for _, badge := range createdBadges { + assert.NoError(t, user_model.CreateBadge(t.Context(), badge)) + } + + opts := &user_model.SearchBadgeOptions{ + ListOptions: db.ListOptions{ListAll: true}, + Keyword: "badge-sort-", + OrderBy: db.SearchOrderBy("`badge`.id ASC"), + } + + oldestFirst, count, err := user_model.SearchBadges(t.Context(), opts) + assert.NoError(t, err) + assert.EqualValues(t, 4, count) + assert.Equal(t, []string{"badge-sort-b", "badge-sort-c", "badge-sort-a", "badge-sort-case"}, collectBadgeSlugs(oldestFirst)) + + opts.OrderBy = db.SearchOrderBy("`badge`.id DESC") + newestFirst, count, err := user_model.SearchBadges(t.Context(), opts) + assert.NoError(t, err) + assert.EqualValues(t, 4, count) + assert.Equal(t, []string{"badge-sort-case", "badge-sort-a", "badge-sort-c", "badge-sort-b"}, collectBadgeSlugs(newestFirst)) + + opts.OrderBy = db.SearchOrderBy("`badge`.slug ASC") + alpha, count, err := user_model.SearchBadges(t.Context(), opts) + assert.NoError(t, err) + assert.EqualValues(t, 4, count) + assert.Equal(t, []string{"badge-sort-a", "badge-sort-b", "badge-sort-c", "badge-sort-case"}, collectBadgeSlugs(alpha)) + + opts.OrderBy = db.SearchOrderBy("`badge`.slug DESC") + reverseAlpha, count, err := user_model.SearchBadges(t.Context(), opts) + assert.NoError(t, err) + assert.EqualValues(t, 4, count) + assert.Equal(t, []string{"badge-sort-case", "badge-sort-c", "badge-sort-b", "badge-sort-a"}, collectBadgeSlugs(reverseAlpha)) + + opts.Keyword = "mixedcasekeyword" + opts.OrderBy = db.SearchOrderBy("`badge`.slug ASC") + caseInsensitive, count, err := user_model.SearchBadges(t.Context(), opts) + assert.NoError(t, err) + assert.EqualValues(t, 1, count) + assert.Equal(t, []string{"badge-sort-case"}, collectBadgeSlugs(caseInsensitive)) +} + +func collectBadgeSlugs(badges []*user_model.Badge) []string { + slugs := make([]string, 0, len(badges)) + for _, badge := range badges { + slugs = append(slugs, badge.Slug) + } + return slugs +} diff --git a/models/user/list.go b/models/user/list.go index 4337c34963..aaaa7965c8 100644 --- a/models/user/list.go +++ b/models/user/list.go @@ -24,7 +24,7 @@ func (users UserList) GetUserIDs() []int64 { return userIDs } -// GetTwoFaStatus return state of 2FA enrollement +// GetTwoFaStatus return state of 2FA enrollment func (users UserList) GetTwoFaStatus(ctx context.Context) map[int64]bool { results := make(map[int64]bool, len(users)) for _, user := range users { diff --git a/models/user/setting.go b/models/user/setting.go index c65afae76c..a16fc86e55 100644 --- a/models/user/setting.go +++ b/models/user/setting.go @@ -11,10 +11,12 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/json" setting_module "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "xorm.io/builder" + "xorm.io/xorm/convert" ) // Setting is a key value store of user settings @@ -211,3 +213,44 @@ func upsertUserSettingValue(ctx context.Context, userID int64, key, value string return err }) } + +func GetUserSettingJSON[T any](ctx context.Context, userID int64, key string, def T) (ret T, _ error) { + ret = def + str, err := GetUserSetting(ctx, userID, key) + if err != nil { + return ret, err + } + + conv, ok := any(&ret).(convert.ConversionFrom) + if !ok { + conv, ok = any(ret).(convert.ConversionFrom) + } + if ok { + if err := conv.FromDB(util.UnsafeStringToBytes(str)); err != nil { + return ret, err + } + } else { + if str == "" { + return ret, nil + } + err = json.Unmarshal(util.UnsafeStringToBytes(str), &ret) + } + return ret, err +} + +func SetUserSettingJSON[T any](ctx context.Context, userID int64, key string, val T) (err error) { + conv, ok := any(&val).(convert.ConversionTo) + if !ok { + conv, ok = any(val).(convert.ConversionTo) + } + var bs []byte + if ok { + bs, err = conv.ToDB() + } else { + bs, err = json.Marshal(val) + } + if err != nil { + return err + } + return SetUserSetting(ctx, userID, key, util.UnsafeBytesToString(bs)) +} diff --git a/models/user/setting_options.go b/models/user/setting_options.go index 6d37ef63d5..5867b908d1 100644 --- a/models/user/setting_options.go +++ b/models/user/setting_options.go @@ -11,10 +11,6 @@ const ( // SettingsKeyShowOutdatedComments is the setting key whether or not to show outdated comments in PRs SettingsKeyShowOutdatedComments = "comment_code.show_outdated" - // UserActivityPubPrivPem is user's private key - UserActivityPubPrivPem = "activitypub.priv_pem" - // UserActivityPubPubPem is user's public key - UserActivityPubPubPem = "activitypub.pub_pem" // SignupIP is the IP address that the user signed up with SignupIP = "signup.ip" // SignupUserAgent is the user agent that the user signed up with @@ -26,4 +22,6 @@ const ( SettingEmailNotificationGiteaActionsAll = "all" SettingEmailNotificationGiteaActionsFailureOnly = "failure-only" // Default for actions email preference SettingEmailNotificationGiteaActionsDisabled = "disabled" + + SettingsKeyActionsConfig = "actions.config" ) diff --git a/models/user/user.go b/models/user/user.go index f8e8b5c64a..ade747401a 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -8,6 +8,7 @@ import ( "context" "encoding/hex" "fmt" + "html/template" "mime" "net/mail" "net/url" @@ -28,6 +29,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/htmlutil" "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" @@ -185,7 +187,7 @@ func (u *User) BeforeUpdate() { } // FIXME: this email doesn't need to be in lowercase, because the emails are mainly managed by the email table with lower_email field - // This trick could be removed in new releases to display the user inputed email as-is. + // This trick could be removed in new releases to display the user inputted email as-is. u.Email = strings.ToLower(u.Email) if !u.IsOrganization() { if len(u.AvatarEmail) == 0 { @@ -417,16 +419,6 @@ func (u *User) IsTokenAccessAllowed() bool { return u.Type == UserTypeIndividual || u.Type == UserTypeBot } -// DisplayName returns full name if it's not empty, -// returns username otherwise. -func (u *User) DisplayName() string { - trimmed := strings.TrimSpace(u.FullName) - if len(trimmed) > 0 { - return trimmed - } - return u.Name -} - // EmailTo returns a string suitable to be put into a e-mail `To:` header. func (u *User) EmailTo() string { sanitizedDisplayName := globalVars().emailToReplacer.Replace(u.DisplayName()) @@ -445,27 +437,45 @@ func (u *User) EmailTo() string { return fmt.Sprintf("%s <%s>", mime.QEncoding.Encode("utf-8", add.Name), add.Address) } -// GetDisplayName returns full name if it's not empty and DEFAULT_SHOW_FULL_NAME is set, -// returns username otherwise. +// TODO: DefaultShowFullName causes messy logic, there are already too many methods to display a user's "display name", need to refactor them +// * user.Name / user.FullName: directly used in templates +// * user.DisplayName(): always show FullName if it's not empty, otherwise show Name +// * user.GetDisplayName(): show FullName if it's not empty and DefaultShowFullName is set, otherwise show Name +// * user.ShortName(): used a lot in templates, but it should be removed and let frontend use "ellipsis" styles +// * activity action.ShortActUserName/GetActDisplayName/GetActDisplayNameTitle, etc: duplicate and messy + +// DisplayName returns full name if it's not empty, returns username otherwise. +func (u *User) DisplayName() string { + fullName := strings.TrimSpace(u.FullName) + if fullName != "" { + return fullName + } + return u.Name +} + +// GetDisplayName returns full name if it's not empty and DEFAULT_SHOW_FULL_NAME is set, otherwise, username. func (u *User) GetDisplayName() string { if setting.UI.DefaultShowFullName { - trimmed := strings.TrimSpace(u.FullName) - if len(trimmed) > 0 { - return trimmed + fullName := strings.TrimSpace(u.FullName) + if fullName != "" { + return fullName } } return u.Name } -// GetCompleteName returns the full name and username in the form of -// "Full Name (username)" if full name is not empty, otherwise it returns -// "username". -func (u *User) GetCompleteName() string { - trimmedFullName := strings.TrimSpace(u.FullName) - if len(trimmedFullName) > 0 { - return fmt.Sprintf("%s (%s)", trimmedFullName, u.Name) +// ShortName ellipses username to length (still used by many templates), it calls GetDisplayName and respects DEFAULT_SHOW_FULL_NAME +func (u *User) ShortName(length int) string { + return util.EllipsisDisplayString(u.GetDisplayName(), length) +} + +func (u *User) GetShortDisplayNameLinkHTML() template.HTML { + fullName := strings.TrimSpace(u.FullName) + displayName, displayTooltip := u.Name, fullName + if setting.UI.DefaultShowFullName && fullName != "" { + displayName, displayTooltip = fullName, u.Name } - return u.Name + return htmlutil.HTMLFormat(`%s`, u.HomeLink(), displayTooltip, displayName) } func gitSafeName(name string) string { @@ -488,14 +498,6 @@ func (u *User) GitName() string { return fmt.Sprintf("user-%d", u.ID) } -// ShortName ellipses username to length -func (u *User) ShortName(length int) string { - if setting.UI.DefaultShowFullName && len(u.FullName) > 0 { - return util.EllipsisDisplayString(u.FullName, length) - } - return util.EllipsisDisplayString(u.Name, length) -} - // IsMailable checks if a user is eligible to receive emails. // System users like Ghost and Gitea Actions are excluded. func (u *User) IsMailable() bool { @@ -1321,9 +1323,12 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) { return nil, ErrUserNotExist{Name: email} } -// GetUser checks if a user already exists -func GetUser(ctx context.Context, user *User) (bool, error) { - return db.GetEngine(ctx).Get(user) +func GetIndividualUser(ctx context.Context, user *User) (bool, error) { + has, err := db.GetEngine(ctx).Get(user) + if has && user.Type != UserTypeIndividual { + has = false + } + return has, err } // GetUserByOpenID returns the user object by given OpenID if exists. @@ -1457,16 +1462,6 @@ func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool { return false } -// CountWrongUserType count OrgUser who have wrong type -func CountWrongUserType(ctx context.Context) (int64, error) { - return db.GetEngine(ctx).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Count(new(User)) -} - -// FixWrongUserType fix OrgUser who have wrong type -func FixWrongUserType(ctx context.Context) (int64, error) { - return db.GetEngine(ctx).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Cols("type").NoAutoTime().Update(&User{Type: 1}) -} - func GetOrderByName() string { if setting.UI.DefaultShowFullName { return "full_name, name" diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go index 7d4b2e2237..5144c0eca6 100644 --- a/models/webhook/webhook.go +++ b/models/webhook/webhook.go @@ -126,6 +126,7 @@ type Webhook struct { OwnerID int64 `xorm:"INDEX"` IsSystemWebhook bool URL string `xorm:"url TEXT"` + Name string `xorm:"VARCHAR(255) NOT NULL DEFAULT ''"` HTTPMethod string `xorm:"http_method"` ContentType HookContentType Secret string `xorm:"TEXT"` diff --git a/modules/actions/artifacts.go b/modules/actions/artifacts.go index d28726e899..4884eb42e8 100644 --- a/modules/actions/artifacts.go +++ b/modules/actions/artifacts.go @@ -5,44 +5,61 @@ package actions import ( "net/http" + "strings" actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/modules/httplib" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/services/context" ) -// Artifacts using the v4 backend are stored as a single combined zip file per artifact on the backend -// The v4 backend ensures ContentEncoding is set to "application/zip", which is not the case for the old backend +// IsArtifactV4 detects whether the artifact is likely from v4. +// V4 backend stores the files as a single combined zip file per artifact, and ensures ContentEncoding contains a slash +// (otherwise this uses application/zip instead of the custom mime type), which is not the case for the old backend. func IsArtifactV4(art *actions_model.ActionArtifact) bool { - return art.ArtifactName+".zip" == art.ArtifactPath && art.ContentEncoding == "application/zip" + return strings.Contains(art.ContentEncodingOrType, "/") } -func DownloadArtifactV4ServeDirectOnly(ctx *context.Base, art *actions_model.ActionArtifact) (bool, error) { - if setting.Actions.ArtifactStorage.ServeDirect() { - u, err := storage.ActionsArtifacts.URL(art.StoragePath, art.ArtifactPath, ctx.Req.Method, nil) - if u != nil && err == nil { - ctx.Redirect(u.String(), http.StatusFound) - return true, nil - } +func GetArtifactV4ServeDirectURL(art *actions_model.ActionArtifact, method string) (string, error) { + contentType := art.ContentEncodingOrType + u, err := storage.ActionsArtifacts.ServeDirectURL(art.StoragePath, art.ArtifactPath, method, &storage.ServeDirectOptions{ContentType: contentType}) + if err != nil { + return "", err } - return false, nil + return u.String(), nil } -func DownloadArtifactV4Fallback(ctx *context.Base, art *actions_model.ActionArtifact) error { +func DownloadArtifactV4ServeDirect(ctx *context.Base, art *actions_model.ActionArtifact) bool { + if !setting.Actions.ArtifactStorage.ServeDirect() { + return false + } + u, err := GetArtifactV4ServeDirectURL(art, ctx.Req.Method) + if err != nil { + log.Error("GetArtifactV4ServeDirectURL: %v", err) + return false + } + ctx.Redirect(u, http.StatusFound) + return true +} + +func DownloadArtifactV4ReadStorage(ctx *context.Base, art *actions_model.ActionArtifact) error { f, err := storage.ActionsArtifacts.Open(art.StoragePath) if err != nil { return err } defer f.Close() - http.ServeContent(ctx.Resp, ctx.Req, art.ArtifactName+".zip", art.CreatedUnix.AsLocalTime(), f) + httplib.ServeUserContentByFile(ctx.Req, ctx.Resp, f, httplib.ServeHeaderOptions{ + Filename: art.ArtifactPath, + ContentType: art.ContentEncodingOrType, // v4 guarantees that the field is Content-Type + }) return nil } func DownloadArtifactV4(ctx *context.Base, art *actions_model.ActionArtifact) error { - ok, err := DownloadArtifactV4ServeDirectOnly(ctx, art) - if ok || err != nil { - return err + if DownloadArtifactV4ServeDirect(ctx, art) { + return nil } - return DownloadArtifactV4Fallback(ctx, art) + return DownloadArtifactV4ReadStorage(ctx, art) } diff --git a/modules/actions/github.go b/modules/actions/github.go index 68116ec83a..b7f475aa91 100644 --- a/modules/actions/github.go +++ b/modules/actions/github.go @@ -59,6 +59,10 @@ func IsDefaultBranchWorkflow(triggedEvent webhook_module.HookEventType) bool { // Github "issues" event // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issues return true + case webhook_module.HookEventWorkflowRun: + // GitHub "workflow_run" event + // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_run + return true } return false diff --git a/modules/actions/jobparser/evaluator.go b/modules/actions/jobparser/evaluator.go index 363cb2c18b..b320095071 100644 --- a/modules/actions/jobparser/evaluator.go +++ b/modules/actions/jobparser/evaluator.go @@ -135,7 +135,7 @@ func rewriteSubExpression(in string, forceFormat bool) (string, error) { exprStart := -1 strStart := -1 var results []string - formatOut := "" + var formatOut strings.Builder for pos < len(in) { if strStart > -1 { matches := strPattern.FindStringIndex(in[pos:]) @@ -158,7 +158,7 @@ func rewriteSubExpression(in string, forceFormat bool) (string, error) { } if exprEnd > -1 { - formatOut += fmt.Sprintf("{%d}", len(results)) + fmt.Fprintf(&formatOut, "{%d}", len(results)) results = append(results, strings.TrimSpace(in[exprStart:pos+exprEnd])) pos += exprEnd + 2 exprStart = -1 @@ -170,20 +170,20 @@ func rewriteSubExpression(in string, forceFormat bool) (string, error) { } else { exprStart = strings.Index(in[pos:], "${{") if exprStart != -1 { - formatOut += escapeFormatString(in[pos : pos+exprStart]) + formatOut.WriteString(escapeFormatString(in[pos : pos+exprStart])) exprStart = pos + exprStart + 3 pos = exprStart } else { - formatOut += escapeFormatString(in[pos:]) + formatOut.WriteString(escapeFormatString(in[pos:])) pos = len(in) } } } - if len(results) == 1 && formatOut == "{0}" && !forceFormat { + if len(results) == 1 && formatOut.String() == "{0}" && !forceFormat { return in, nil } - out := fmt.Sprintf("format('%s', %s)", strings.ReplaceAll(formatOut, "'", "''"), strings.Join(results, ", ")) + out := fmt.Sprintf("format('%s', %s)", strings.ReplaceAll(formatOut.String(), "'", "''"), strings.Join(results, ", ")) return out, nil } diff --git a/modules/actions/log.go b/modules/actions/log.go index 5a1425e031..3fb56b402a 100644 --- a/modules/actions/log.go +++ b/modules/actions/log.go @@ -33,21 +33,22 @@ const ( // It doesn't respect the file format in the filename like ".zst", since it's difficult to reopen a closed compressed file and append new content. // Why doesn't it store logs in object storage directly? Because it's not efficient to append content to object storage. func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runnerv1.LogRow) ([]int, error) { - flag := os.O_WRONLY + flag, openFileFor := os.O_WRONLY, "write-only" if offset == 0 { - // Create file only if offset is 0, or it could result in content holes if the file doesn't exist. - flag |= os.O_CREATE + // Only allow to create file if offset is 0 (the first write), see #25560. + // Otherwise, it might result in content holes if the file has been deleted after transferred (actions.TransferLogs). + flag, openFileFor = os.O_WRONLY|os.O_CREATE, "write-create" } name := DBFSPrefix + filename f, err := dbfs.OpenFile(ctx, name, flag) if err != nil { - return nil, fmt.Errorf("dbfs OpenFile %q: %w", name, err) + return nil, fmt.Errorf("dbfs.OpenFile %q for %s: %w", name, openFileFor, err) } defer f.Close() stat, err := f.Stat() if err != nil { - return nil, fmt.Errorf("dbfs Stat %q: %w", name, err) + return nil, fmt.Errorf("dbfs.Stat %q: %w", name, err) } if stat.Size() < offset { // If the size is less than offset, refuse to write, or it could result in content holes. @@ -56,7 +57,7 @@ func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runne } if _, err := f.Seek(offset, io.SeekStart); err != nil { - return nil, fmt.Errorf("dbfs Seek %q: %w", name, err) + return nil, fmt.Errorf("dbfs.Seek %q: %w", name, err) } writer := bufio.NewWriterSize(f, defaultBufSize) @@ -121,16 +122,17 @@ const ( // TransferLogs transfers logs from DBFS to object storage. // It happens when the file is complete and no more logs will be appended. // It respects the file format in the filename like ".zst", and compresses the content if needed. +// The task log file must be marked as "log_in_storage=true" after the transfer. func TransferLogs(ctx context.Context, filename string) (func(), error) { name := DBFSPrefix + filename remove := func() { if err := dbfs.Remove(ctx, name); err != nil { - log.Warn("dbfs remove %q: %v", name, err) + log.Warn("dbfs.Remove %q: %v", name, err) } } f, err := dbfs.Open(ctx, name) if err != nil { - return nil, fmt.Errorf("dbfs open %q: %w", name, err) + return nil, fmt.Errorf("dbfs.Open %q: %w", name, err) } defer f.Close() @@ -164,7 +166,7 @@ func RemoveLogs(ctx context.Context, inStorage bool, filename string) error { name := DBFSPrefix + filename err := dbfs.Remove(ctx, name) if err != nil { - return fmt.Errorf("dbfs remove %q: %w", name, err) + return fmt.Errorf("dbfs.Remove %q: %w", name, err) } return nil } @@ -180,7 +182,7 @@ func OpenLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeek name := DBFSPrefix + filename f, err := dbfs.Open(ctx, name) if err != nil { - return nil, fmt.Errorf("dbfs open %q: %w", name, err) + return nil, fmt.Errorf("dbfs.Open %q: %w", name, err) } return f, nil } diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 4ac06def4d..ba1aee7d72 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -103,10 +103,20 @@ func GetEventsFromContent(content []byte) ([]*jobparser.Event, error) { if err != nil { return nil, err } + if err := ValidateWorkflowContent(content); err != nil { + return nil, err + } return events, nil } +// ValidateWorkflowContent catches structural errors (e.g. blank lines in run: | blocks) +// that model.ReadWorkflow alone does not detect. +func ValidateWorkflowContent(content []byte) error { + _, err := jobparser.Parse(content) + return err +} + func DetectWorkflows( gitRepo *git.Repository, commit *git.Commit, diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index 77a65aae49..cda2de13e2 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -9,16 +9,26 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" webhook_module "code.gitea.io/gitea/modules/webhook" "github.com/stretchr/testify/assert" ) +func fullWorkflowContent(part string) []byte { + return []byte(` +name: test +` + part + ` +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo hello +`) +} + func TestIsWorkflow(t *testing.T) { - oldDirs := setting.Actions.WorkflowDirs - defer func() { - setting.Actions.WorkflowDirs = oldDirs - }() + defer test.MockVariableValue(&setting.Actions.WorkflowDirs)() tests := []struct { name string @@ -190,7 +200,7 @@ func TestDetectMatched(t *testing.T) { expected: true, }, { - desc: "HookEventSchedue(schedule) matches GithubEventSchedule(schedule)", + desc: "HookEventSchedule(schedule) matches GithubEventSchedule(schedule)", triggedEvent: webhook_module.HookEventSchedule, payload: nil, yamlOn: "on: schedule", @@ -218,7 +228,7 @@ func TestDetectMatched(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - evts, err := GetEventsFromContent([]byte(tc.yamlOn)) + evts, err := GetEventsFromContent(fullWorkflowContent(tc.yamlOn)) assert.NoError(t, err) assert.Len(t, evts, 1) assert.Equal(t, tc.expected, detectMatched(nil, tc.commit, tc.triggedEvent, tc.payload, evts[0])) @@ -373,7 +383,7 @@ func TestMatchIssuesEvent(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { - evts, err := GetEventsFromContent([]byte(tc.yamlOn)) + evts, err := GetEventsFromContent(fullWorkflowContent(tc.yamlOn)) assert.NoError(t, err) assert.Len(t, evts, 1) diff --git a/modules/activitypub/client.go b/modules/activitypub/client.go deleted file mode 100644 index a9941f9b75..0000000000 --- a/modules/activitypub/client.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package activitypub - -import ( - "bytes" - "context" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" - "net/http" - "strings" - "time" - - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/proxy" - "code.gitea.io/gitea/modules/setting" - - "github.com/42wim/httpsig" -) - -const ( - // ActivityStreamsContentType const - ActivityStreamsContentType = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"` - httpsigExpirationTime = 60 -) - -// Gets the current time as an RFC 2616 formatted string -// RFC 2616 requires RFC 1123 dates but with GMT instead of UTC -func CurrentTime() string { - return strings.ReplaceAll(time.Now().UTC().Format(time.RFC1123), "UTC", "GMT") -} - -func containsRequiredHTTPHeaders(method string, headers []string) error { - var hasRequestTarget, hasDate, hasDigest bool - for _, header := range headers { - hasRequestTarget = hasRequestTarget || header == httpsig.RequestTarget - hasDate = hasDate || header == "Date" - hasDigest = hasDigest || header == "Digest" - } - if !hasRequestTarget { - return fmt.Errorf("missing http header for %s: %s", method, httpsig.RequestTarget) - } else if !hasDate { - return fmt.Errorf("missing http header for %s: Date", method) - } else if !hasDigest && method != http.MethodGet { - return fmt.Errorf("missing http header for %s: Digest", method) - } - return nil -} - -// Client struct -type Client struct { - client *http.Client - algs []httpsig.Algorithm - digestAlg httpsig.DigestAlgorithm - getHeaders []string - postHeaders []string - priv *rsa.PrivateKey - pubID string -} - -// NewClient function -func NewClient(ctx context.Context, user *user_model.User, pubID string) (c *Client, err error) { - if err = containsRequiredHTTPHeaders(http.MethodGet, setting.Federation.GetHeaders); err != nil { - return nil, err - } else if err = containsRequiredHTTPHeaders(http.MethodPost, setting.Federation.PostHeaders); err != nil { - return nil, err - } - - priv, err := GetPrivateKey(ctx, user) - if err != nil { - return nil, err - } - privPem, _ := pem.Decode([]byte(priv)) - privParsed, err := x509.ParsePKCS1PrivateKey(privPem.Bytes) - if err != nil { - return nil, err - } - - c = &Client{ - client: &http.Client{ - Transport: &http.Transport{ - Proxy: proxy.Proxy(), - }, - }, - algs: setting.HttpsigAlgs, - digestAlg: httpsig.DigestAlgorithm(setting.Federation.DigestAlgorithm), - getHeaders: setting.Federation.GetHeaders, - postHeaders: setting.Federation.PostHeaders, - priv: privParsed, - pubID: pubID, - } - return c, err -} - -// NewRequest function -func (c *Client) NewRequest(b []byte, to string) (req *http.Request, err error) { - buf := bytes.NewBuffer(b) - req, err = http.NewRequest(http.MethodPost, to, buf) - if err != nil { - return nil, err - } - req.Header.Add("Content-Type", ActivityStreamsContentType) - req.Header.Add("Date", CurrentTime()) - req.Header.Add("User-Agent", "Gitea/"+setting.AppVer) - signer, _, err := httpsig.NewSigner(c.algs, c.digestAlg, c.postHeaders, httpsig.Signature, httpsigExpirationTime) - if err != nil { - return nil, err - } - err = signer.SignRequest(c.priv, c.pubID, req, b) - return req, err -} - -// Post function -func (c *Client) Post(b []byte, to string) (resp *http.Response, err error) { - var req *http.Request - if req, err = c.NewRequest(b, to); err != nil { - return nil, err - } - resp, err = c.client.Do(req) - return resp, err -} diff --git a/modules/activitypub/client_test.go b/modules/activitypub/client_test.go deleted file mode 100644 index 361270a800..0000000000 --- a/modules/activitypub/client_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package activitypub - -import ( - "fmt" - "io" - "net/http" - "net/http/httptest" - "testing" - - "code.gitea.io/gitea/models/unittest" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/setting" - - "github.com/stretchr/testify/assert" -) - -func TestActivityPubSignedPost(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - pubID := "https://example.com/pubID" - c, err := NewClient(t.Context(), user, pubID) - assert.NoError(t, err) - - expected := "BODY" - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Regexp(t, "^"+setting.Federation.DigestAlgorithm, r.Header.Get("Digest")) - assert.Contains(t, r.Header.Get("Signature"), pubID) - assert.Equal(t, ActivityStreamsContentType, r.Header.Get("Content-Type")) - body, err := io.ReadAll(r.Body) - assert.NoError(t, err) - assert.Equal(t, expected, string(body)) - fmt.Fprint(w, expected) - })) - defer srv.Close() - - r, err := c.Post([]byte(expected), srv.URL) - assert.NoError(t, err) - defer r.Body.Close() - body, err := io.ReadAll(r.Body) - assert.NoError(t, err) - assert.Equal(t, expected, string(body)) -} diff --git a/modules/activitypub/main_test.go b/modules/activitypub/main_test.go deleted file mode 100644 index 4591f1fa55..0000000000 --- a/modules/activitypub/main_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package activitypub - -import ( - "testing" - - "code.gitea.io/gitea/models/unittest" - - _ "code.gitea.io/gitea/models" - _ "code.gitea.io/gitea/models/actions" - _ "code.gitea.io/gitea/models/activities" -) - -func TestMain(m *testing.M) { - unittest.MainTest(m) -} diff --git a/modules/activitypub/user_settings.go b/modules/activitypub/user_settings.go deleted file mode 100644 index 7f939af352..0000000000 --- a/modules/activitypub/user_settings.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package activitypub - -import ( - "context" - - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/util" -) - -const rsaBits = 3072 - -// GetKeyPair function returns a user's private and public keys -func GetKeyPair(ctx context.Context, user *user_model.User) (pub, priv string, err error) { - var settings map[string]*user_model.Setting - settings, err = user_model.GetSettings(ctx, user.ID, []string{user_model.UserActivityPubPrivPem, user_model.UserActivityPubPubPem}) - if err != nil { - return pub, priv, err - } else if len(settings) == 0 { - if priv, pub, err = util.GenerateKeyPair(rsaBits); err != nil { - return pub, priv, err - } - if err = user_model.SetUserSetting(ctx, user.ID, user_model.UserActivityPubPrivPem, priv); err != nil { - return pub, priv, err - } - if err = user_model.SetUserSetting(ctx, user.ID, user_model.UserActivityPubPubPem, pub); err != nil { - return pub, priv, err - } - return pub, priv, err - } - priv = settings[user_model.UserActivityPubPrivPem].SettingValue - pub = settings[user_model.UserActivityPubPubPem].SettingValue - return pub, priv, err -} - -// GetPublicKey function returns a user's public key -func GetPublicKey(ctx context.Context, user *user_model.User) (pub string, err error) { - pub, _, err = GetKeyPair(ctx, user) - return pub, err -} - -// GetPrivateKey function returns a user's private key -func GetPrivateKey(ctx context.Context, user *user_model.User) (priv string, err error) { - _, priv, err = GetKeyPair(ctx, user) - return priv, err -} diff --git a/modules/activitypub/user_settings_test.go b/modules/activitypub/user_settings_test.go deleted file mode 100644 index 105d4aedea..0000000000 --- a/modules/activitypub/user_settings_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package activitypub - -import ( - "testing" - - "code.gitea.io/gitea/models/unittest" - user_model "code.gitea.io/gitea/models/user" - - _ "code.gitea.io/gitea/models" // https://forum.gitea.com/t/testfixtures-could-not-clean-table-access-no-such-table-access/4137/4 - - "github.com/stretchr/testify/assert" -) - -func TestUserSettings(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - pub, priv, err := GetKeyPair(t.Context(), user1) - assert.NoError(t, err) - pub1, err := GetPublicKey(t.Context(), user1) - assert.NoError(t, err) - assert.Equal(t, pub, pub1) - priv1, err := GetPrivateKey(t.Context(), user1) - assert.NoError(t, err) - assert.Equal(t, priv, priv1) -} diff --git a/modules/assetfs/embed.go b/modules/assetfs/embed.go index 0b544635db..7c90e3aca2 100644 --- a/modules/assetfs/embed.go +++ b/modules/assetfs/embed.go @@ -260,7 +260,7 @@ func (fi *embeddedFileInfo) Mode() fs.FileMode { } func (fi *embeddedFileInfo) ModTime() time.Time { - return getExecutableModTime() + return GetExecutableModTime() } func (fi *embeddedFileInfo) IsDir() bool { @@ -279,9 +279,9 @@ func (fi *embeddedFileInfo) Info() (fs.FileInfo, error) { return fi, nil } -// getExecutableModTime returns the modification time of the executable file. +// GetExecutableModTime returns the modification time of the executable file. // In bindata, we can't use the ModTime of the files because we need to make the build reproducible -var getExecutableModTime = sync.OnceValue(func() (modTime time.Time) { +var GetExecutableModTime = sync.OnceValue(func() (modTime time.Time) { exePath, err := os.Executable() if err != nil { return modTime diff --git a/modules/assetfs/layered.go b/modules/assetfs/layered.go index 41e4ca7376..380c3ac455 100644 --- a/modules/assetfs/layered.go +++ b/modules/assetfs/layered.go @@ -9,7 +9,9 @@ import ( "io/fs" "os" "path/filepath" + "slices" "sort" + "strings" "time" "code.gitea.io/gitea/modules/container" @@ -61,6 +63,8 @@ type LayeredFS struct { layers []*Layer } +var _ fs.ReadDirFS = (*LayeredFS)(nil) + // Layered returns a new LayeredFS with the given layers. The first layer is the top layer. func Layered(layers ...*Layer) *LayeredFS { return &LayeredFS{layers: layers} @@ -83,6 +87,27 @@ func (l *LayeredFS) ReadFile(elems ...string) ([]byte, error) { return bs, err } +func (l *LayeredFS) ReadDir(name string) (files []fs.DirEntry, _ error) { + filesMap := map[string]fs.DirEntry{} + for _, layer := range l.layers { + entries, err := readDirOptional(layer, name) + if err != nil { + return nil, err + } + for _, entry := range entries { + entryName := entry.Name() + if _, exist := filesMap[entryName]; !exist && shouldInclude(entry) { + filesMap[entryName] = entry + } + } + } + for _, file := range filesMap { + files = append(files, file) + } + slices.SortFunc(files, func(a, b fs.DirEntry) int { return strings.Compare(a.Name(), b.Name()) }) + return files, nil +} + // ReadLayeredFile reads the named file, and returns the layer name. func (l *LayeredFS) ReadLayeredFile(elems ...string) ([]byte, string, error) { name := util.PathJoinRel(elems...) diff --git a/modules/auth/pam/pam.go b/modules/auth/pam/pam.go index cca1482b1d..a8b608e6be 100644 --- a/modules/auth/pam/pam.go +++ b/modules/auth/pam/pam.go @@ -8,7 +8,7 @@ package pam import ( "errors" - "github.com/msteinert/pam" + "github.com/msteinert/pam/v2" ) // Supported is true when built with PAM @@ -28,6 +28,7 @@ func Auth(serviceName, userName, passwd string) (string, error) { if err != nil { return "", err } + defer t.End() if err = t.Authenticate(0); err != nil { return "", err diff --git a/modules/auth/password/hash/setting.go b/modules/auth/password/hash/setting.go index f0715f31e1..e0caed5ba8 100644 --- a/modules/auth/password/hash/setting.go +++ b/modules/auth/password/hash/setting.go @@ -14,7 +14,7 @@ const DefaultHashAlgorithmName = "pbkdf2" var DefaultHashAlgorithm *PasswordHashAlgorithm -// aliasAlgorithNames provides a mapping between the value of PASSWORD_HASH_ALGO +// aliasAlgorithmNames provides a mapping between the value of PASSWORD_HASH_ALGO // configured in the app.ini and the parameters used within the hashers internally. // // If it is necessary to change the default parameters for any hasher in future you diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go index 106215ec0b..3b622b99af 100644 --- a/modules/avatar/avatar.go +++ b/modules/avatar/avatar.go @@ -28,21 +28,18 @@ import ( // than the size after resizing. const DefaultAvatarSize = 256 -// RandomImageSize generates and returns a random avatar image unique to input data +// RandomImageWithSize generates and returns a random avatar image unique to input data // in custom size (height and width). -func RandomImageSize(size int, data []byte) (image.Image, error) { +func RandomImageWithSize(size int, data []byte) image.Image { // we use white as background, and use dark colors to draw blocks - imgMaker, err := identicon.New(size, color.White, identicon.DarkColors...) - if err != nil { - return nil, fmt.Errorf("identicon.New: %w", err) - } - return imgMaker.Make(data), nil + imgMaker := identicon.New(size, color.White, identicon.DarkColors) + return imgMaker.Make(data) } -// RandomImage generates and returns a random avatar image unique to input data +// RandomImageDefaultSize generates and returns a random avatar image unique to input data // in default size (height and width). -func RandomImage(data []byte) (image.Image, error) { - return RandomImageSize(DefaultAvatarSize*setting.Avatar.RenderedSizeFactor, data) +func RandomImageDefaultSize(data []byte) image.Image { + return RandomImageWithSize(DefaultAvatarSize*setting.Avatar.RenderedSizeFactor, data) } // processAvatarImage process the avatar image data, crop and resize it if necessary. diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go index a5a1a7c1b0..5414184b6b 100644 --- a/modules/avatar/avatar_test.go +++ b/modules/avatar/avatar_test.go @@ -15,19 +15,6 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_RandomImageSize(t *testing.T) { - _, err := RandomImageSize(0, []byte("gitea@local")) - assert.Error(t, err) - - _, err = RandomImageSize(64, []byte("gitea@local")) - assert.NoError(t, err) -} - -func Test_RandomImage(t *testing.T) { - _, err := RandomImage([]byte("gitea@local")) - assert.NoError(t, err) -} - func Test_ProcessAvatarPNG(t *testing.T) { setting.Avatar.MaxWidth = 4096 setting.Avatar.MaxHeight = 4096 @@ -134,3 +121,18 @@ func Test_ProcessAvatarImage(t *testing.T) { _, err = processAvatarImage(origin, 262144) assert.ErrorContains(t, err, "image width is too large: 10 > 5") } + +func BenchmarkRandomImage(b *testing.B) { + b.Run("size-48", func(b *testing.B) { + for b.Loop() { + // BenchmarkRandomImage/size-48-12 49549 22899 ns/op + RandomImageWithSize(48, []byte("test-content")) + } + }) + b.Run("size-96", func(b *testing.B) { + for b.Loop() { + // BenchmarkRandomImage/size-96-12 13816 88187 ns/op + RandomImageWithSize(96, []byte("test-content")) + } + }) +} diff --git a/modules/avatar/identicon/identicon.go b/modules/avatar/identicon/identicon.go index 19f87da85a..a1a0f0d7bf 100644 --- a/modules/avatar/identicon/identicon.go +++ b/modules/avatar/identicon/identicon.go @@ -8,13 +8,14 @@ package identicon import ( "crypto/sha256" - "errors" - "fmt" "image" "image/color" ) -const minImageSize = 16 +const ( + minImageSize = 16 + maxImageSize = 2048 +) // Identicon is used to generate pseudo-random avatars type Identicon struct { @@ -24,25 +25,17 @@ type Identicon struct { rect image.Rectangle } -// New returns an Identicon struct with the correct settings -// size image size -// back background color -// fore all possible foreground colors. only one foreground color will be picked randomly for one image -func New(size int, back color.Color, fore ...color.Color) (*Identicon, error) { - if len(fore) == 0 { - return nil, errors.New("foreground is not set") - } - - if size < minImageSize { - return nil, fmt.Errorf("size %d is smaller than min size %d", size, minImageSize) - } - +// New returns an Identicon struct. +// Only one foreground color will be picked randomly for one image. +func New(size int, backColor color.Color, foreColors []color.Color) *Identicon { + size = max(size, minImageSize) + size = min(size, maxImageSize) return &Identicon{ - foreColors: fore, - backColor: back, + foreColors: foreColors, + backColor: backColor, size: size, rect: image.Rect(0, 0, size, size), - }, nil + } } // Make generates an avatar by data diff --git a/modules/avatar/identicon/identicon_test.go b/modules/avatar/identicon/identicon_test.go index 23bcc73e2e..6af4b41cce 100644 --- a/modules/avatar/identicon/identicon_test.go +++ b/modules/avatar/identicon/identicon_test.go @@ -23,7 +23,7 @@ func TestGenerate(t *testing.T) { } backColor := color.White - imgMaker, err := New(64, backColor, DarkColors...) + imgMaker, err := New(64, backColor, DarkColors) assert.NoError(t, err) for i := 0; i < 100; i++ { s := strconv.Itoa(i) diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go index b7365e40c4..85d28836f1 100644 --- a/modules/base/tool_test.go +++ b/modules/base/tool_test.go @@ -29,8 +29,10 @@ func TestShortSha(t *testing.T) { func TestVerifyTimeLimitCode(t *testing.T) { defer test.MockVariableValue(&setting.InstallLock, true)() initGeneralSecret := func(secret string) { - setting.InstallLock = true setting.CfgProvider, _ = setting.NewConfigProviderFromData(fmt.Sprintf(` +[security] +INTERNAL_TOKEN = dummy +INSTALL_LOCK = true [oauth2] JWT_SECRET = %s `, secret)) diff --git a/modules/charset/ambiguous.go b/modules/charset/ambiguous.go index 96e0561e15..c87d3cfa5a 100644 --- a/modules/charset/ambiguous.go +++ b/modules/charset/ambiguous.go @@ -1,4 +1,3 @@ -// This file is generated by modules/charset/ambiguous/generate.go DO NOT EDIT // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT @@ -14,11 +13,12 @@ import ( // AmbiguousTablesForLocale provides the table of ambiguous characters for this locale. func AmbiguousTablesForLocale(locale translation.Locale) []*AmbiguousTable { + ambiguousTableMap := globalVars().ambiguousTableMap key := locale.Language() var table *AmbiguousTable var ok bool for len(key) > 0 { - if table, ok = AmbiguousCharacters[key]; ok { + if table, ok = ambiguousTableMap[key]; ok { break } idx := strings.LastIndexAny(key, "-_") @@ -29,18 +29,18 @@ func AmbiguousTablesForLocale(locale translation.Locale) []*AmbiguousTable { } } if table == nil && (locale.Language() == "zh-CN" || locale.Language() == "zh_CN") { - table = AmbiguousCharacters["zh-hans"] + table = ambiguousTableMap["zh-hans"] } if table == nil && strings.HasPrefix(locale.Language(), "zh") { - table = AmbiguousCharacters["zh-hant"] + table = ambiguousTableMap["zh-hant"] } if table == nil { - table = AmbiguousCharacters["_default"] + table = ambiguousTableMap["_default"] } return []*AmbiguousTable{ table, - AmbiguousCharacters["_common"], + ambiguousTableMap["_common"], } } @@ -52,7 +52,7 @@ func isAmbiguous(r rune, confusableTo *rune, tables ...*AmbiguousTable) bool { i := sort.Search(len(table.Confusable), func(i int) bool { return table.Confusable[i] >= r }) - (*confusableTo) = table.With[i] + *confusableTo = table.With[i] return true } return false diff --git a/modules/charset/ambiguous/generate.go b/modules/charset/ambiguous/generate.go deleted file mode 100644 index e3fda5be98..0000000000 --- a/modules/charset/ambiguous/generate.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package main - -import ( - "bytes" - "flag" - "fmt" - "go/format" - "os" - "sort" - "text/template" - "unicode" - - "code.gitea.io/gitea/modules/json" - - "golang.org/x/text/unicode/rangetable" -) - -// ambiguous.json provides a one to one mapping of ambiguous characters to other characters -// See https://github.com/hediet/vscode-unicode-data/blob/main/out/ambiguous.json - -type AmbiguousTable struct { - Confusable []rune - With []rune - Locale string - RangeTable *unicode.RangeTable -} - -type RunePair struct { - Confusable rune - With rune -} - -var verbose bool - -func main() { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, `%s: Generate AmbiguousCharacter - -Usage: %[1]s [-v] [-o output.go] ambiguous.json -`, os.Args[0]) - flag.PrintDefaults() - } - - output := "" - flag.BoolVar(&verbose, "v", false, "verbose output") - flag.StringVar(&output, "o", "ambiguous_gen.go", "file to output to") - flag.Parse() - input := flag.Arg(0) - if input == "" { - input = "ambiguous.json" - } - - bs, err := os.ReadFile(input) - if err != nil { - fatalf("Unable to read: %s Err: %v", input, err) - } - - var unwrapped string - if err := json.Unmarshal(bs, &unwrapped); err != nil { - fatalf("Unable to unwrap content in: %s Err: %v", input, err) - } - - fromJSON := map[string][]uint32{} - if err := json.Unmarshal([]byte(unwrapped), &fromJSON); err != nil { - fatalf("Unable to unmarshal content in: %s Err: %v", input, err) - } - - tables := make([]*AmbiguousTable, 0, len(fromJSON)) - for locale, chars := range fromJSON { - table := &AmbiguousTable{Locale: locale} - table.Confusable = make([]rune, 0, len(chars)/2) - table.With = make([]rune, 0, len(chars)/2) - pairs := make([]RunePair, len(chars)/2) - for i := 0; i < len(chars); i += 2 { - pairs[i/2].Confusable, pairs[i/2].With = rune(chars[i]), rune(chars[i+1]) - } - sort.Slice(pairs, func(i, j int) bool { - return pairs[i].Confusable < pairs[j].Confusable - }) - for _, pair := range pairs { - table.Confusable = append(table.Confusable, pair.Confusable) - table.With = append(table.With, pair.With) - } - table.RangeTable = rangetable.New(table.Confusable...) - tables = append(tables, table) - } - sort.Slice(tables, func(i, j int) bool { - return tables[i].Locale < tables[j].Locale - }) - data := map[string]any{ - "Tables": tables, - } - - if err := runTemplate(generatorTemplate, output, &data); err != nil { - fatalf("Unable to run template: %v", err) - } -} - -func runTemplate(t *template.Template, filename string, data any) error { - buf := bytes.NewBuffer(nil) - if err := t.Execute(buf, data); err != nil { - return fmt.Errorf("unable to execute template: %w", err) - } - bs, err := format.Source(buf.Bytes()) - if err != nil { - verbosef("Bad source:\n%s", buf.String()) - return fmt.Errorf("unable to format source: %w", err) - } - - old, err := os.ReadFile(filename) - if err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to read old file %s because %w", filename, err) - } else if err == nil { - if bytes.Equal(bs, old) { - // files are the same don't rewrite it. - return nil - } - } - - file, err := os.Create(filename) - if err != nil { - return fmt.Errorf("failed to create file %s because %w", filename, err) - } - defer file.Close() - _, err = file.Write(bs) - if err != nil { - return fmt.Errorf("unable to write generated source: %w", err) - } - return nil -} - -var generatorTemplate = template.Must(template.New("ambiguousTemplate").Parse(`// This file is generated by modules/charset/ambiguous/generate.go DO NOT EDIT -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - - -package charset - -import "unicode" - -// This file is generated from https://github.com/hediet/vscode-unicode-data/blob/main/out/ambiguous.json - -// AmbiguousTable matches a confusable rune with its partner for the Locale -type AmbiguousTable struct { - Confusable []rune - With []rune - Locale string - RangeTable *unicode.RangeTable -} - -// AmbiguousCharacters provides a map by locale name to the confusable characters in that locale -var AmbiguousCharacters = map[string]*AmbiguousTable{ - {{range .Tables}}{{printf "%q:" .Locale}} { - Confusable: []rune{ {{range .Confusable}}{{.}},{{end}} }, - With: []rune{ {{range .With}}{{.}},{{end}} }, - Locale: {{printf "%q" .Locale}}, - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {{range .RangeTable.R16 }} {Lo:{{.Lo}}, Hi:{{.Hi}}, Stride: {{.Stride}}}, - {{end}} }, - R32: []unicode.Range32{ - {{range .RangeTable.R32}} {Lo:{{.Lo}}, Hi:{{.Hi}}, Stride: {{.Stride}}}, - {{end}} }, - LatinOffset: {{.RangeTable.LatinOffset}}, - }, - }, - {{end}} -} - -`)) - -func logf(format string, args ...any) { - fmt.Fprintf(os.Stderr, format+"\n", args...) -} - -func verbosef(format string, args ...any) { - if verbose { - logf(format, args...) - } -} - -func fatalf(format string, args ...any) { - logf("fatal: "+format+"\n", args...) - os.Exit(1) -} diff --git a/modules/charset/ambiguous_gen.go b/modules/charset/ambiguous_gen.go index c88ffd5aa5..669a46c91a 100644 --- a/modules/charset/ambiguous_gen.go +++ b/modules/charset/ambiguous_gen.go @@ -1,5 +1,5 @@ -// This file is generated by modules/charset/ambiguous/generate.go DO NOT EDIT -// Copyright 2022 The Gitea Authors. All rights reserved. +// This file is generated by modules/charset/generate/generate.go DO NOT EDIT +// Copyright 2026 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package charset @@ -16,821 +16,837 @@ type AmbiguousTable struct { RangeTable *unicode.RangeTable } -// AmbiguousCharacters provides a map by locale name to the confusable characters in that locale -var AmbiguousCharacters = map[string]*AmbiguousTable{ - "_common": { - Confusable: []rune{184, 383, 388, 397, 422, 423, 439, 444, 445, 448, 451, 540, 546, 547, 577, 593, 609, 611, 617, 618, 623, 651, 655, 660, 697, 699, 700, 701, 702, 706, 707, 708, 710, 712, 714, 715, 720, 727, 731, 732, 756, 760, 884, 890, 894, 895, 900, 913, 914, 917, 918, 919, 922, 924, 925, 927, 929, 932, 933, 935, 945, 947, 953, 957, 959, 961, 963, 965, 978, 988, 1000, 1010, 1011, 1017, 1018, 1029, 1030, 1032, 1109, 1110, 1112, 1121, 1140, 1141, 1198, 1199, 1211, 1213, 1216, 1231, 1248, 1281, 1292, 1307, 1308, 1309, 1357, 1359, 1365, 1370, 1373, 1377, 1379, 1382, 1392, 1400, 1404, 1405, 1409, 1412, 1413, 1417, 1472, 1475, 1493, 1496, 1497, 1503, 1505, 1523, 1549, 1575, 1607, 1632, 1633, 1637, 1639, 1643, 1645, 1726, 1729, 1748, 1749, 1776, 1777, 1781, 1783, 1793, 1794, 1795, 1796, 1984, 1994, 2036, 2037, 2042, 2307, 2406, 2429, 2534, 2538, 2541, 2662, 2663, 2666, 2691, 2790, 2819, 2848, 2918, 2920, 3046, 3074, 3174, 3202, 3302, 3330, 3360, 3430, 3437, 3458, 3664, 3792, 4125, 4160, 4327, 4351, 4608, 4816, 5024, 5025, 5026, 5029, 5033, 5034, 5035, 5036, 5038, 5043, 5047, 5051, 5053, 5056, 5058, 5059, 5070, 5071, 5074, 5076, 5077, 5081, 5082, 5086, 5087, 5090, 5094, 5095, 5102, 5107, 5108, 5120, 5167, 5171, 5176, 5194, 5196, 5229, 5231, 5234, 5261, 5290, 5311, 5441, 5500, 5501, 5511, 5551, 5556, 5573, 5598, 5610, 5616, 5623, 5741, 5742, 5760, 5810, 5815, 5825, 5836, 5845, 5846, 5868, 5869, 5941, 6147, 6153, 7428, 7439, 7441, 7452, 7456, 7457, 7458, 7462, 7555, 7564, 7837, 7935, 8125, 8126, 8127, 8128, 8175, 8189, 8190, 8192, 8193, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201, 8202, 8208, 8209, 8210, 8218, 8219, 8228, 8232, 8233, 8239, 8242, 8249, 8250, 8257, 8259, 8260, 8270, 8275, 8282, 8287, 8450, 8458, 8459, 8460, 8461, 8462, 8464, 8465, 8466, 8467, 8469, 8473, 8474, 8475, 8476, 8477, 8484, 8488, 8490, 8492, 8493, 8494, 8495, 8496, 8497, 8499, 8500, 8505, 8509, 8517, 8518, 8519, 8520, 8521, 8544, 8548, 8553, 8556, 8557, 8558, 8559, 8560, 8564, 8569, 8572, 8573, 8574, 8722, 8725, 8726, 8727, 8739, 8744, 8746, 8758, 8764, 8868, 8897, 8899, 8959, 9075, 9076, 9082, 9213, 9585, 9587, 10088, 10089, 10094, 10095, 10098, 10099, 10100, 10101, 10133, 10134, 10187, 10189, 10201, 10539, 10540, 10741, 10744, 10745, 10799, 11397, 11406, 11410, 11412, 11416, 11418, 11422, 11423, 11426, 11427, 11428, 11429, 11430, 11432, 11436, 11450, 11462, 11466, 11468, 11472, 11474, 11576, 11577, 11599, 11601, 11604, 11605, 11613, 11840, 12034, 12035, 12295, 12308, 12309, 12339, 12448, 12755, 12756, 20022, 20031, 42192, 42193, 42194, 42195, 42196, 42198, 42199, 42201, 42202, 42204, 42205, 42207, 42208, 42209, 42210, 42211, 42214, 42215, 42218, 42219, 42220, 42222, 42224, 42226, 42227, 42228, 42232, 42233, 42237, 42239, 42510, 42564, 42567, 42719, 42731, 42735, 42801, 42842, 42858, 42862, 42872, 42889, 42892, 42904, 42905, 42911, 42923, 42930, 42931, 42932, 43826, 43829, 43837, 43847, 43848, 43854, 43858, 43866, 43893, 43905, 43907, 43923, 43945, 43946, 43951, 64422, 64423, 64424, 64425, 64426, 64427, 64428, 64429, 64830, 64831, 65072, 65101, 65102, 65103, 65112, 65128, 65165, 65166, 65257, 65258, 65259, 65260, 65282, 65284, 65285, 65286, 65287, 65290, 65291, 65293, 65294, 65295, 65296, 65297, 65298, 65299, 65300, 65301, 65302, 65303, 65304, 65305, 65308, 65309, 65310, 65312, 65313, 65314, 65315, 65316, 65317, 65318, 65319, 65320, 65321, 65322, 65323, 65324, 65325, 65326, 65327, 65328, 65329, 65330, 65331, 65332, 65333, 65334, 65335, 65336, 65337, 65338, 65339, 65340, 65341, 65342, 65343, 65344, 65345, 65346, 65347, 65348, 65349, 65350, 65351, 65352, 65353, 65354, 65355, 65356, 65357, 65358, 65359, 65360, 65361, 65362, 65363, 65364, 65365, 65366, 65367, 65368, 65369, 65370, 65371, 65372, 65373, 65512, 66178, 66182, 66183, 66186, 66192, 66194, 66197, 66198, 66199, 66203, 66208, 66209, 66210, 66213, 66219, 66224, 66225, 66226, 66228, 66255, 66293, 66305, 66306, 66313, 66321, 66325, 66327, 66330, 66335, 66336, 66338, 66564, 66581, 66587, 66592, 66604, 66621, 66632, 66740, 66754, 66766, 66770, 66794, 66806, 66835, 66838, 66840, 66844, 66845, 66853, 66854, 66855, 68176, 70864, 71430, 71434, 71438, 71439, 71840, 71842, 71843, 71844, 71846, 71849, 71852, 71854, 71855, 71858, 71861, 71864, 71867, 71868, 71872, 71873, 71874, 71875, 71876, 71878, 71880, 71882, 71884, 71893, 71894, 71895, 71896, 71900, 71904, 71909, 71910, 71913, 71916, 71919, 71922, 93960, 93962, 93974, 93992, 94005, 94010, 94011, 94015, 94016, 94018, 94019, 94033, 94034, 119060, 119149, 119302, 119309, 119311, 119314, 119315, 119318, 119338, 119350, 119351, 119354, 119355, 119808, 119809, 119810, 119811, 119812, 119813, 119814, 119815, 119816, 119817, 119818, 119819, 119820, 119821, 119822, 119823, 119824, 119825, 119826, 119827, 119828, 119829, 119830, 119831, 119832, 119833, 119834, 119835, 119836, 119837, 119838, 119839, 119840, 119841, 119842, 119843, 119844, 119845, 119847, 119848, 119849, 119850, 119851, 119852, 119853, 119854, 119855, 119856, 119857, 119858, 119859, 119860, 119861, 119862, 119863, 119864, 119865, 119866, 119867, 119868, 119869, 119870, 119871, 119872, 119873, 119874, 119875, 119876, 119877, 119878, 119879, 119880, 119881, 119882, 119883, 119884, 119885, 119886, 119887, 119888, 119889, 119890, 119891, 119892, 119894, 119895, 119896, 119897, 119899, 119900, 119901, 119902, 119903, 119904, 119905, 119906, 119907, 119908, 119909, 119910, 119911, 119912, 119913, 119914, 119915, 119916, 119917, 119918, 119919, 119920, 119921, 119922, 119923, 119924, 119925, 119926, 119927, 119928, 119929, 119930, 119931, 119932, 119933, 119934, 119935, 119936, 119937, 119938, 119939, 119940, 119941, 119942, 119943, 119944, 119945, 119946, 119947, 119948, 119949, 119951, 119952, 119953, 119954, 119955, 119956, 119957, 119958, 119959, 119960, 119961, 119962, 119963, 119964, 119966, 119967, 119970, 119973, 119974, 119977, 119978, 119979, 119980, 119982, 119983, 119984, 119985, 119986, 119987, 119988, 119989, 119990, 119991, 119992, 119993, 119995, 119997, 119998, 119999, 120000, 120001, 120003, 120005, 120006, 120007, 120008, 120009, 120010, 120011, 120012, 120013, 120014, 120015, 120016, 120017, 120018, 120019, 120020, 120021, 120022, 120023, 120024, 120025, 120026, 120027, 120028, 120029, 120030, 120031, 120032, 120033, 120034, 120035, 120036, 120037, 120038, 120039, 120040, 120041, 120042, 120043, 120044, 120045, 120046, 120047, 120048, 120049, 120050, 120051, 120052, 120053, 120055, 120056, 120057, 120058, 120059, 120060, 120061, 120062, 120063, 120064, 120065, 120066, 120067, 120068, 120069, 120071, 120072, 120073, 120074, 120077, 120078, 120079, 120080, 120081, 120082, 120083, 120084, 120086, 120087, 120088, 120089, 120090, 120091, 120092, 120094, 120095, 120096, 120097, 120098, 120099, 120100, 120101, 120102, 120103, 120104, 120105, 120107, 120108, 120109, 120110, 120111, 120112, 120113, 120114, 120115, 120116, 120117, 120118, 120119, 120120, 120121, 120123, 120124, 120125, 120126, 120128, 120129, 120130, 120131, 120132, 120134, 120138, 120139, 120140, 120141, 120142, 120143, 120144, 120146, 120147, 120148, 120149, 120150, 120151, 120152, 120153, 120154, 120155, 120156, 120157, 120159, 120160, 120161, 120162, 120163, 120164, 120165, 120166, 120167, 120168, 120169, 120170, 120171, 120172, 120173, 120174, 120175, 120176, 120177, 120178, 120179, 120180, 120181, 120182, 120183, 120184, 120185, 120186, 120187, 120188, 120189, 120190, 120191, 120192, 120193, 120194, 120195, 120196, 120197, 120198, 120199, 120200, 120201, 120202, 120203, 120204, 120205, 120206, 120207, 120208, 120209, 120211, 120212, 120213, 120214, 120215, 120216, 120217, 120218, 120219, 120220, 120221, 120222, 120223, 120224, 120225, 120226, 120227, 120228, 120229, 120230, 120231, 120232, 120233, 120234, 120235, 120236, 120237, 120238, 120239, 120240, 120241, 120242, 120243, 120244, 120245, 120246, 120247, 120248, 120249, 120250, 120251, 120252, 120253, 120254, 120255, 120256, 120257, 120258, 120259, 120260, 120261, 120263, 120264, 120265, 120266, 120267, 120268, 120269, 120270, 120271, 120272, 120273, 120274, 120275, 120276, 120277, 120278, 120279, 120280, 120281, 120282, 120283, 120284, 120285, 120286, 120287, 120288, 120289, 120290, 120291, 120292, 120293, 120294, 120295, 120296, 120297, 120298, 120299, 120300, 120301, 120302, 120303, 120304, 120305, 120306, 120307, 120308, 120309, 120310, 120311, 120312, 120313, 120315, 120316, 120317, 120318, 120319, 120320, 120321, 120322, 120323, 120324, 120325, 120326, 120327, 120328, 120329, 120330, 120331, 120332, 120333, 120334, 120335, 120336, 120337, 120338, 120339, 120340, 120341, 120342, 120343, 120344, 120345, 120346, 120347, 120348, 120349, 120350, 120351, 120352, 120353, 120354, 120355, 120356, 120357, 120358, 120359, 120360, 120361, 120362, 120363, 120364, 120365, 120367, 120368, 120369, 120370, 120371, 120372, 120373, 120374, 120375, 120376, 120377, 120378, 120379, 120380, 120381, 120382, 120383, 120384, 120385, 120386, 120387, 120388, 120389, 120390, 120391, 120392, 120393, 120394, 120395, 120396, 120397, 120398, 120399, 120400, 120401, 120402, 120403, 120404, 120405, 120406, 120407, 120408, 120409, 120410, 120411, 120412, 120413, 120414, 120415, 120416, 120417, 120419, 120420, 120421, 120422, 120423, 120424, 120425, 120426, 120427, 120428, 120429, 120430, 120431, 120432, 120433, 120434, 120435, 120436, 120437, 120438, 120439, 120440, 120441, 120442, 120443, 120444, 120445, 120446, 120447, 120448, 120449, 120450, 120451, 120452, 120453, 120454, 120455, 120456, 120457, 120458, 120459, 120460, 120461, 120462, 120463, 120464, 120465, 120466, 120467, 120468, 120469, 120471, 120472, 120473, 120474, 120475, 120476, 120477, 120478, 120479, 120480, 120481, 120482, 120483, 120484, 120488, 120489, 120492, 120493, 120494, 120496, 120497, 120499, 120500, 120502, 120504, 120507, 120508, 120510, 120514, 120516, 120522, 120526, 120528, 120530, 120532, 120534, 120544, 120546, 120547, 120550, 120551, 120552, 120554, 120555, 120557, 120558, 120560, 120562, 120565, 120566, 120568, 120572, 120574, 120580, 120584, 120586, 120588, 120590, 120592, 120602, 120604, 120605, 120608, 120609, 120610, 120612, 120613, 120615, 120616, 120618, 120620, 120623, 120624, 120626, 120630, 120632, 120638, 120642, 120644, 120646, 120648, 120650, 120660, 120662, 120663, 120666, 120667, 120668, 120670, 120671, 120673, 120674, 120676, 120678, 120681, 120682, 120684, 120688, 120690, 120696, 120700, 120702, 120704, 120706, 120708, 120718, 120720, 120721, 120724, 120725, 120726, 120728, 120729, 120731, 120732, 120734, 120736, 120739, 120740, 120742, 120746, 120748, 120754, 120758, 120760, 120762, 120764, 120766, 120776, 120778, 120782, 120783, 120784, 120785, 120786, 120787, 120788, 120789, 120790, 120791, 120792, 120793, 120794, 120795, 120796, 120797, 120798, 120799, 120800, 120801, 120802, 120803, 120804, 120805, 120806, 120807, 120808, 120809, 120810, 120811, 120812, 120813, 120814, 120815, 120816, 120817, 120818, 120819, 120820, 120821, 120822, 120823, 120824, 120825, 120826, 120827, 120828, 120829, 120830, 120831, 125127, 125131, 126464, 126500, 126564, 126592, 126596, 128844, 128872, 130032, 130033, 130034, 130035, 130036, 130037, 130038, 130039, 130040, 130041}, - With: []rune{44, 102, 98, 103, 82, 50, 51, 53, 115, 73, 33, 51, 56, 56, 63, 97, 103, 121, 105, 105, 119, 117, 121, 63, 96, 96, 96, 96, 96, 60, 62, 94, 94, 96, 96, 96, 58, 45, 105, 126, 96, 58, 96, 105, 59, 74, 96, 65, 66, 69, 90, 72, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 89, 70, 50, 99, 106, 67, 77, 83, 73, 74, 115, 105, 106, 119, 86, 118, 89, 121, 104, 101, 73, 105, 51, 100, 71, 113, 87, 119, 85, 83, 79, 96, 96, 119, 113, 113, 104, 110, 110, 117, 103, 102, 111, 58, 108, 58, 108, 118, 96, 108, 111, 96, 44, 108, 111, 46, 108, 111, 86, 44, 42, 111, 111, 45, 111, 46, 73, 111, 86, 46, 46, 58, 58, 79, 108, 96, 96, 95, 58, 111, 63, 79, 56, 57, 111, 57, 56, 58, 111, 56, 79, 79, 57, 111, 111, 111, 111, 111, 111, 111, 111, 57, 111, 111, 111, 111, 111, 121, 111, 85, 79, 68, 82, 84, 105, 89, 65, 74, 69, 63, 87, 77, 72, 89, 71, 104, 90, 52, 98, 82, 87, 83, 86, 83, 76, 67, 80, 75, 100, 54, 71, 66, 61, 86, 62, 60, 96, 85, 80, 100, 98, 74, 76, 50, 120, 72, 120, 82, 98, 70, 65, 68, 68, 77, 66, 88, 120, 32, 60, 88, 73, 96, 75, 77, 58, 43, 47, 58, 58, 99, 111, 111, 117, 118, 119, 122, 114, 103, 121, 102, 121, 96, 105, 96, 126, 96, 96, 96, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 44, 96, 46, 32, 32, 32, 96, 60, 62, 47, 45, 47, 42, 126, 58, 32, 67, 103, 72, 72, 72, 104, 73, 73, 76, 108, 78, 80, 81, 82, 82, 82, 90, 90, 75, 66, 67, 101, 101, 69, 70, 77, 111, 105, 121, 68, 100, 101, 105, 106, 73, 86, 88, 76, 67, 68, 77, 105, 118, 120, 73, 99, 100, 45, 47, 92, 42, 73, 118, 85, 58, 126, 84, 118, 85, 69, 105, 112, 97, 73, 47, 88, 40, 41, 60, 62, 40, 41, 123, 125, 43, 45, 47, 92, 84, 120, 120, 92, 47, 92, 120, 114, 72, 73, 75, 77, 78, 79, 111, 80, 112, 67, 99, 84, 89, 88, 45, 47, 57, 51, 76, 54, 86, 69, 73, 33, 79, 81, 88, 61, 92, 47, 79, 40, 41, 47, 61, 47, 92, 92, 47, 66, 80, 100, 68, 84, 71, 75, 74, 67, 90, 70, 77, 78, 76, 83, 82, 86, 72, 87, 88, 89, 65, 69, 73, 79, 85, 46, 44, 58, 61, 46, 50, 105, 86, 63, 50, 115, 50, 51, 57, 38, 58, 96, 70, 102, 117, 51, 74, 88, 66, 101, 102, 111, 114, 114, 117, 117, 121, 105, 114, 119, 122, 118, 115, 99, 111, 111, 111, 111, 111, 111, 111, 111, 40, 41, 58, 95, 95, 95, 45, 92, 108, 108, 111, 111, 111, 111, 34, 36, 37, 38, 96, 42, 43, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 60, 61, 62, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 73, 66, 69, 70, 124, 88, 79, 80, 83, 84, 43, 65, 66, 67, 70, 79, 77, 84, 89, 88, 72, 90, 66, 67, 124, 77, 84, 88, 56, 42, 108, 88, 79, 67, 76, 83, 111, 99, 115, 82, 79, 85, 55, 111, 117, 78, 79, 75, 67, 86, 70, 76, 88, 46, 79, 118, 119, 119, 119, 86, 70, 76, 89, 69, 90, 57, 69, 52, 76, 79, 85, 53, 84, 118, 115, 70, 105, 122, 55, 111, 51, 57, 54, 57, 111, 117, 121, 79, 90, 87, 67, 88, 87, 67, 86, 84, 76, 73, 82, 83, 51, 62, 65, 85, 89, 96, 96, 123, 46, 51, 86, 92, 55, 70, 82, 76, 60, 62, 47, 92, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 105, 106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 67, 68, 71, 74, 75, 78, 79, 80, 81, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 102, 104, 105, 106, 107, 108, 110, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 68, 69, 70, 71, 74, 75, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 87, 88, 89, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 68, 69, 70, 71, 73, 74, 75, 76, 77, 79, 83, 84, 85, 86, 87, 88, 89, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 105, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 70, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 108, 56, 108, 111, 111, 108, 111, 67, 84, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57}, - Locale: "_common", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 184, Hi: 383, Stride: 199}, - {Lo: 388, Hi: 397, Stride: 9}, - {Lo: 422, Hi: 423, Stride: 1}, - {Lo: 439, Hi: 444, Stride: 5}, - {Lo: 445, Hi: 451, Stride: 3}, - {Lo: 540, Hi: 546, Stride: 6}, - {Lo: 547, Hi: 577, Stride: 30}, - {Lo: 593, Hi: 609, Stride: 16}, - {Lo: 611, Hi: 617, Stride: 6}, - {Lo: 618, Hi: 623, Stride: 5}, - {Lo: 651, Hi: 655, Stride: 4}, - {Lo: 660, Hi: 697, Stride: 37}, - {Lo: 699, Hi: 702, Stride: 1}, - {Lo: 706, Hi: 708, Stride: 1}, - {Lo: 710, Hi: 714, Stride: 2}, - {Lo: 715, Hi: 720, Stride: 5}, - {Lo: 727, Hi: 731, Stride: 4}, - {Lo: 732, Hi: 756, Stride: 24}, - {Lo: 760, Hi: 884, Stride: 124}, - {Lo: 890, Hi: 894, Stride: 4}, - {Lo: 895, Hi: 900, Stride: 5}, - {Lo: 913, Hi: 914, Stride: 1}, - {Lo: 917, Hi: 919, Stride: 1}, - {Lo: 922, Hi: 924, Stride: 2}, - {Lo: 925, Hi: 929, Stride: 2}, - {Lo: 932, Hi: 933, Stride: 1}, - {Lo: 935, Hi: 945, Stride: 10}, - {Lo: 947, Hi: 953, Stride: 6}, - {Lo: 957, Hi: 965, Stride: 2}, - {Lo: 978, Hi: 988, Stride: 10}, - {Lo: 1000, Hi: 1010, Stride: 10}, - {Lo: 1011, Hi: 1017, Stride: 6}, - {Lo: 1018, Hi: 1029, Stride: 11}, - {Lo: 1030, Hi: 1032, Stride: 2}, - {Lo: 1109, Hi: 1110, Stride: 1}, - {Lo: 1112, Hi: 1121, Stride: 9}, - {Lo: 1140, Hi: 1141, Stride: 1}, - {Lo: 1198, Hi: 1199, Stride: 1}, - {Lo: 1211, Hi: 1213, Stride: 2}, - {Lo: 1216, Hi: 1231, Stride: 15}, - {Lo: 1248, Hi: 1281, Stride: 33}, - {Lo: 1292, Hi: 1307, Stride: 15}, - {Lo: 1308, Hi: 1309, Stride: 1}, - {Lo: 1357, Hi: 1359, Stride: 2}, - {Lo: 1365, Hi: 1370, Stride: 5}, - {Lo: 1373, Hi: 1377, Stride: 4}, - {Lo: 1379, Hi: 1382, Stride: 3}, - {Lo: 1392, Hi: 1400, Stride: 8}, - {Lo: 1404, Hi: 1405, Stride: 1}, - {Lo: 1409, Hi: 1412, Stride: 3}, - {Lo: 1413, Hi: 1417, Stride: 4}, - {Lo: 1472, Hi: 1475, Stride: 3}, - {Lo: 1493, Hi: 1496, Stride: 3}, - {Lo: 1497, Hi: 1503, Stride: 6}, - {Lo: 1505, Hi: 1523, Stride: 18}, - {Lo: 1549, Hi: 1575, Stride: 26}, - {Lo: 1607, Hi: 1632, Stride: 25}, - {Lo: 1633, Hi: 1637, Stride: 4}, - {Lo: 1639, Hi: 1643, Stride: 4}, - {Lo: 1645, Hi: 1726, Stride: 81}, - {Lo: 1729, Hi: 1748, Stride: 19}, - {Lo: 1749, Hi: 1776, Stride: 27}, - {Lo: 1777, Hi: 1781, Stride: 4}, - {Lo: 1783, Hi: 1793, Stride: 10}, - {Lo: 1794, Hi: 1796, Stride: 1}, - {Lo: 1984, Hi: 1994, Stride: 10}, - {Lo: 2036, Hi: 2037, Stride: 1}, - {Lo: 2042, Hi: 2307, Stride: 265}, - {Lo: 2406, Hi: 2429, Stride: 23}, - {Lo: 2534, Hi: 2538, Stride: 4}, - {Lo: 2541, Hi: 2662, Stride: 121}, - {Lo: 2663, Hi: 2666, Stride: 3}, - {Lo: 2691, Hi: 2790, Stride: 99}, - {Lo: 2819, Hi: 2848, Stride: 29}, - {Lo: 2918, Hi: 2920, Stride: 2}, - {Lo: 3046, Hi: 3074, Stride: 28}, - {Lo: 3174, Hi: 3202, Stride: 28}, - {Lo: 3302, Hi: 3330, Stride: 28}, - {Lo: 3360, Hi: 3430, Stride: 70}, - {Lo: 3437, Hi: 3458, Stride: 21}, - {Lo: 3664, Hi: 3792, Stride: 128}, - {Lo: 4125, Hi: 4160, Stride: 35}, - {Lo: 4327, Hi: 4351, Stride: 24}, - {Lo: 4608, Hi: 5024, Stride: 208}, - {Lo: 5025, Hi: 5026, Stride: 1}, - {Lo: 5029, Hi: 5033, Stride: 4}, - {Lo: 5034, Hi: 5036, Stride: 1}, - {Lo: 5038, Hi: 5043, Stride: 5}, - {Lo: 5047, Hi: 5051, Stride: 4}, - {Lo: 5053, Hi: 5056, Stride: 3}, - {Lo: 5058, Hi: 5059, Stride: 1}, - {Lo: 5070, Hi: 5071, Stride: 1}, - {Lo: 5074, Hi: 5076, Stride: 2}, - {Lo: 5077, Hi: 5081, Stride: 4}, - {Lo: 5082, Hi: 5086, Stride: 4}, - {Lo: 5087, Hi: 5090, Stride: 3}, - {Lo: 5094, Hi: 5095, Stride: 1}, - {Lo: 5102, Hi: 5107, Stride: 5}, - {Lo: 5108, Hi: 5120, Stride: 12}, - {Lo: 5167, Hi: 5171, Stride: 4}, - {Lo: 5176, Hi: 5194, Stride: 18}, - {Lo: 5196, Hi: 5229, Stride: 33}, - {Lo: 5231, Hi: 5234, Stride: 3}, - {Lo: 5261, Hi: 5290, Stride: 29}, - {Lo: 5311, Hi: 5441, Stride: 130}, - {Lo: 5500, Hi: 5501, Stride: 1}, - {Lo: 5511, Hi: 5551, Stride: 40}, - {Lo: 5556, Hi: 5573, Stride: 17}, - {Lo: 5598, Hi: 5610, Stride: 12}, - {Lo: 5616, Hi: 5623, Stride: 7}, - {Lo: 5741, Hi: 5742, Stride: 1}, - {Lo: 5760, Hi: 5810, Stride: 50}, - {Lo: 5815, Hi: 5825, Stride: 10}, - {Lo: 5836, Hi: 5845, Stride: 9}, - {Lo: 5846, Hi: 5868, Stride: 22}, - {Lo: 5869, Hi: 5941, Stride: 72}, - {Lo: 6147, Hi: 6153, Stride: 6}, - {Lo: 7428, Hi: 7439, Stride: 11}, - {Lo: 7441, Hi: 7452, Stride: 11}, - {Lo: 7456, Hi: 7458, Stride: 1}, - {Lo: 7462, Hi: 7555, Stride: 93}, - {Lo: 7564, Hi: 7837, Stride: 273}, - {Lo: 7935, Hi: 8125, Stride: 190}, - {Lo: 8126, Hi: 8128, Stride: 1}, - {Lo: 8175, Hi: 8189, Stride: 14}, - {Lo: 8190, Hi: 8192, Stride: 2}, - {Lo: 8193, Hi: 8202, Stride: 1}, - {Lo: 8208, Hi: 8210, Stride: 1}, - {Lo: 8218, Hi: 8219, Stride: 1}, - {Lo: 8228, Hi: 8232, Stride: 4}, - {Lo: 8233, Hi: 8239, Stride: 6}, - {Lo: 8242, Hi: 8249, Stride: 7}, - {Lo: 8250, Hi: 8257, Stride: 7}, - {Lo: 8259, Hi: 8260, Stride: 1}, - {Lo: 8270, Hi: 8275, Stride: 5}, - {Lo: 8282, Hi: 8287, Stride: 5}, - {Lo: 8450, Hi: 8458, Stride: 8}, - {Lo: 8459, Hi: 8462, Stride: 1}, - {Lo: 8464, Hi: 8467, Stride: 1}, - {Lo: 8469, Hi: 8473, Stride: 4}, - {Lo: 8474, Hi: 8477, Stride: 1}, - {Lo: 8484, Hi: 8488, Stride: 4}, - {Lo: 8490, Hi: 8492, Stride: 2}, - {Lo: 8493, Hi: 8497, Stride: 1}, - {Lo: 8499, Hi: 8500, Stride: 1}, - {Lo: 8505, Hi: 8509, Stride: 4}, - {Lo: 8517, Hi: 8521, Stride: 1}, - {Lo: 8544, Hi: 8548, Stride: 4}, - {Lo: 8553, Hi: 8556, Stride: 3}, - {Lo: 8557, Hi: 8560, Stride: 1}, - {Lo: 8564, Hi: 8569, Stride: 5}, - {Lo: 8572, Hi: 8574, Stride: 1}, - {Lo: 8722, Hi: 8725, Stride: 3}, - {Lo: 8726, Hi: 8727, Stride: 1}, - {Lo: 8739, Hi: 8744, Stride: 5}, - {Lo: 8746, Hi: 8758, Stride: 12}, - {Lo: 8764, Hi: 8868, Stride: 104}, - {Lo: 8897, Hi: 8899, Stride: 2}, - {Lo: 8959, Hi: 9075, Stride: 116}, - {Lo: 9076, Hi: 9082, Stride: 6}, - {Lo: 9213, Hi: 9585, Stride: 372}, - {Lo: 9587, Hi: 10088, Stride: 501}, - {Lo: 10089, Hi: 10094, Stride: 5}, - {Lo: 10095, Hi: 10098, Stride: 3}, - {Lo: 10099, Hi: 10101, Stride: 1}, - {Lo: 10133, Hi: 10134, Stride: 1}, - {Lo: 10187, Hi: 10189, Stride: 2}, - {Lo: 10201, Hi: 10539, Stride: 338}, - {Lo: 10540, Hi: 10741, Stride: 201}, - {Lo: 10744, Hi: 10745, Stride: 1}, - {Lo: 10799, Hi: 11397, Stride: 598}, - {Lo: 11406, Hi: 11410, Stride: 4}, - {Lo: 11412, Hi: 11416, Stride: 4}, - {Lo: 11418, Hi: 11422, Stride: 4}, - {Lo: 11423, Hi: 11426, Stride: 3}, - {Lo: 11427, Hi: 11430, Stride: 1}, - {Lo: 11432, Hi: 11436, Stride: 4}, - {Lo: 11450, Hi: 11462, Stride: 12}, - {Lo: 11466, Hi: 11468, Stride: 2}, - {Lo: 11472, Hi: 11474, Stride: 2}, - {Lo: 11576, Hi: 11577, Stride: 1}, - {Lo: 11599, Hi: 11601, Stride: 2}, - {Lo: 11604, Hi: 11605, Stride: 1}, - {Lo: 11613, Hi: 11840, Stride: 227}, - {Lo: 12034, Hi: 12035, Stride: 1}, - {Lo: 12295, Hi: 12308, Stride: 13}, - {Lo: 12309, Hi: 12339, Stride: 30}, - {Lo: 12448, Hi: 12755, Stride: 307}, - {Lo: 12756, Hi: 20022, Stride: 7266}, - {Lo: 20031, Hi: 42192, Stride: 22161}, - {Lo: 42193, Hi: 42196, Stride: 1}, - {Lo: 42198, Hi: 42199, Stride: 1}, - {Lo: 42201, Hi: 42202, Stride: 1}, - {Lo: 42204, Hi: 42205, Stride: 1}, - {Lo: 42207, Hi: 42211, Stride: 1}, - {Lo: 42214, Hi: 42215, Stride: 1}, - {Lo: 42218, Hi: 42220, Stride: 1}, - {Lo: 42222, Hi: 42226, Stride: 2}, - {Lo: 42227, Hi: 42228, Stride: 1}, - {Lo: 42232, Hi: 42233, Stride: 1}, - {Lo: 42237, Hi: 42239, Stride: 2}, - {Lo: 42510, Hi: 42564, Stride: 54}, - {Lo: 42567, Hi: 42719, Stride: 152}, - {Lo: 42731, Hi: 42735, Stride: 4}, - {Lo: 42801, Hi: 42842, Stride: 41}, - {Lo: 42858, Hi: 42862, Stride: 4}, - {Lo: 42872, Hi: 42889, Stride: 17}, - {Lo: 42892, Hi: 42904, Stride: 12}, - {Lo: 42905, Hi: 42911, Stride: 6}, - {Lo: 42923, Hi: 42930, Stride: 7}, - {Lo: 42931, Hi: 42932, Stride: 1}, - {Lo: 43826, Hi: 43829, Stride: 3}, - {Lo: 43837, Hi: 43847, Stride: 10}, - {Lo: 43848, Hi: 43854, Stride: 6}, - {Lo: 43858, Hi: 43866, Stride: 8}, - {Lo: 43893, Hi: 43905, Stride: 12}, - {Lo: 43907, Hi: 43923, Stride: 16}, - {Lo: 43945, Hi: 43946, Stride: 1}, - {Lo: 43951, Hi: 64422, Stride: 20471}, - {Lo: 64423, Hi: 64429, Stride: 1}, - {Lo: 64830, Hi: 64831, Stride: 1}, - {Lo: 65072, Hi: 65101, Stride: 29}, - {Lo: 65102, Hi: 65103, Stride: 1}, - {Lo: 65112, Hi: 65128, Stride: 16}, - {Lo: 65165, Hi: 65166, Stride: 1}, - {Lo: 65257, Hi: 65260, Stride: 1}, - {Lo: 65282, Hi: 65284, Stride: 2}, - {Lo: 65285, Hi: 65287, Stride: 1}, - {Lo: 65290, Hi: 65291, Stride: 1}, - {Lo: 65293, Hi: 65305, Stride: 1}, - {Lo: 65308, Hi: 65310, Stride: 1}, - {Lo: 65312, Hi: 65373, Stride: 1}, - {Lo: 65512, Hi: 65512, Stride: 1}, +func newAmbiguousTableMap() map[string]*AmbiguousTable { + return map[string]*AmbiguousTable{ + "_common": { + Confusable: []rune{184, 383, 388, 397, 422, 423, 439, 444, 445, 448, 451, 540, 546, 547, 577, 593, 609, 611, 617, 618, 623, 651, 655, 660, 697, 699, 700, 701, 702, 706, 707, 708, 710, 712, 714, 715, 720, 727, 731, 732, 756, 760, 884, 890, 894, 895, 900, 913, 914, 917, 918, 919, 922, 924, 925, 927, 929, 932, 933, 935, 945, 947, 953, 957, 959, 961, 963, 965, 978, 988, 1000, 1010, 1011, 1017, 1018, 1029, 1030, 1032, 1109, 1110, 1112, 1121, 1140, 1141, 1198, 1199, 1211, 1213, 1216, 1231, 1248, 1281, 1292, 1307, 1308, 1309, 1357, 1359, 1365, 1370, 1373, 1377, 1379, 1382, 1392, 1400, 1404, 1405, 1409, 1412, 1413, 1417, 1472, 1475, 1493, 1496, 1497, 1503, 1505, 1523, 1549, 1575, 1607, 1632, 1633, 1637, 1639, 1643, 1645, 1726, 1729, 1748, 1749, 1776, 1777, 1781, 1783, 1793, 1794, 1795, 1796, 1984, 1994, 2036, 2037, 2042, 2307, 2406, 2429, 2534, 2538, 2541, 2662, 2663, 2666, 2691, 2790, 2819, 2848, 2918, 2920, 3046, 3074, 3174, 3202, 3302, 3330, 3360, 3430, 3437, 3458, 3664, 3792, 4125, 4160, 4327, 4351, 4608, 4816, 5024, 5025, 5026, 5029, 5033, 5034, 5035, 5036, 5038, 5043, 5047, 5051, 5053, 5056, 5058, 5059, 5070, 5071, 5074, 5076, 5077, 5081, 5082, 5086, 5087, 5090, 5094, 5095, 5102, 5107, 5108, 5120, 5167, 5171, 5176, 5194, 5196, 5229, 5231, 5234, 5261, 5290, 5311, 5441, 5500, 5501, 5511, 5551, 5556, 5573, 5598, 5610, 5616, 5623, 5741, 5742, 5760, 5810, 5815, 5825, 5836, 5845, 5846, 5868, 5869, 5941, 6147, 6153, 7428, 7439, 7441, 7452, 7456, 7457, 7458, 7462, 7555, 7564, 7837, 7935, 8125, 8126, 8127, 8128, 8175, 8189, 8190, 8192, 8193, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201, 8202, 8208, 8209, 8210, 8218, 8219, 8228, 8232, 8233, 8239, 8242, 8249, 8250, 8257, 8259, 8260, 8270, 8275, 8282, 8287, 8450, 8458, 8459, 8460, 8461, 8462, 8464, 8465, 8466, 8467, 8469, 8473, 8474, 8475, 8476, 8477, 8484, 8488, 8490, 8492, 8493, 8494, 8495, 8496, 8497, 8499, 8500, 8505, 8509, 8517, 8518, 8519, 8520, 8521, 8544, 8548, 8553, 8556, 8557, 8558, 8559, 8560, 8564, 8569, 8572, 8573, 8574, 8722, 8725, 8726, 8727, 8739, 8744, 8746, 8758, 8764, 8868, 8897, 8899, 8959, 9075, 9076, 9082, 9213, 9585, 9587, 10088, 10089, 10094, 10095, 10098, 10099, 10100, 10101, 10133, 10134, 10187, 10189, 10201, 10539, 10540, 10741, 10744, 10745, 10799, 11397, 11406, 11410, 11412, 11416, 11418, 11422, 11423, 11426, 11427, 11428, 11429, 11430, 11432, 11436, 11450, 11462, 11466, 11468, 11472, 11474, 11576, 11577, 11599, 11601, 11604, 11605, 11613, 11840, 12034, 12035, 12295, 12308, 12309, 12339, 12448, 12755, 12756, 20022, 20031, 42192, 42193, 42194, 42195, 42196, 42198, 42199, 42201, 42202, 42204, 42205, 42207, 42208, 42209, 42210, 42211, 42214, 42215, 42218, 42219, 42220, 42222, 42224, 42226, 42227, 42228, 42232, 42233, 42237, 42239, 42510, 42564, 42567, 42719, 42731, 42735, 42801, 42842, 42858, 42862, 42872, 42889, 42892, 42904, 42905, 42911, 42923, 42930, 42931, 42932, 43826, 43829, 43837, 43847, 43848, 43854, 43858, 43866, 43893, 43905, 43907, 43923, 43945, 43946, 43951, 64422, 64423, 64424, 64425, 64426, 64427, 64428, 64429, 64830, 64831, 65072, 65101, 65102, 65103, 65112, 65128, 65165, 65166, 65257, 65258, 65259, 65260, 65282, 65284, 65285, 65286, 65287, 65290, 65291, 65293, 65294, 65295, 65296, 65297, 65298, 65299, 65300, 65301, 65302, 65303, 65304, 65305, 65308, 65309, 65310, 65312, 65313, 65314, 65315, 65316, 65317, 65318, 65319, 65320, 65321, 65322, 65323, 65324, 65325, 65326, 65327, 65328, 65329, 65330, 65331, 65332, 65333, 65334, 65335, 65336, 65337, 65338, 65339, 65340, 65341, 65342, 65343, 65344, 65345, 65346, 65347, 65348, 65349, 65350, 65351, 65352, 65353, 65354, 65355, 65356, 65357, 65358, 65359, 65360, 65361, 65362, 65363, 65364, 65365, 65366, 65367, 65368, 65369, 65370, 65371, 65372, 65373, 65512, 66178, 66182, 66183, 66186, 66192, 66194, 66197, 66198, 66199, 66203, 66208, 66209, 66210, 66213, 66219, 66224, 66225, 66226, 66228, 66255, 66293, 66305, 66306, 66313, 66321, 66325, 66327, 66330, 66335, 66336, 66338, 66564, 66581, 66587, 66592, 66604, 66621, 66632, 66740, 66754, 66766, 66770, 66794, 66806, 66835, 66838, 66840, 66844, 66845, 66853, 66854, 66855, 68176, 70864, 71430, 71434, 71438, 71439, 71840, 71842, 71843, 71844, 71846, 71849, 71852, 71854, 71855, 71858, 71861, 71864, 71867, 71868, 71872, 71873, 71874, 71875, 71876, 71878, 71880, 71882, 71884, 71893, 71894, 71895, 71896, 71900, 71904, 71909, 71910, 71913, 71916, 71919, 71922, 93960, 93962, 93974, 93992, 94005, 94010, 94011, 94015, 94016, 94018, 94019, 94033, 94034, 119060, 119149, 119302, 119309, 119311, 119314, 119315, 119318, 119338, 119350, 119351, 119354, 119355, 119808, 119809, 119810, 119811, 119812, 119813, 119814, 119815, 119816, 119817, 119818, 119819, 119820, 119821, 119822, 119823, 119824, 119825, 119826, 119827, 119828, 119829, 119830, 119831, 119832, 119833, 119834, 119835, 119836, 119837, 119838, 119839, 119840, 119841, 119842, 119843, 119844, 119845, 119847, 119848, 119849, 119850, 119851, 119852, 119853, 119854, 119855, 119856, 119857, 119858, 119859, 119860, 119861, 119862, 119863, 119864, 119865, 119866, 119867, 119868, 119869, 119870, 119871, 119872, 119873, 119874, 119875, 119876, 119877, 119878, 119879, 119880, 119881, 119882, 119883, 119884, 119885, 119886, 119887, 119888, 119889, 119890, 119891, 119892, 119894, 119895, 119896, 119897, 119899, 119900, 119901, 119902, 119903, 119904, 119905, 119906, 119907, 119908, 119909, 119910, 119911, 119912, 119913, 119914, 119915, 119916, 119917, 119918, 119919, 119920, 119921, 119922, 119923, 119924, 119925, 119926, 119927, 119928, 119929, 119930, 119931, 119932, 119933, 119934, 119935, 119936, 119937, 119938, 119939, 119940, 119941, 119942, 119943, 119944, 119945, 119946, 119947, 119948, 119949, 119951, 119952, 119953, 119954, 119955, 119956, 119957, 119958, 119959, 119960, 119961, 119962, 119963, 119964, 119966, 119967, 119970, 119973, 119974, 119977, 119978, 119979, 119980, 119982, 119983, 119984, 119985, 119986, 119987, 119988, 119989, 119990, 119991, 119992, 119993, 119995, 119997, 119998, 119999, 120000, 120001, 120003, 120005, 120006, 120007, 120008, 120009, 120010, 120011, 120012, 120013, 120014, 120015, 120016, 120017, 120018, 120019, 120020, 120021, 120022, 120023, 120024, 120025, 120026, 120027, 120028, 120029, 120030, 120031, 120032, 120033, 120034, 120035, 120036, 120037, 120038, 120039, 120040, 120041, 120042, 120043, 120044, 120045, 120046, 120047, 120048, 120049, 120050, 120051, 120052, 120053, 120055, 120056, 120057, 120058, 120059, 120060, 120061, 120062, 120063, 120064, 120065, 120066, 120067, 120068, 120069, 120071, 120072, 120073, 120074, 120077, 120078, 120079, 120080, 120081, 120082, 120083, 120084, 120086, 120087, 120088, 120089, 120090, 120091, 120092, 120094, 120095, 120096, 120097, 120098, 120099, 120100, 120101, 120102, 120103, 120104, 120105, 120107, 120108, 120109, 120110, 120111, 120112, 120113, 120114, 120115, 120116, 120117, 120118, 120119, 120120, 120121, 120123, 120124, 120125, 120126, 120128, 120129, 120130, 120131, 120132, 120134, 120138, 120139, 120140, 120141, 120142, 120143, 120144, 120146, 120147, 120148, 120149, 120150, 120151, 120152, 120153, 120154, 120155, 120156, 120157, 120159, 120160, 120161, 120162, 120163, 120164, 120165, 120166, 120167, 120168, 120169, 120170, 120171, 120172, 120173, 120174, 120175, 120176, 120177, 120178, 120179, 120180, 120181, 120182, 120183, 120184, 120185, 120186, 120187, 120188, 120189, 120190, 120191, 120192, 120193, 120194, 120195, 120196, 120197, 120198, 120199, 120200, 120201, 120202, 120203, 120204, 120205, 120206, 120207, 120208, 120209, 120211, 120212, 120213, 120214, 120215, 120216, 120217, 120218, 120219, 120220, 120221, 120222, 120223, 120224, 120225, 120226, 120227, 120228, 120229, 120230, 120231, 120232, 120233, 120234, 120235, 120236, 120237, 120238, 120239, 120240, 120241, 120242, 120243, 120244, 120245, 120246, 120247, 120248, 120249, 120250, 120251, 120252, 120253, 120254, 120255, 120256, 120257, 120258, 120259, 120260, 120261, 120263, 120264, 120265, 120266, 120267, 120268, 120269, 120270, 120271, 120272, 120273, 120274, 120275, 120276, 120277, 120278, 120279, 120280, 120281, 120282, 120283, 120284, 120285, 120286, 120287, 120288, 120289, 120290, 120291, 120292, 120293, 120294, 120295, 120296, 120297, 120298, 120299, 120300, 120301, 120302, 120303, 120304, 120305, 120306, 120307, 120308, 120309, 120310, 120311, 120312, 120313, 120315, 120316, 120317, 120318, 120319, 120320, 120321, 120322, 120323, 120324, 120325, 120326, 120327, 120328, 120329, 120330, 120331, 120332, 120333, 120334, 120335, 120336, 120337, 120338, 120339, 120340, 120341, 120342, 120343, 120344, 120345, 120346, 120347, 120348, 120349, 120350, 120351, 120352, 120353, 120354, 120355, 120356, 120357, 120358, 120359, 120360, 120361, 120362, 120363, 120364, 120365, 120367, 120368, 120369, 120370, 120371, 120372, 120373, 120374, 120375, 120376, 120377, 120378, 120379, 120380, 120381, 120382, 120383, 120384, 120385, 120386, 120387, 120388, 120389, 120390, 120391, 120392, 120393, 120394, 120395, 120396, 120397, 120398, 120399, 120400, 120401, 120402, 120403, 120404, 120405, 120406, 120407, 120408, 120409, 120410, 120411, 120412, 120413, 120414, 120415, 120416, 120417, 120419, 120420, 120421, 120422, 120423, 120424, 120425, 120426, 120427, 120428, 120429, 120430, 120431, 120432, 120433, 120434, 120435, 120436, 120437, 120438, 120439, 120440, 120441, 120442, 120443, 120444, 120445, 120446, 120447, 120448, 120449, 120450, 120451, 120452, 120453, 120454, 120455, 120456, 120457, 120458, 120459, 120460, 120461, 120462, 120463, 120464, 120465, 120466, 120467, 120468, 120469, 120471, 120472, 120473, 120474, 120475, 120476, 120477, 120478, 120479, 120480, 120481, 120482, 120483, 120484, 120488, 120489, 120492, 120493, 120494, 120496, 120497, 120499, 120500, 120502, 120504, 120507, 120508, 120510, 120514, 120516, 120522, 120526, 120528, 120530, 120532, 120534, 120544, 120546, 120547, 120550, 120551, 120552, 120554, 120555, 120557, 120558, 120560, 120562, 120565, 120566, 120568, 120572, 120574, 120580, 120584, 120586, 120588, 120590, 120592, 120602, 120604, 120605, 120608, 120609, 120610, 120612, 120613, 120615, 120616, 120618, 120620, 120623, 120624, 120626, 120630, 120632, 120638, 120642, 120644, 120646, 120648, 120650, 120660, 120662, 120663, 120666, 120667, 120668, 120670, 120671, 120673, 120674, 120676, 120678, 120681, 120682, 120684, 120688, 120690, 120696, 120700, 120702, 120704, 120706, 120708, 120718, 120720, 120721, 120724, 120725, 120726, 120728, 120729, 120731, 120732, 120734, 120736, 120739, 120740, 120742, 120746, 120748, 120754, 120758, 120760, 120762, 120764, 120766, 120776, 120778, 120782, 120783, 120784, 120785, 120786, 120787, 120788, 120789, 120790, 120791, 120792, 120793, 120794, 120795, 120796, 120797, 120798, 120799, 120800, 120801, 120802, 120803, 120804, 120805, 120806, 120807, 120808, 120809, 120810, 120811, 120812, 120813, 120814, 120815, 120816, 120817, 120818, 120819, 120820, 120821, 120822, 120823, 120824, 120825, 120826, 120827, 120828, 120829, 120830, 120831, 125127, 125131, 126464, 126500, 126564, 126592, 126596, 128844, 128872, 130032, 130033, 130034, 130035, 130036, 130037, 130038, 130039, 130040, 130041}, + With: []rune{44, 102, 98, 103, 82, 50, 51, 53, 115, 73, 33, 51, 56, 56, 63, 97, 103, 121, 105, 105, 119, 117, 121, 63, 96, 96, 96, 96, 96, 60, 62, 94, 94, 96, 96, 96, 58, 45, 105, 126, 96, 58, 96, 105, 59, 74, 96, 65, 66, 69, 90, 72, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 89, 70, 50, 99, 106, 67, 77, 83, 73, 74, 115, 105, 106, 119, 86, 118, 89, 121, 104, 101, 73, 105, 51, 100, 71, 113, 87, 119, 85, 83, 79, 96, 96, 119, 113, 113, 104, 110, 110, 117, 103, 102, 111, 58, 108, 58, 108, 118, 96, 108, 111, 96, 44, 108, 111, 46, 108, 111, 86, 44, 42, 111, 111, 45, 111, 46, 73, 111, 86, 46, 46, 58, 58, 79, 108, 96, 96, 95, 58, 111, 63, 79, 56, 57, 111, 57, 56, 58, 111, 56, 79, 79, 57, 111, 111, 111, 111, 111, 111, 111, 111, 57, 111, 111, 111, 111, 111, 121, 111, 85, 79, 68, 82, 84, 105, 89, 65, 74, 69, 63, 87, 77, 72, 89, 71, 104, 90, 52, 98, 82, 87, 83, 86, 83, 76, 67, 80, 75, 100, 54, 71, 66, 61, 86, 62, 60, 96, 85, 80, 100, 98, 74, 76, 50, 120, 72, 120, 82, 98, 70, 65, 68, 68, 77, 66, 88, 120, 32, 60, 88, 73, 96, 75, 77, 58, 43, 47, 58, 58, 99, 111, 111, 117, 118, 119, 122, 114, 103, 121, 102, 121, 96, 105, 96, 126, 96, 96, 96, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 44, 96, 46, 32, 32, 32, 96, 60, 62, 47, 45, 47, 42, 126, 58, 32, 67, 103, 72, 72, 72, 104, 73, 73, 76, 108, 78, 80, 81, 82, 82, 82, 90, 90, 75, 66, 67, 101, 101, 69, 70, 77, 111, 105, 121, 68, 100, 101, 105, 106, 73, 86, 88, 76, 67, 68, 77, 105, 118, 120, 73, 99, 100, 45, 47, 92, 42, 73, 118, 85, 58, 126, 84, 118, 85, 69, 105, 112, 97, 73, 47, 88, 40, 41, 60, 62, 40, 41, 123, 125, 43, 45, 47, 92, 84, 120, 120, 92, 47, 92, 120, 114, 72, 73, 75, 77, 78, 79, 111, 80, 112, 67, 99, 84, 89, 88, 45, 47, 57, 51, 76, 54, 86, 69, 73, 33, 79, 81, 88, 61, 92, 47, 79, 40, 41, 47, 61, 47, 92, 92, 47, 66, 80, 100, 68, 84, 71, 75, 74, 67, 90, 70, 77, 78, 76, 83, 82, 86, 72, 87, 88, 89, 65, 69, 73, 79, 85, 46, 44, 58, 61, 46, 50, 105, 86, 63, 50, 115, 50, 51, 57, 38, 58, 96, 70, 102, 117, 51, 74, 88, 66, 101, 102, 111, 114, 114, 117, 117, 121, 105, 114, 119, 122, 118, 115, 99, 111, 111, 111, 111, 111, 111, 111, 111, 40, 41, 58, 95, 95, 95, 45, 92, 108, 108, 111, 111, 111, 111, 34, 36, 37, 38, 96, 42, 43, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 60, 61, 62, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 73, 66, 69, 70, 124, 88, 79, 80, 83, 84, 43, 65, 66, 67, 70, 79, 77, 84, 89, 88, 72, 90, 66, 67, 124, 77, 84, 88, 56, 42, 108, 88, 79, 67, 76, 83, 111, 99, 115, 82, 79, 85, 55, 111, 117, 78, 79, 75, 67, 86, 70, 76, 88, 46, 79, 118, 119, 119, 119, 86, 70, 76, 89, 69, 90, 57, 69, 52, 76, 79, 85, 53, 84, 118, 115, 70, 105, 122, 55, 111, 51, 57, 54, 57, 111, 117, 121, 79, 90, 87, 67, 88, 87, 67, 86, 84, 76, 73, 82, 83, 51, 62, 65, 85, 89, 96, 96, 123, 46, 51, 86, 92, 55, 70, 82, 76, 60, 62, 47, 92, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 105, 106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 67, 68, 71, 74, 75, 78, 79, 80, 81, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 102, 104, 105, 106, 107, 108, 110, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 68, 69, 70, 71, 74, 75, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 87, 88, 89, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 68, 69, 70, 71, 73, 74, 75, 76, 77, 79, 83, 84, 85, 86, 87, 88, 89, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 73, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 105, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 65, 66, 69, 90, 72, 73, 75, 77, 78, 79, 80, 84, 89, 88, 97, 121, 105, 118, 111, 112, 111, 117, 112, 70, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57, 108, 56, 108, 111, 111, 108, 111, 67, 84, 79, 73, 50, 51, 52, 53, 54, 55, 56, 57}, + Locale: "_common", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 184, Hi: 383, Stride: 199}, + {Lo: 388, Hi: 397, Stride: 9}, + {Lo: 422, Hi: 423, Stride: 1}, + {Lo: 439, Hi: 444, Stride: 5}, + {Lo: 445, Hi: 451, Stride: 3}, + {Lo: 540, Hi: 546, Stride: 6}, + {Lo: 547, Hi: 577, Stride: 30}, + {Lo: 593, Hi: 609, Stride: 16}, + {Lo: 611, Hi: 617, Stride: 6}, + {Lo: 618, Hi: 623, Stride: 5}, + {Lo: 651, Hi: 655, Stride: 4}, + {Lo: 660, Hi: 697, Stride: 37}, + {Lo: 699, Hi: 702, Stride: 1}, + {Lo: 706, Hi: 708, Stride: 1}, + {Lo: 710, Hi: 714, Stride: 2}, + {Lo: 715, Hi: 720, Stride: 5}, + {Lo: 727, Hi: 731, Stride: 4}, + {Lo: 732, Hi: 756, Stride: 24}, + {Lo: 760, Hi: 884, Stride: 124}, + {Lo: 890, Hi: 894, Stride: 4}, + {Lo: 895, Hi: 900, Stride: 5}, + {Lo: 913, Hi: 914, Stride: 1}, + {Lo: 917, Hi: 919, Stride: 1}, + {Lo: 922, Hi: 924, Stride: 2}, + {Lo: 925, Hi: 929, Stride: 2}, + {Lo: 932, Hi: 933, Stride: 1}, + {Lo: 935, Hi: 945, Stride: 10}, + {Lo: 947, Hi: 953, Stride: 6}, + {Lo: 957, Hi: 965, Stride: 2}, + {Lo: 978, Hi: 988, Stride: 10}, + {Lo: 1000, Hi: 1010, Stride: 10}, + {Lo: 1011, Hi: 1017, Stride: 6}, + {Lo: 1018, Hi: 1029, Stride: 11}, + {Lo: 1030, Hi: 1032, Stride: 2}, + {Lo: 1109, Hi: 1110, Stride: 1}, + {Lo: 1112, Hi: 1121, Stride: 9}, + {Lo: 1140, Hi: 1141, Stride: 1}, + {Lo: 1198, Hi: 1199, Stride: 1}, + {Lo: 1211, Hi: 1213, Stride: 2}, + {Lo: 1216, Hi: 1231, Stride: 15}, + {Lo: 1248, Hi: 1281, Stride: 33}, + {Lo: 1292, Hi: 1307, Stride: 15}, + {Lo: 1308, Hi: 1309, Stride: 1}, + {Lo: 1357, Hi: 1359, Stride: 2}, + {Lo: 1365, Hi: 1370, Stride: 5}, + {Lo: 1373, Hi: 1377, Stride: 4}, + {Lo: 1379, Hi: 1382, Stride: 3}, + {Lo: 1392, Hi: 1400, Stride: 8}, + {Lo: 1404, Hi: 1405, Stride: 1}, + {Lo: 1409, Hi: 1412, Stride: 3}, + {Lo: 1413, Hi: 1417, Stride: 4}, + {Lo: 1472, Hi: 1475, Stride: 3}, + {Lo: 1493, Hi: 1496, Stride: 3}, + {Lo: 1497, Hi: 1503, Stride: 6}, + {Lo: 1505, Hi: 1523, Stride: 18}, + {Lo: 1549, Hi: 1575, Stride: 26}, + {Lo: 1607, Hi: 1632, Stride: 25}, + {Lo: 1633, Hi: 1637, Stride: 4}, + {Lo: 1639, Hi: 1643, Stride: 4}, + {Lo: 1645, Hi: 1726, Stride: 81}, + {Lo: 1729, Hi: 1748, Stride: 19}, + {Lo: 1749, Hi: 1776, Stride: 27}, + {Lo: 1777, Hi: 1781, Stride: 4}, + {Lo: 1783, Hi: 1793, Stride: 10}, + {Lo: 1794, Hi: 1796, Stride: 1}, + {Lo: 1984, Hi: 1994, Stride: 10}, + {Lo: 2036, Hi: 2037, Stride: 1}, + {Lo: 2042, Hi: 2307, Stride: 265}, + {Lo: 2406, Hi: 2429, Stride: 23}, + {Lo: 2534, Hi: 2538, Stride: 4}, + {Lo: 2541, Hi: 2662, Stride: 121}, + {Lo: 2663, Hi: 2666, Stride: 3}, + {Lo: 2691, Hi: 2790, Stride: 99}, + {Lo: 2819, Hi: 2848, Stride: 29}, + {Lo: 2918, Hi: 2920, Stride: 2}, + {Lo: 3046, Hi: 3074, Stride: 28}, + {Lo: 3174, Hi: 3202, Stride: 28}, + {Lo: 3302, Hi: 3330, Stride: 28}, + {Lo: 3360, Hi: 3430, Stride: 70}, + {Lo: 3437, Hi: 3458, Stride: 21}, + {Lo: 3664, Hi: 3792, Stride: 128}, + {Lo: 4125, Hi: 4160, Stride: 35}, + {Lo: 4327, Hi: 4351, Stride: 24}, + {Lo: 4608, Hi: 5024, Stride: 208}, + {Lo: 5025, Hi: 5026, Stride: 1}, + {Lo: 5029, Hi: 5033, Stride: 4}, + {Lo: 5034, Hi: 5036, Stride: 1}, + {Lo: 5038, Hi: 5043, Stride: 5}, + {Lo: 5047, Hi: 5051, Stride: 4}, + {Lo: 5053, Hi: 5056, Stride: 3}, + {Lo: 5058, Hi: 5059, Stride: 1}, + {Lo: 5070, Hi: 5071, Stride: 1}, + {Lo: 5074, Hi: 5076, Stride: 2}, + {Lo: 5077, Hi: 5081, Stride: 4}, + {Lo: 5082, Hi: 5086, Stride: 4}, + {Lo: 5087, Hi: 5090, Stride: 3}, + {Lo: 5094, Hi: 5095, Stride: 1}, + {Lo: 5102, Hi: 5107, Stride: 5}, + {Lo: 5108, Hi: 5120, Stride: 12}, + {Lo: 5167, Hi: 5171, Stride: 4}, + {Lo: 5176, Hi: 5194, Stride: 18}, + {Lo: 5196, Hi: 5229, Stride: 33}, + {Lo: 5231, Hi: 5234, Stride: 3}, + {Lo: 5261, Hi: 5290, Stride: 29}, + {Lo: 5311, Hi: 5441, Stride: 130}, + {Lo: 5500, Hi: 5501, Stride: 1}, + {Lo: 5511, Hi: 5551, Stride: 40}, + {Lo: 5556, Hi: 5573, Stride: 17}, + {Lo: 5598, Hi: 5610, Stride: 12}, + {Lo: 5616, Hi: 5623, Stride: 7}, + {Lo: 5741, Hi: 5742, Stride: 1}, + {Lo: 5760, Hi: 5810, Stride: 50}, + {Lo: 5815, Hi: 5825, Stride: 10}, + {Lo: 5836, Hi: 5845, Stride: 9}, + {Lo: 5846, Hi: 5868, Stride: 22}, + {Lo: 5869, Hi: 5941, Stride: 72}, + {Lo: 6147, Hi: 6153, Stride: 6}, + {Lo: 7428, Hi: 7439, Stride: 11}, + {Lo: 7441, Hi: 7452, Stride: 11}, + {Lo: 7456, Hi: 7458, Stride: 1}, + {Lo: 7462, Hi: 7555, Stride: 93}, + {Lo: 7564, Hi: 7837, Stride: 273}, + {Lo: 7935, Hi: 8125, Stride: 190}, + {Lo: 8126, Hi: 8128, Stride: 1}, + {Lo: 8175, Hi: 8189, Stride: 14}, + {Lo: 8190, Hi: 8192, Stride: 2}, + {Lo: 8193, Hi: 8202, Stride: 1}, + {Lo: 8208, Hi: 8210, Stride: 1}, + {Lo: 8218, Hi: 8219, Stride: 1}, + {Lo: 8228, Hi: 8232, Stride: 4}, + {Lo: 8233, Hi: 8239, Stride: 6}, + {Lo: 8242, Hi: 8249, Stride: 7}, + {Lo: 8250, Hi: 8257, Stride: 7}, + {Lo: 8259, Hi: 8260, Stride: 1}, + {Lo: 8270, Hi: 8275, Stride: 5}, + {Lo: 8282, Hi: 8287, Stride: 5}, + {Lo: 8450, Hi: 8458, Stride: 8}, + {Lo: 8459, Hi: 8462, Stride: 1}, + {Lo: 8464, Hi: 8467, Stride: 1}, + {Lo: 8469, Hi: 8473, Stride: 4}, + {Lo: 8474, Hi: 8477, Stride: 1}, + {Lo: 8484, Hi: 8488, Stride: 4}, + {Lo: 8490, Hi: 8492, Stride: 2}, + {Lo: 8493, Hi: 8497, Stride: 1}, + {Lo: 8499, Hi: 8500, Stride: 1}, + {Lo: 8505, Hi: 8509, Stride: 4}, + {Lo: 8517, Hi: 8521, Stride: 1}, + {Lo: 8544, Hi: 8548, Stride: 4}, + {Lo: 8553, Hi: 8556, Stride: 3}, + {Lo: 8557, Hi: 8560, Stride: 1}, + {Lo: 8564, Hi: 8569, Stride: 5}, + {Lo: 8572, Hi: 8574, Stride: 1}, + {Lo: 8722, Hi: 8725, Stride: 3}, + {Lo: 8726, Hi: 8727, Stride: 1}, + {Lo: 8739, Hi: 8744, Stride: 5}, + {Lo: 8746, Hi: 8758, Stride: 12}, + {Lo: 8764, Hi: 8868, Stride: 104}, + {Lo: 8897, Hi: 8899, Stride: 2}, + {Lo: 8959, Hi: 9075, Stride: 116}, + {Lo: 9076, Hi: 9082, Stride: 6}, + {Lo: 9213, Hi: 9585, Stride: 372}, + {Lo: 9587, Hi: 10088, Stride: 501}, + {Lo: 10089, Hi: 10094, Stride: 5}, + {Lo: 10095, Hi: 10098, Stride: 3}, + {Lo: 10099, Hi: 10101, Stride: 1}, + {Lo: 10133, Hi: 10134, Stride: 1}, + {Lo: 10187, Hi: 10189, Stride: 2}, + {Lo: 10201, Hi: 10539, Stride: 338}, + {Lo: 10540, Hi: 10741, Stride: 201}, + {Lo: 10744, Hi: 10745, Stride: 1}, + {Lo: 10799, Hi: 11397, Stride: 598}, + {Lo: 11406, Hi: 11410, Stride: 4}, + {Lo: 11412, Hi: 11416, Stride: 4}, + {Lo: 11418, Hi: 11422, Stride: 4}, + {Lo: 11423, Hi: 11426, Stride: 3}, + {Lo: 11427, Hi: 11430, Stride: 1}, + {Lo: 11432, Hi: 11436, Stride: 4}, + {Lo: 11450, Hi: 11462, Stride: 12}, + {Lo: 11466, Hi: 11468, Stride: 2}, + {Lo: 11472, Hi: 11474, Stride: 2}, + {Lo: 11576, Hi: 11577, Stride: 1}, + {Lo: 11599, Hi: 11601, Stride: 2}, + {Lo: 11604, Hi: 11605, Stride: 1}, + {Lo: 11613, Hi: 11840, Stride: 227}, + {Lo: 12034, Hi: 12035, Stride: 1}, + {Lo: 12295, Hi: 12308, Stride: 13}, + {Lo: 12309, Hi: 12339, Stride: 30}, + {Lo: 12448, Hi: 12755, Stride: 307}, + {Lo: 12756, Hi: 20022, Stride: 7266}, + {Lo: 20031, Hi: 42192, Stride: 22161}, + {Lo: 42193, Hi: 42196, Stride: 1}, + {Lo: 42198, Hi: 42199, Stride: 1}, + {Lo: 42201, Hi: 42202, Stride: 1}, + {Lo: 42204, Hi: 42205, Stride: 1}, + {Lo: 42207, Hi: 42211, Stride: 1}, + {Lo: 42214, Hi: 42215, Stride: 1}, + {Lo: 42218, Hi: 42220, Stride: 1}, + {Lo: 42222, Hi: 42226, Stride: 2}, + {Lo: 42227, Hi: 42228, Stride: 1}, + {Lo: 42232, Hi: 42233, Stride: 1}, + {Lo: 42237, Hi: 42239, Stride: 2}, + {Lo: 42510, Hi: 42564, Stride: 54}, + {Lo: 42567, Hi: 42719, Stride: 152}, + {Lo: 42731, Hi: 42735, Stride: 4}, + {Lo: 42801, Hi: 42842, Stride: 41}, + {Lo: 42858, Hi: 42862, Stride: 4}, + {Lo: 42872, Hi: 42889, Stride: 17}, + {Lo: 42892, Hi: 42904, Stride: 12}, + {Lo: 42905, Hi: 42911, Stride: 6}, + {Lo: 42923, Hi: 42930, Stride: 7}, + {Lo: 42931, Hi: 42932, Stride: 1}, + {Lo: 43826, Hi: 43829, Stride: 3}, + {Lo: 43837, Hi: 43847, Stride: 10}, + {Lo: 43848, Hi: 43854, Stride: 6}, + {Lo: 43858, Hi: 43866, Stride: 8}, + {Lo: 43893, Hi: 43905, Stride: 12}, + {Lo: 43907, Hi: 43923, Stride: 16}, + {Lo: 43945, Hi: 43946, Stride: 1}, + {Lo: 43951, Hi: 64422, Stride: 20471}, + {Lo: 64423, Hi: 64429, Stride: 1}, + {Lo: 64830, Hi: 64831, Stride: 1}, + {Lo: 65072, Hi: 65101, Stride: 29}, + {Lo: 65102, Hi: 65103, Stride: 1}, + {Lo: 65112, Hi: 65128, Stride: 16}, + {Lo: 65165, Hi: 65166, Stride: 1}, + {Lo: 65257, Hi: 65260, Stride: 1}, + {Lo: 65282, Hi: 65284, Stride: 2}, + {Lo: 65285, Hi: 65287, Stride: 1}, + {Lo: 65290, Hi: 65291, Stride: 1}, + {Lo: 65293, Hi: 65305, Stride: 1}, + {Lo: 65308, Hi: 65310, Stride: 1}, + {Lo: 65312, Hi: 65373, Stride: 1}, + {Lo: 65512, Hi: 65512, Stride: 1}, + }, + R32: []unicode.Range32{ + {Lo: 66178, Hi: 66182, Stride: 4}, + {Lo: 66183, Hi: 66186, Stride: 3}, + {Lo: 66192, Hi: 66194, Stride: 2}, + {Lo: 66197, Hi: 66199, Stride: 1}, + {Lo: 66203, Hi: 66208, Stride: 5}, + {Lo: 66209, Hi: 66210, Stride: 1}, + {Lo: 66213, Hi: 66219, Stride: 6}, + {Lo: 66224, Hi: 66226, Stride: 1}, + {Lo: 66228, Hi: 66255, Stride: 27}, + {Lo: 66293, Hi: 66305, Stride: 12}, + {Lo: 66306, Hi: 66313, Stride: 7}, + {Lo: 66321, Hi: 66325, Stride: 4}, + {Lo: 66327, Hi: 66330, Stride: 3}, + {Lo: 66335, Hi: 66336, Stride: 1}, + {Lo: 66338, Hi: 66564, Stride: 226}, + {Lo: 66581, Hi: 66587, Stride: 6}, + {Lo: 66592, Hi: 66604, Stride: 12}, + {Lo: 66621, Hi: 66632, Stride: 11}, + {Lo: 66740, Hi: 66754, Stride: 14}, + {Lo: 66766, Hi: 66770, Stride: 4}, + {Lo: 66794, Hi: 66806, Stride: 12}, + {Lo: 66835, Hi: 66838, Stride: 3}, + {Lo: 66840, Hi: 66844, Stride: 4}, + {Lo: 66845, Hi: 66853, Stride: 8}, + {Lo: 66854, Hi: 66855, Stride: 1}, + {Lo: 68176, Hi: 70864, Stride: 2688}, + {Lo: 71430, Hi: 71438, Stride: 4}, + {Lo: 71439, Hi: 71840, Stride: 401}, + {Lo: 71842, Hi: 71844, Stride: 1}, + {Lo: 71846, Hi: 71852, Stride: 3}, + {Lo: 71854, Hi: 71855, Stride: 1}, + {Lo: 71858, Hi: 71867, Stride: 3}, + {Lo: 71868, Hi: 71872, Stride: 4}, + {Lo: 71873, Hi: 71876, Stride: 1}, + {Lo: 71878, Hi: 71884, Stride: 2}, + {Lo: 71893, Hi: 71896, Stride: 1}, + {Lo: 71900, Hi: 71904, Stride: 4}, + {Lo: 71909, Hi: 71910, Stride: 1}, + {Lo: 71913, Hi: 71922, Stride: 3}, + {Lo: 93960, Hi: 93962, Stride: 2}, + {Lo: 93974, Hi: 93992, Stride: 18}, + {Lo: 94005, Hi: 94010, Stride: 5}, + {Lo: 94011, Hi: 94015, Stride: 4}, + {Lo: 94016, Hi: 94018, Stride: 2}, + {Lo: 94019, Hi: 94033, Stride: 14}, + {Lo: 94034, Hi: 119060, Stride: 25026}, + {Lo: 119149, Hi: 119302, Stride: 153}, + {Lo: 119309, Hi: 119311, Stride: 2}, + {Lo: 119314, Hi: 119315, Stride: 1}, + {Lo: 119318, Hi: 119338, Stride: 20}, + {Lo: 119350, Hi: 119351, Stride: 1}, + {Lo: 119354, Hi: 119355, Stride: 1}, + {Lo: 119808, Hi: 119845, Stride: 1}, + {Lo: 119847, Hi: 119892, Stride: 1}, + {Lo: 119894, Hi: 119897, Stride: 1}, + {Lo: 119899, Hi: 119949, Stride: 1}, + {Lo: 119951, Hi: 119964, Stride: 1}, + {Lo: 119966, Hi: 119967, Stride: 1}, + {Lo: 119970, Hi: 119973, Stride: 3}, + {Lo: 119974, Hi: 119977, Stride: 3}, + {Lo: 119978, Hi: 119980, Stride: 1}, + {Lo: 119982, Hi: 119993, Stride: 1}, + {Lo: 119995, Hi: 119997, Stride: 2}, + {Lo: 119998, Hi: 120001, Stride: 1}, + {Lo: 120003, Hi: 120005, Stride: 2}, + {Lo: 120006, Hi: 120053, Stride: 1}, + {Lo: 120055, Hi: 120069, Stride: 1}, + {Lo: 120071, Hi: 120074, Stride: 1}, + {Lo: 120077, Hi: 120084, Stride: 1}, + {Lo: 120086, Hi: 120092, Stride: 1}, + {Lo: 120094, Hi: 120105, Stride: 1}, + {Lo: 120107, Hi: 120121, Stride: 1}, + {Lo: 120123, Hi: 120126, Stride: 1}, + {Lo: 120128, Hi: 120132, Stride: 1}, + {Lo: 120134, Hi: 120138, Stride: 4}, + {Lo: 120139, Hi: 120144, Stride: 1}, + {Lo: 120146, Hi: 120157, Stride: 1}, + {Lo: 120159, Hi: 120209, Stride: 1}, + {Lo: 120211, Hi: 120261, Stride: 1}, + {Lo: 120263, Hi: 120313, Stride: 1}, + {Lo: 120315, Hi: 120365, Stride: 1}, + {Lo: 120367, Hi: 120417, Stride: 1}, + {Lo: 120419, Hi: 120469, Stride: 1}, + {Lo: 120471, Hi: 120484, Stride: 1}, + {Lo: 120488, Hi: 120489, Stride: 1}, + {Lo: 120492, Hi: 120494, Stride: 1}, + {Lo: 120496, Hi: 120497, Stride: 1}, + {Lo: 120499, Hi: 120500, Stride: 1}, + {Lo: 120502, Hi: 120504, Stride: 2}, + {Lo: 120507, Hi: 120508, Stride: 1}, + {Lo: 120510, Hi: 120514, Stride: 4}, + {Lo: 120516, Hi: 120522, Stride: 6}, + {Lo: 120526, Hi: 120534, Stride: 2}, + {Lo: 120544, Hi: 120546, Stride: 2}, + {Lo: 120547, Hi: 120550, Stride: 3}, + {Lo: 120551, Hi: 120552, Stride: 1}, + {Lo: 120554, Hi: 120555, Stride: 1}, + {Lo: 120557, Hi: 120558, Stride: 1}, + {Lo: 120560, Hi: 120562, Stride: 2}, + {Lo: 120565, Hi: 120566, Stride: 1}, + {Lo: 120568, Hi: 120572, Stride: 4}, + {Lo: 120574, Hi: 120580, Stride: 6}, + {Lo: 120584, Hi: 120592, Stride: 2}, + {Lo: 120602, Hi: 120604, Stride: 2}, + {Lo: 120605, Hi: 120608, Stride: 3}, + {Lo: 120609, Hi: 120610, Stride: 1}, + {Lo: 120612, Hi: 120613, Stride: 1}, + {Lo: 120615, Hi: 120616, Stride: 1}, + {Lo: 120618, Hi: 120620, Stride: 2}, + {Lo: 120623, Hi: 120624, Stride: 1}, + {Lo: 120626, Hi: 120630, Stride: 4}, + {Lo: 120632, Hi: 120638, Stride: 6}, + {Lo: 120642, Hi: 120650, Stride: 2}, + {Lo: 120660, Hi: 120662, Stride: 2}, + {Lo: 120663, Hi: 120666, Stride: 3}, + {Lo: 120667, Hi: 120668, Stride: 1}, + {Lo: 120670, Hi: 120671, Stride: 1}, + {Lo: 120673, Hi: 120674, Stride: 1}, + {Lo: 120676, Hi: 120678, Stride: 2}, + {Lo: 120681, Hi: 120682, Stride: 1}, + {Lo: 120684, Hi: 120688, Stride: 4}, + {Lo: 120690, Hi: 120696, Stride: 6}, + {Lo: 120700, Hi: 120708, Stride: 2}, + {Lo: 120718, Hi: 120720, Stride: 2}, + {Lo: 120721, Hi: 120724, Stride: 3}, + {Lo: 120725, Hi: 120726, Stride: 1}, + {Lo: 120728, Hi: 120729, Stride: 1}, + {Lo: 120731, Hi: 120732, Stride: 1}, + {Lo: 120734, Hi: 120736, Stride: 2}, + {Lo: 120739, Hi: 120740, Stride: 1}, + {Lo: 120742, Hi: 120746, Stride: 4}, + {Lo: 120748, Hi: 120754, Stride: 6}, + {Lo: 120758, Hi: 120766, Stride: 2}, + {Lo: 120776, Hi: 120778, Stride: 2}, + {Lo: 120782, Hi: 120831, Stride: 1}, + {Lo: 125127, Hi: 125131, Stride: 4}, + {Lo: 126464, Hi: 126500, Stride: 36}, + {Lo: 126564, Hi: 126592, Stride: 28}, + {Lo: 126596, Hi: 128844, Stride: 2248}, + {Lo: 128872, Hi: 130032, Stride: 1160}, + {Lo: 130033, Hi: 130041, Stride: 1}, + }, + LatinOffset: 0, }, - R32: []unicode.Range32{ - {Lo: 66178, Hi: 66182, Stride: 4}, - {Lo: 66183, Hi: 66186, Stride: 3}, - {Lo: 66192, Hi: 66194, Stride: 2}, - {Lo: 66197, Hi: 66199, Stride: 1}, - {Lo: 66203, Hi: 66208, Stride: 5}, - {Lo: 66209, Hi: 66210, Stride: 1}, - {Lo: 66213, Hi: 66219, Stride: 6}, - {Lo: 66224, Hi: 66226, Stride: 1}, - {Lo: 66228, Hi: 66255, Stride: 27}, - {Lo: 66293, Hi: 66305, Stride: 12}, - {Lo: 66306, Hi: 66313, Stride: 7}, - {Lo: 66321, Hi: 66325, Stride: 4}, - {Lo: 66327, Hi: 66330, Stride: 3}, - {Lo: 66335, Hi: 66336, Stride: 1}, - {Lo: 66338, Hi: 66564, Stride: 226}, - {Lo: 66581, Hi: 66587, Stride: 6}, - {Lo: 66592, Hi: 66604, Stride: 12}, - {Lo: 66621, Hi: 66632, Stride: 11}, - {Lo: 66740, Hi: 66754, Stride: 14}, - {Lo: 66766, Hi: 66770, Stride: 4}, - {Lo: 66794, Hi: 66806, Stride: 12}, - {Lo: 66835, Hi: 66838, Stride: 3}, - {Lo: 66840, Hi: 66844, Stride: 4}, - {Lo: 66845, Hi: 66853, Stride: 8}, - {Lo: 66854, Hi: 66855, Stride: 1}, - {Lo: 68176, Hi: 70864, Stride: 2688}, - {Lo: 71430, Hi: 71438, Stride: 4}, - {Lo: 71439, Hi: 71840, Stride: 401}, - {Lo: 71842, Hi: 71844, Stride: 1}, - {Lo: 71846, Hi: 71852, Stride: 3}, - {Lo: 71854, Hi: 71855, Stride: 1}, - {Lo: 71858, Hi: 71867, Stride: 3}, - {Lo: 71868, Hi: 71872, Stride: 4}, - {Lo: 71873, Hi: 71876, Stride: 1}, - {Lo: 71878, Hi: 71884, Stride: 2}, - {Lo: 71893, Hi: 71896, Stride: 1}, - {Lo: 71900, Hi: 71904, Stride: 4}, - {Lo: 71909, Hi: 71910, Stride: 1}, - {Lo: 71913, Hi: 71922, Stride: 3}, - {Lo: 93960, Hi: 93962, Stride: 2}, - {Lo: 93974, Hi: 93992, Stride: 18}, - {Lo: 94005, Hi: 94010, Stride: 5}, - {Lo: 94011, Hi: 94015, Stride: 4}, - {Lo: 94016, Hi: 94018, Stride: 2}, - {Lo: 94019, Hi: 94033, Stride: 14}, - {Lo: 94034, Hi: 119060, Stride: 25026}, - {Lo: 119149, Hi: 119302, Stride: 153}, - {Lo: 119309, Hi: 119311, Stride: 2}, - {Lo: 119314, Hi: 119315, Stride: 1}, - {Lo: 119318, Hi: 119338, Stride: 20}, - {Lo: 119350, Hi: 119351, Stride: 1}, - {Lo: 119354, Hi: 119355, Stride: 1}, - {Lo: 119808, Hi: 119845, Stride: 1}, - {Lo: 119847, Hi: 119892, Stride: 1}, - {Lo: 119894, Hi: 119897, Stride: 1}, - {Lo: 119899, Hi: 119949, Stride: 1}, - {Lo: 119951, Hi: 119964, Stride: 1}, - {Lo: 119966, Hi: 119967, Stride: 1}, - {Lo: 119970, Hi: 119973, Stride: 3}, - {Lo: 119974, Hi: 119977, Stride: 3}, - {Lo: 119978, Hi: 119980, Stride: 1}, - {Lo: 119982, Hi: 119993, Stride: 1}, - {Lo: 119995, Hi: 119997, Stride: 2}, - {Lo: 119998, Hi: 120001, Stride: 1}, - {Lo: 120003, Hi: 120005, Stride: 2}, - {Lo: 120006, Hi: 120053, Stride: 1}, - {Lo: 120055, Hi: 120069, Stride: 1}, - {Lo: 120071, Hi: 120074, Stride: 1}, - {Lo: 120077, Hi: 120084, Stride: 1}, - {Lo: 120086, Hi: 120092, Stride: 1}, - {Lo: 120094, Hi: 120105, Stride: 1}, - {Lo: 120107, Hi: 120121, Stride: 1}, - {Lo: 120123, Hi: 120126, Stride: 1}, - {Lo: 120128, Hi: 120132, Stride: 1}, - {Lo: 120134, Hi: 120138, Stride: 4}, - {Lo: 120139, Hi: 120144, Stride: 1}, - {Lo: 120146, Hi: 120157, Stride: 1}, - {Lo: 120159, Hi: 120209, Stride: 1}, - {Lo: 120211, Hi: 120261, Stride: 1}, - {Lo: 120263, Hi: 120313, Stride: 1}, - {Lo: 120315, Hi: 120365, Stride: 1}, - {Lo: 120367, Hi: 120417, Stride: 1}, - {Lo: 120419, Hi: 120469, Stride: 1}, - {Lo: 120471, Hi: 120484, Stride: 1}, - {Lo: 120488, Hi: 120489, Stride: 1}, - {Lo: 120492, Hi: 120494, Stride: 1}, - {Lo: 120496, Hi: 120497, Stride: 1}, - {Lo: 120499, Hi: 120500, Stride: 1}, - {Lo: 120502, Hi: 120504, Stride: 2}, - {Lo: 120507, Hi: 120508, Stride: 1}, - {Lo: 120510, Hi: 120514, Stride: 4}, - {Lo: 120516, Hi: 120522, Stride: 6}, - {Lo: 120526, Hi: 120534, Stride: 2}, - {Lo: 120544, Hi: 120546, Stride: 2}, - {Lo: 120547, Hi: 120550, Stride: 3}, - {Lo: 120551, Hi: 120552, Stride: 1}, - {Lo: 120554, Hi: 120555, Stride: 1}, - {Lo: 120557, Hi: 120558, Stride: 1}, - {Lo: 120560, Hi: 120562, Stride: 2}, - {Lo: 120565, Hi: 120566, Stride: 1}, - {Lo: 120568, Hi: 120572, Stride: 4}, - {Lo: 120574, Hi: 120580, Stride: 6}, - {Lo: 120584, Hi: 120592, Stride: 2}, - {Lo: 120602, Hi: 120604, Stride: 2}, - {Lo: 120605, Hi: 120608, Stride: 3}, - {Lo: 120609, Hi: 120610, Stride: 1}, - {Lo: 120612, Hi: 120613, Stride: 1}, - {Lo: 120615, Hi: 120616, Stride: 1}, - {Lo: 120618, Hi: 120620, Stride: 2}, - {Lo: 120623, Hi: 120624, Stride: 1}, - {Lo: 120626, Hi: 120630, Stride: 4}, - {Lo: 120632, Hi: 120638, Stride: 6}, - {Lo: 120642, Hi: 120650, Stride: 2}, - {Lo: 120660, Hi: 120662, Stride: 2}, - {Lo: 120663, Hi: 120666, Stride: 3}, - {Lo: 120667, Hi: 120668, Stride: 1}, - {Lo: 120670, Hi: 120671, Stride: 1}, - {Lo: 120673, Hi: 120674, Stride: 1}, - {Lo: 120676, Hi: 120678, Stride: 2}, - {Lo: 120681, Hi: 120682, Stride: 1}, - {Lo: 120684, Hi: 120688, Stride: 4}, - {Lo: 120690, Hi: 120696, Stride: 6}, - {Lo: 120700, Hi: 120708, Stride: 2}, - {Lo: 120718, Hi: 120720, Stride: 2}, - {Lo: 120721, Hi: 120724, Stride: 3}, - {Lo: 120725, Hi: 120726, Stride: 1}, - {Lo: 120728, Hi: 120729, Stride: 1}, - {Lo: 120731, Hi: 120732, Stride: 1}, - {Lo: 120734, Hi: 120736, Stride: 2}, - {Lo: 120739, Hi: 120740, Stride: 1}, - {Lo: 120742, Hi: 120746, Stride: 4}, - {Lo: 120748, Hi: 120754, Stride: 6}, - {Lo: 120758, Hi: 120766, Stride: 2}, - {Lo: 120776, Hi: 120778, Stride: 2}, - {Lo: 120782, Hi: 120831, Stride: 1}, - {Lo: 125127, Hi: 125131, Stride: 4}, - {Lo: 126464, Hi: 126500, Stride: 36}, - {Lo: 126564, Hi: 126592, Stride: 28}, - {Lo: 126596, Hi: 128844, Stride: 2248}, - {Lo: 128872, Hi: 130032, Stride: 1160}, - {Lo: 130033, Hi: 130041, Stride: 1}, - }, - LatinOffset: 0, }, - }, - "_default": { - Confusable: []rune{160, 180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, - With: []rune{32, 96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, - Locale: "_default", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 160, Hi: 180, Stride: 20}, - {Lo: 215, Hi: 305, Stride: 90}, - {Lo: 921, Hi: 1009, Stride: 88}, - {Lo: 1040, Hi: 1042, Stride: 2}, - {Lo: 1045, Hi: 1047, Stride: 2}, - {Lo: 1050, Hi: 1052, Stride: 2}, - {Lo: 1053, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8211, Hi: 8216, Stride: 5}, - {Lo: 8217, Hi: 8245, Stride: 28}, - {Lo: 12494, Hi: 65281, Stride: 52787}, - {Lo: 65283, Hi: 65288, Stride: 5}, - {Lo: 65289, Hi: 65292, Stride: 3}, - {Lo: 65306, Hi: 65307, Stride: 1}, - {Lo: 65311, Hi: 65374, Stride: 63}, + + "_default": { + Confusable: []rune{160, 180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, + With: []rune{32, 96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, + Locale: "_default", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 160, Hi: 180, Stride: 20}, + {Lo: 215, Hi: 305, Stride: 90}, + {Lo: 921, Hi: 1009, Stride: 88}, + {Lo: 1040, Hi: 1042, Stride: 2}, + {Lo: 1045, Hi: 1047, Stride: 2}, + {Lo: 1050, Hi: 1052, Stride: 2}, + {Lo: 1053, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8211, Hi: 8216, Stride: 5}, + {Lo: 8217, Hi: 8245, Stride: 28}, + {Lo: 12494, Hi: 65281, Stride: 52787}, + {Lo: 65283, Hi: 65288, Stride: 5}, + {Lo: 65289, Hi: 65292, Stride: 3}, + {Lo: 65306, Hi: 65307, Stride: 1}, + {Lo: 65311, Hi: 65374, Stride: 63}, + }, + R32: []unicode.Range32{}, + LatinOffset: 1, }, - R32: []unicode.Range32{}, - LatinOffset: 1, }, - }, - "cs": { - Confusable: []rune{180, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, - With: []rune{96, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, - Locale: "cs", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 180, Hi: 305, Stride: 125}, - {Lo: 921, Hi: 1009, Stride: 88}, - {Lo: 1040, Hi: 1042, Stride: 2}, - {Lo: 1045, Hi: 1047, Stride: 2}, - {Lo: 1050, Hi: 1052, Stride: 2}, - {Lo: 1053, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8216, Hi: 8217, Stride: 1}, - {Lo: 8245, Hi: 12494, Stride: 4249}, - {Lo: 65281, Hi: 65283, Stride: 2}, - {Lo: 65288, Hi: 65289, Stride: 1}, - {Lo: 65292, Hi: 65306, Stride: 14}, - {Lo: 65307, Hi: 65311, Stride: 4}, - {Lo: 65374, Hi: 65374, Stride: 1}, + + "cs": { + Confusable: []rune{180, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, + With: []rune{96, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, + Locale: "cs", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 180, Hi: 305, Stride: 125}, + {Lo: 921, Hi: 1009, Stride: 88}, + {Lo: 1040, Hi: 1042, Stride: 2}, + {Lo: 1045, Hi: 1047, Stride: 2}, + {Lo: 1050, Hi: 1052, Stride: 2}, + {Lo: 1053, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8216, Hi: 8217, Stride: 1}, + {Lo: 8245, Hi: 12494, Stride: 4249}, + {Lo: 65281, Hi: 65283, Stride: 2}, + {Lo: 65288, Hi: 65289, Stride: 1}, + {Lo: 65292, Hi: 65306, Stride: 14}, + {Lo: 65307, Hi: 65311, Stride: 4}, + {Lo: 65374, Hi: 65374, Stride: 1}, + }, + R32: []unicode.Range32{}, + LatinOffset: 0, }, - R32: []unicode.Range32{}, - LatinOffset: 0, }, - }, - "de": { - Confusable: []rune{180, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, - With: []rune{96, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, - Locale: "de", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 180, Hi: 305, Stride: 125}, - {Lo: 921, Hi: 1009, Stride: 88}, - {Lo: 1040, Hi: 1042, Stride: 2}, - {Lo: 1045, Hi: 1047, Stride: 2}, - {Lo: 1050, Hi: 1052, Stride: 2}, - {Lo: 1053, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8216, Hi: 8217, Stride: 1}, - {Lo: 8245, Hi: 12494, Stride: 4249}, - {Lo: 65281, Hi: 65283, Stride: 2}, - {Lo: 65288, Hi: 65289, Stride: 1}, - {Lo: 65292, Hi: 65306, Stride: 14}, - {Lo: 65307, Hi: 65311, Stride: 4}, - {Lo: 65374, Hi: 65374, Stride: 1}, + + "de": { + Confusable: []rune{180, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, + With: []rune{96, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, + Locale: "de", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 180, Hi: 305, Stride: 125}, + {Lo: 921, Hi: 1009, Stride: 88}, + {Lo: 1040, Hi: 1042, Stride: 2}, + {Lo: 1045, Hi: 1047, Stride: 2}, + {Lo: 1050, Hi: 1052, Stride: 2}, + {Lo: 1053, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8216, Hi: 8217, Stride: 1}, + {Lo: 8245, Hi: 12494, Stride: 4249}, + {Lo: 65281, Hi: 65283, Stride: 2}, + {Lo: 65288, Hi: 65289, Stride: 1}, + {Lo: 65292, Hi: 65306, Stride: 14}, + {Lo: 65307, Hi: 65311, Stride: 4}, + {Lo: 65374, Hi: 65374, Stride: 1}, + }, + R32: []unicode.Range32{}, + LatinOffset: 0, }, - R32: []unicode.Range32{}, - LatinOffset: 0, }, - }, - "es": { - Confusable: []rune{180, 215, 305, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, - With: []rune{96, 120, 105, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, - Locale: "es", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 180, Hi: 215, Stride: 35}, - {Lo: 305, Hi: 1009, Stride: 704}, - {Lo: 1040, Hi: 1042, Stride: 2}, - {Lo: 1045, Hi: 1047, Stride: 2}, - {Lo: 1050, Hi: 1052, Stride: 2}, - {Lo: 1053, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8211, Hi: 8245, Stride: 34}, - {Lo: 12494, Hi: 65281, Stride: 52787}, - {Lo: 65283, Hi: 65288, Stride: 5}, - {Lo: 65289, Hi: 65292, Stride: 3}, - {Lo: 65306, Hi: 65307, Stride: 1}, - {Lo: 65311, Hi: 65374, Stride: 63}, + + "es": { + Confusable: []rune{180, 215, 305, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, + With: []rune{96, 120, 105, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, + Locale: "es", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 180, Hi: 215, Stride: 35}, + {Lo: 305, Hi: 1009, Stride: 704}, + {Lo: 1040, Hi: 1042, Stride: 2}, + {Lo: 1045, Hi: 1047, Stride: 2}, + {Lo: 1050, Hi: 1052, Stride: 2}, + {Lo: 1053, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8211, Hi: 8245, Stride: 34}, + {Lo: 12494, Hi: 65281, Stride: 52787}, + {Lo: 65283, Hi: 65288, Stride: 5}, + {Lo: 65289, Hi: 65292, Stride: 3}, + {Lo: 65306, Hi: 65307, Stride: 1}, + {Lo: 65311, Hi: 65374, Stride: 63}, + }, + R32: []unicode.Range32{}, + LatinOffset: 1, }, - R32: []unicode.Range32{}, - LatinOffset: 1, }, - }, - "fr": { - Confusable: []rune{215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, - With: []rune{120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, - Locale: "fr", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 215, Hi: 305, Stride: 90}, - {Lo: 921, Hi: 1009, Stride: 88}, - {Lo: 1040, Hi: 1042, Stride: 2}, - {Lo: 1045, Hi: 1047, Stride: 2}, - {Lo: 1050, Hi: 1052, Stride: 2}, - {Lo: 1053, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8216, Hi: 8245, Stride: 29}, - {Lo: 12494, Hi: 65281, Stride: 52787}, - {Lo: 65283, Hi: 65288, Stride: 5}, - {Lo: 65289, Hi: 65292, Stride: 3}, - {Lo: 65306, Hi: 65307, Stride: 1}, - {Lo: 65311, Hi: 65374, Stride: 63}, + + "fr": { + Confusable: []rune{215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, + With: []rune{120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, + Locale: "fr", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 215, Hi: 305, Stride: 90}, + {Lo: 921, Hi: 1009, Stride: 88}, + {Lo: 1040, Hi: 1042, Stride: 2}, + {Lo: 1045, Hi: 1047, Stride: 2}, + {Lo: 1050, Hi: 1052, Stride: 2}, + {Lo: 1053, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8216, Hi: 8245, Stride: 29}, + {Lo: 12494, Hi: 65281, Stride: 52787}, + {Lo: 65283, Hi: 65288, Stride: 5}, + {Lo: 65289, Hi: 65292, Stride: 3}, + {Lo: 65306, Hi: 65307, Stride: 1}, + {Lo: 65311, Hi: 65374, Stride: 63}, + }, + R32: []unicode.Range32{}, + LatinOffset: 0, }, - R32: []unicode.Range32{}, - LatinOffset: 0, }, - }, - "it": { - Confusable: []rune{160, 180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8216, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, - With: []rune{32, 96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, - Locale: "it", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 160, Hi: 180, Stride: 20}, - {Lo: 215, Hi: 305, Stride: 90}, - {Lo: 921, Hi: 1009, Stride: 88}, - {Lo: 1040, Hi: 1042, Stride: 2}, - {Lo: 1045, Hi: 1047, Stride: 2}, - {Lo: 1050, Hi: 1052, Stride: 2}, - {Lo: 1053, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8211, Hi: 8216, Stride: 5}, - {Lo: 8245, Hi: 12494, Stride: 4249}, - {Lo: 65281, Hi: 65283, Stride: 2}, - {Lo: 65288, Hi: 65289, Stride: 1}, - {Lo: 65292, Hi: 65306, Stride: 14}, - {Lo: 65307, Hi: 65311, Stride: 4}, - {Lo: 65374, Hi: 65374, Stride: 1}, + + "it": { + Confusable: []rune{160, 180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8216, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, + With: []rune{32, 96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, + Locale: "it", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 160, Hi: 180, Stride: 20}, + {Lo: 215, Hi: 305, Stride: 90}, + {Lo: 921, Hi: 1009, Stride: 88}, + {Lo: 1040, Hi: 1042, Stride: 2}, + {Lo: 1045, Hi: 1047, Stride: 2}, + {Lo: 1050, Hi: 1052, Stride: 2}, + {Lo: 1053, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8211, Hi: 8216, Stride: 5}, + {Lo: 8245, Hi: 12494, Stride: 4249}, + {Lo: 65281, Hi: 65283, Stride: 2}, + {Lo: 65288, Hi: 65289, Stride: 1}, + {Lo: 65292, Hi: 65306, Stride: 14}, + {Lo: 65307, Hi: 65311, Stride: 4}, + {Lo: 65374, Hi: 65374, Stride: 1}, + }, + R32: []unicode.Range32{}, + LatinOffset: 1, }, - R32: []unicode.Range32{}, - LatinOffset: 1, }, - }, - "ja": { - Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8216, 8217, 8245, 65281, 65283, 65292, 65306, 65307}, - With: []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 96, 96, 33, 35, 44, 58, 59}, - Locale: "ja", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 180, Hi: 215, Stride: 35}, - {Lo: 305, Hi: 921, Stride: 616}, - {Lo: 1009, Hi: 1040, Stride: 31}, - {Lo: 1042, Hi: 1045, Stride: 3}, - {Lo: 1047, Hi: 1050, Stride: 3}, - {Lo: 1052, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8211, Hi: 8216, Stride: 5}, - {Lo: 8217, Hi: 8245, Stride: 28}, - {Lo: 65281, Hi: 65283, Stride: 2}, - {Lo: 65292, Hi: 65306, Stride: 14}, - {Lo: 65307, Hi: 65307, Stride: 1}, + + "ja": { + Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8216, 8217, 8245, 65281, 65283, 65292, 65306, 65307}, + With: []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 96, 96, 33, 35, 44, 58, 59}, + Locale: "ja", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 180, Hi: 215, Stride: 35}, + {Lo: 305, Hi: 921, Stride: 616}, + {Lo: 1009, Hi: 1040, Stride: 31}, + {Lo: 1042, Hi: 1045, Stride: 3}, + {Lo: 1047, Hi: 1050, Stride: 3}, + {Lo: 1052, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8211, Hi: 8216, Stride: 5}, + {Lo: 8217, Hi: 8245, Stride: 28}, + {Lo: 65281, Hi: 65283, Stride: 2}, + {Lo: 65292, Hi: 65306, Stride: 14}, + {Lo: 65307, Hi: 65307, Stride: 1}, + }, + R32: []unicode.Range32{}, + LatinOffset: 1, }, - R32: []unicode.Range32{}, - LatinOffset: 1, }, - }, - "ko": { - Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, - With: []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, - Locale: "ko", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 180, Hi: 215, Stride: 35}, - {Lo: 305, Hi: 921, Stride: 616}, - {Lo: 1009, Hi: 1040, Stride: 31}, - {Lo: 1042, Hi: 1045, Stride: 3}, - {Lo: 1047, Hi: 1050, Stride: 3}, - {Lo: 1052, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8211, Hi: 8245, Stride: 34}, - {Lo: 12494, Hi: 65281, Stride: 52787}, - {Lo: 65283, Hi: 65288, Stride: 5}, - {Lo: 65289, Hi: 65292, Stride: 3}, - {Lo: 65306, Hi: 65307, Stride: 1}, - {Lo: 65311, Hi: 65374, Stride: 63}, + + "ko": { + Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, + With: []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, + Locale: "ko", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 180, Hi: 215, Stride: 35}, + {Lo: 305, Hi: 921, Stride: 616}, + {Lo: 1009, Hi: 1040, Stride: 31}, + {Lo: 1042, Hi: 1045, Stride: 3}, + {Lo: 1047, Hi: 1050, Stride: 3}, + {Lo: 1052, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8211, Hi: 8245, Stride: 34}, + {Lo: 12494, Hi: 65281, Stride: 52787}, + {Lo: 65283, Hi: 65288, Stride: 5}, + {Lo: 65289, Hi: 65292, Stride: 3}, + {Lo: 65306, Hi: 65307, Stride: 1}, + {Lo: 65311, Hi: 65374, Stride: 63}, + }, + R32: []unicode.Range32{}, + LatinOffset: 1, }, - R32: []unicode.Range32{}, - LatinOffset: 1, }, - }, - "pl": { - Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, - With: []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, - Locale: "pl", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 180, Hi: 215, Stride: 35}, - {Lo: 305, Hi: 921, Stride: 616}, - {Lo: 1009, Hi: 1040, Stride: 31}, - {Lo: 1042, Hi: 1045, Stride: 3}, - {Lo: 1047, Hi: 1050, Stride: 3}, - {Lo: 1052, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8216, Hi: 8217, Stride: 1}, - {Lo: 8245, Hi: 12494, Stride: 4249}, - {Lo: 65281, Hi: 65283, Stride: 2}, - {Lo: 65288, Hi: 65289, Stride: 1}, - {Lo: 65292, Hi: 65306, Stride: 14}, - {Lo: 65307, Hi: 65311, Stride: 4}, - {Lo: 65374, Hi: 65374, Stride: 1}, + + "pl": { + Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, + With: []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, + Locale: "pl", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 180, Hi: 215, Stride: 35}, + {Lo: 305, Hi: 921, Stride: 616}, + {Lo: 1009, Hi: 1040, Stride: 31}, + {Lo: 1042, Hi: 1045, Stride: 3}, + {Lo: 1047, Hi: 1050, Stride: 3}, + {Lo: 1052, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8216, Hi: 8217, Stride: 1}, + {Lo: 8245, Hi: 12494, Stride: 4249}, + {Lo: 65281, Hi: 65283, Stride: 2}, + {Lo: 65288, Hi: 65289, Stride: 1}, + {Lo: 65292, Hi: 65306, Stride: 14}, + {Lo: 65307, Hi: 65311, Stride: 4}, + {Lo: 65374, Hi: 65374, Stride: 1}, + }, + R32: []unicode.Range32{}, + LatinOffset: 1, }, - R32: []unicode.Range32{}, - LatinOffset: 1, }, - }, - "pt-BR": { - Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, - With: []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, - Locale: "pt-BR", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 180, Hi: 215, Stride: 35}, - {Lo: 305, Hi: 921, Stride: 616}, - {Lo: 1009, Hi: 1040, Stride: 31}, - {Lo: 1042, Hi: 1045, Stride: 3}, - {Lo: 1047, Hi: 1050, Stride: 3}, - {Lo: 1052, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8216, Hi: 8217, Stride: 1}, - {Lo: 8245, Hi: 12494, Stride: 4249}, - {Lo: 65281, Hi: 65283, Stride: 2}, - {Lo: 65288, Hi: 65289, Stride: 1}, - {Lo: 65292, Hi: 65306, Stride: 14}, - {Lo: 65307, Hi: 65311, Stride: 4}, - {Lo: 65374, Hi: 65374, Stride: 1}, + + "pt-BR": { + Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, + With: []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, + Locale: "pt-BR", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 180, Hi: 215, Stride: 35}, + {Lo: 305, Hi: 921, Stride: 616}, + {Lo: 1009, Hi: 1040, Stride: 31}, + {Lo: 1042, Hi: 1045, Stride: 3}, + {Lo: 1047, Hi: 1050, Stride: 3}, + {Lo: 1052, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8216, Hi: 8217, Stride: 1}, + {Lo: 8245, Hi: 12494, Stride: 4249}, + {Lo: 65281, Hi: 65283, Stride: 2}, + {Lo: 65288, Hi: 65289, Stride: 1}, + {Lo: 65292, Hi: 65306, Stride: 14}, + {Lo: 65307, Hi: 65311, Stride: 4}, + {Lo: 65374, Hi: 65374, Stride: 1}, + }, + R32: []unicode.Range32{}, + LatinOffset: 1, }, - R32: []unicode.Range32{}, - LatinOffset: 1, }, - }, - "qps-ploc": { - Confusable: []rune{160, 180, 215, 305, 921, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, - With: []rune{32, 96, 120, 105, 73, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, - Locale: "qps-ploc", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 160, Hi: 180, Stride: 20}, - {Lo: 215, Hi: 305, Stride: 90}, - {Lo: 921, Hi: 1040, Stride: 119}, - {Lo: 1042, Hi: 1045, Stride: 3}, - {Lo: 1047, Hi: 1050, Stride: 3}, - {Lo: 1052, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8211, Hi: 8216, Stride: 5}, - {Lo: 8217, Hi: 8245, Stride: 28}, - {Lo: 12494, Hi: 65281, Stride: 52787}, - {Lo: 65283, Hi: 65288, Stride: 5}, - {Lo: 65289, Hi: 65292, Stride: 3}, - {Lo: 65306, Hi: 65307, Stride: 1}, - {Lo: 65311, Hi: 65374, Stride: 63}, + + "qps-ploc": { + Confusable: []rune{160, 180, 215, 305, 921, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, + With: []rune{32, 96, 120, 105, 73, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, + Locale: "qps-ploc", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 160, Hi: 180, Stride: 20}, + {Lo: 215, Hi: 305, Stride: 90}, + {Lo: 921, Hi: 1040, Stride: 119}, + {Lo: 1042, Hi: 1045, Stride: 3}, + {Lo: 1047, Hi: 1050, Stride: 3}, + {Lo: 1052, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8211, Hi: 8216, Stride: 5}, + {Lo: 8217, Hi: 8245, Stride: 28}, + {Lo: 12494, Hi: 65281, Stride: 52787}, + {Lo: 65283, Hi: 65288, Stride: 5}, + {Lo: 65289, Hi: 65292, Stride: 3}, + {Lo: 65306, Hi: 65307, Stride: 1}, + {Lo: 65311, Hi: 65374, Stride: 63}, + }, + R32: []unicode.Range32{}, + LatinOffset: 1, }, - R32: []unicode.Range32{}, - LatinOffset: 1, }, - }, - "ru": { - Confusable: []rune{180, 215, 305, 921, 1009, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, - With: []rune{96, 120, 105, 73, 112, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, - Locale: "ru", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 180, Hi: 215, Stride: 35}, - {Lo: 305, Hi: 921, Stride: 616}, - {Lo: 1009, Hi: 8216, Stride: 7207}, - {Lo: 8217, Hi: 8245, Stride: 28}, - {Lo: 12494, Hi: 65281, Stride: 52787}, - {Lo: 65283, Hi: 65288, Stride: 5}, - {Lo: 65289, Hi: 65292, Stride: 3}, - {Lo: 65306, Hi: 65307, Stride: 1}, - {Lo: 65311, Hi: 65374, Stride: 63}, + + "ru": { + Confusable: []rune{180, 215, 305, 921, 1009, 8216, 8217, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, + With: []rune{96, 120, 105, 73, 112, 96, 96, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, + Locale: "ru", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 180, Hi: 215, Stride: 35}, + {Lo: 305, Hi: 921, Stride: 616}, + {Lo: 1009, Hi: 8216, Stride: 7207}, + {Lo: 8217, Hi: 8245, Stride: 28}, + {Lo: 12494, Hi: 65281, Stride: 52787}, + {Lo: 65283, Hi: 65288, Stride: 5}, + {Lo: 65289, Hi: 65292, Stride: 3}, + {Lo: 65306, Hi: 65307, Stride: 1}, + {Lo: 65311, Hi: 65374, Stride: 63}, + }, + R32: []unicode.Range32{}, + LatinOffset: 1, }, - R32: []unicode.Range32{}, - LatinOffset: 1, }, - }, - "tr": { - Confusable: []rune{160, 180, 215, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, - With: []rune{32, 96, 120, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, - Locale: "tr", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 160, Hi: 180, Stride: 20}, - {Lo: 215, Hi: 921, Stride: 706}, - {Lo: 1009, Hi: 1040, Stride: 31}, - {Lo: 1042, Hi: 1045, Stride: 3}, - {Lo: 1047, Hi: 1050, Stride: 3}, - {Lo: 1052, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8211, Hi: 8245, Stride: 34}, - {Lo: 12494, Hi: 65281, Stride: 52787}, - {Lo: 65283, Hi: 65288, Stride: 5}, - {Lo: 65289, Hi: 65292, Stride: 3}, - {Lo: 65306, Hi: 65307, Stride: 1}, - {Lo: 65311, Hi: 65374, Stride: 63}, + + "tr": { + Confusable: []rune{160, 180, 215, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 8245, 12494, 65281, 65283, 65288, 65289, 65292, 65306, 65307, 65311, 65374}, + With: []rune{32, 96, 120, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 96, 47, 33, 35, 40, 41, 44, 58, 59, 63, 126}, + Locale: "tr", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 160, Hi: 180, Stride: 20}, + {Lo: 215, Hi: 921, Stride: 706}, + {Lo: 1009, Hi: 1040, Stride: 31}, + {Lo: 1042, Hi: 1045, Stride: 3}, + {Lo: 1047, Hi: 1050, Stride: 3}, + {Lo: 1052, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8211, Hi: 8245, Stride: 34}, + {Lo: 12494, Hi: 65281, Stride: 52787}, + {Lo: 65283, Hi: 65288, Stride: 5}, + {Lo: 65289, Hi: 65292, Stride: 3}, + {Lo: 65306, Hi: 65307, Stride: 1}, + {Lo: 65311, Hi: 65374, Stride: 63}, + }, + R32: []unicode.Range32{}, + LatinOffset: 1, }, - R32: []unicode.Range32{}, - LatinOffset: 1, }, - }, - "zh-hans": { - Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8245, 12494, 65281, 65288, 65289, 65306, 65374}, - With: []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 47, 33, 40, 41, 58, 126}, - Locale: "zh-hans", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 180, Hi: 215, Stride: 35}, - {Lo: 305, Hi: 921, Stride: 616}, - {Lo: 1009, Hi: 1040, Stride: 31}, - {Lo: 1042, Hi: 1045, Stride: 3}, - {Lo: 1047, Hi: 1050, Stride: 3}, - {Lo: 1052, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8245, Hi: 12494, Stride: 4249}, - {Lo: 65281, Hi: 65288, Stride: 7}, - {Lo: 65289, Hi: 65306, Stride: 17}, - {Lo: 65374, Hi: 65374, Stride: 1}, + + "zh-hans": { + Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8245, 12494, 65281, 65288, 65289, 65306, 65374}, + With: []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 96, 47, 33, 40, 41, 58, 126}, + Locale: "zh-hans", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 180, Hi: 215, Stride: 35}, + {Lo: 305, Hi: 921, Stride: 616}, + {Lo: 1009, Hi: 1040, Stride: 31}, + {Lo: 1042, Hi: 1045, Stride: 3}, + {Lo: 1047, Hi: 1050, Stride: 3}, + {Lo: 1052, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8245, Hi: 12494, Stride: 4249}, + {Lo: 65281, Hi: 65288, Stride: 7}, + {Lo: 65289, Hi: 65306, Stride: 17}, + {Lo: 65374, Hi: 65374, Stride: 1}, + }, + R32: []unicode.Range32{}, + LatinOffset: 1, }, - R32: []unicode.Range32{}, - LatinOffset: 1, }, - }, - "zh-hant": { - Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 12494, 65283, 65307, 65374}, - With: []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 47, 35, 59, 126}, - Locale: "zh-hant", - RangeTable: &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 180, Hi: 215, Stride: 35}, - {Lo: 305, Hi: 921, Stride: 616}, - {Lo: 1009, Hi: 1040, Stride: 31}, - {Lo: 1042, Hi: 1045, Stride: 3}, - {Lo: 1047, Hi: 1050, Stride: 3}, - {Lo: 1052, Hi: 1054, Stride: 1}, - {Lo: 1056, Hi: 1059, Stride: 1}, - {Lo: 1061, Hi: 1068, Stride: 7}, - {Lo: 1072, Hi: 1073, Stride: 1}, - {Lo: 1075, Hi: 1077, Stride: 2}, - {Lo: 1086, Hi: 1088, Stride: 2}, - {Lo: 1089, Hi: 1093, Stride: 2}, - {Lo: 8211, Hi: 12494, Stride: 4283}, - {Lo: 65283, Hi: 65307, Stride: 24}, - {Lo: 65374, Hi: 65374, Stride: 1}, + + "zh-hant": { + Confusable: []rune{180, 215, 305, 921, 1009, 1040, 1042, 1045, 1047, 1050, 1052, 1053, 1054, 1056, 1057, 1058, 1059, 1061, 1068, 1072, 1073, 1075, 1077, 1086, 1088, 1089, 1091, 1093, 8211, 12494, 65283, 65307, 65374}, + With: []rune{96, 120, 105, 73, 112, 65, 66, 69, 51, 75, 77, 72, 79, 80, 67, 84, 89, 88, 98, 97, 54, 114, 101, 111, 112, 99, 121, 120, 45, 47, 35, 59, 126}, + Locale: "zh-hant", + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 180, Hi: 215, Stride: 35}, + {Lo: 305, Hi: 921, Stride: 616}, + {Lo: 1009, Hi: 1040, Stride: 31}, + {Lo: 1042, Hi: 1045, Stride: 3}, + {Lo: 1047, Hi: 1050, Stride: 3}, + {Lo: 1052, Hi: 1054, Stride: 1}, + {Lo: 1056, Hi: 1059, Stride: 1}, + {Lo: 1061, Hi: 1068, Stride: 7}, + {Lo: 1072, Hi: 1073, Stride: 1}, + {Lo: 1075, Hi: 1077, Stride: 2}, + {Lo: 1086, Hi: 1088, Stride: 2}, + {Lo: 1089, Hi: 1093, Stride: 2}, + {Lo: 8211, Hi: 12494, Stride: 4283}, + {Lo: 65283, Hi: 65307, Stride: 24}, + {Lo: 65374, Hi: 65374, Stride: 1}, + }, + R32: []unicode.Range32{}, + LatinOffset: 1, }, - R32: []unicode.Range32{}, - LatinOffset: 1, }, - }, + } } diff --git a/modules/charset/ambiguous_gen_test.go b/modules/charset/ambiguous_gen_test.go index d3be0b1a13..81d2e8065b 100644 --- a/modules/charset/ambiguous_gen_test.go +++ b/modules/charset/ambiguous_gen_test.go @@ -8,11 +8,13 @@ import ( "testing" "unicode" + "code.gitea.io/gitea/modules/translation" + "github.com/stretchr/testify/assert" ) func TestAmbiguousCharacters(t *testing.T) { - for locale, ambiguous := range AmbiguousCharacters { + for locale, ambiguous := range globalVars().ambiguousTableMap { assert.Equal(t, locale, ambiguous.Locale) assert.Len(t, ambiguous.With, len(ambiguous.Confusable)) assert.True(t, sort.SliceIsSorted(ambiguous.Confusable, func(i, j int) bool { @@ -28,4 +30,8 @@ func TestAmbiguousCharacters(t *testing.T) { assert.True(t, found, "%c is not in %d", confusable, i) } } + + var confusableTo rune + ret := isAmbiguous('𝐾', &confusableTo, AmbiguousTablesForLocale(&translation.MockLocale{})...) + assert.True(t, ret) } diff --git a/modules/charset/breakwriter.go b/modules/charset/breakwriter.go deleted file mode 100644 index a87e846466..0000000000 --- a/modules/charset/breakwriter.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package charset - -import ( - "bytes" - "io" -) - -// BreakWriter wraps an io.Writer to always write '\n' as '
' -type BreakWriter struct { - io.Writer -} - -// Write writes the provided byte slice transparently replacing '\n' with '
' -func (b *BreakWriter) Write(bs []byte) (n int, err error) { - pos := 0 - for pos < len(bs) { - idx := bytes.IndexByte(bs[pos:], '\n') - if idx < 0 { - wn, err := b.Writer.Write(bs[pos:]) - return n + wn, err - } - - if idx > 0 { - wn, err := b.Writer.Write(bs[pos : pos+idx]) - n += wn - if err != nil { - return n, err - } - } - - if _, err = b.Writer.Write([]byte("
")); err != nil { - return n, err - } - pos += idx + 1 - - n++ - } - - return n, err -} diff --git a/modules/charset/breakwriter_test.go b/modules/charset/breakwriter_test.go deleted file mode 100644 index 5eeeedc4e2..0000000000 --- a/modules/charset/breakwriter_test.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package charset - -import ( - "strings" - "testing" -) - -func TestBreakWriter_Write(t *testing.T) { - tests := []struct { - name string - kase string - expect string - wantErr bool - }{ - { - name: "noline", - kase: "abcdefghijklmnopqrstuvwxyz", - expect: "abcdefghijklmnopqrstuvwxyz", - }, - { - name: "endline", - kase: "abcdefghijklmnopqrstuvwxyz\n", - expect: "abcdefghijklmnopqrstuvwxyz
", - }, - { - name: "startline", - kase: "\nabcdefghijklmnopqrstuvwxyz", - expect: "
abcdefghijklmnopqrstuvwxyz", - }, - { - name: "onlyline", - kase: "\n\n\n", - expect: "


", - }, - { - name: "empty", - kase: "", - expect: "", - }, - { - name: "midline", - kase: "\nabc\ndefghijkl\nmnopqrstuvwxy\nz", - expect: "
abc
defghijkl
mnopqrstuvwxy
z", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - buf := &strings.Builder{} - b := &BreakWriter{ - Writer: buf, - } - n, err := b.Write([]byte(tt.kase)) - if (err != nil) != tt.wantErr { - t.Errorf("BreakWriter.Write() error = %v, wantErr %v", err, tt.wantErr) - return - } - if n != len(tt.kase) { - t.Errorf("BreakWriter.Write() = %v, want %v", n, len(tt.kase)) - } - if buf.String() != tt.expect { - t.Errorf("BreakWriter.Write() wrote %q, want %v", buf.String(), tt.expect) - } - }) - } -} diff --git a/modules/charset/charset.go b/modules/charset/charset.go index b156654973..96de1c9fcc 100644 --- a/modules/charset/charset.go +++ b/modules/charset/charset.go @@ -6,7 +6,10 @@ package charset import ( "bytes" "io" + "regexp" "strings" + "sync" + "unicode" "unicode/utf8" "code.gitea.io/gitea/modules/setting" @@ -17,8 +20,19 @@ import ( "golang.org/x/text/transform" ) -// UTF8BOM is the utf-8 byte-order marker -var UTF8BOM = []byte{'\xef', '\xbb', '\xbf'} +var globalVars = sync.OnceValue(func() (ret struct { + utf8Bom []byte + + defaultWordRegexp *regexp.Regexp + ambiguousTableMap map[string]*AmbiguousTable + invisibleRangeTable *unicode.RangeTable +}, +) { + ret.utf8Bom = []byte{'\xef', '\xbb', '\xbf'} + ret.ambiguousTableMap = newAmbiguousTableMap() + ret.invisibleRangeTable = newInvisibleRangeTable() + return ret +}) type ConvertOpts struct { KeepBOM bool @@ -105,7 +119,7 @@ func maybeRemoveBOM(content []byte, opts ConvertOpts) []byte { if opts.KeepBOM { return content } - return bytes.TrimPrefix(content, UTF8BOM) + return bytes.TrimPrefix(content, globalVars().utf8Bom) } // DetectEncoding detect the encoding of content diff --git a/modules/charset/escape.go b/modules/charset/escape.go index 167683a298..8f25e7876d 100644 --- a/modules/charset/escape.go +++ b/modules/charset/escape.go @@ -1,10 +1,6 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -//go:generate go run invisible/generate.go -v -o ./invisible_gen.go - -//go:generate go run ambiguous/generate.go -v -o ./ambiguous_gen.go ambiguous/ambiguous.json - package charset import ( @@ -12,36 +8,36 @@ import ( "io" "strings" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/translation" ) -// RuneNBSP is the codepoint for NBSP -const RuneNBSP = 0xa0 +type EscapeOptions struct { + Allowed map[rune]bool +} + +func AllowRuneNBSP() map[rune]bool { + return map[rune]bool{0xa0: true} +} + +func EscapeOptionsForView() EscapeOptions { + return EscapeOptions{ + // it's safe to see NBSP in the view, but maybe not in the diff + Allowed: AllowRuneNBSP(), + } +} // EscapeControlHTML escapes the Unicode control sequences in a provided html document -func EscapeControlHTML(html template.HTML, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output template.HTML) { +func EscapeControlHTML(html template.HTML, locale translation.Locale, opts ...EscapeOptions) (escaped *EscapeStatus, output template.HTML) { if !setting.UI.AmbiguousUnicodeDetection { return &EscapeStatus{}, html } sb := &strings.Builder{} - escaped, _ = EscapeControlReader(strings.NewReader(string(html)), sb, locale, allowed...) // err has been handled in EscapeControlReader + escaped, _ = EscapeControlReader(strings.NewReader(string(html)), sb, locale, opts...) // err has been handled in EscapeControlReader return escaped, template.HTML(sb.String()) } // 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) - - if err = StreamHTML(reader, streamer); err != nil { - streamer.escaped.HasError = true - log.Error("Error whilst escaping: %v", err) - } - return streamer.escaped, err +func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.Locale, opts ...EscapeOptions) (*EscapeStatus, error) { + return escapeStream(locale, reader, writer, opts...) } diff --git a/modules/charset/escape_status.go b/modules/charset/escape_status.go index 37b6ad86d4..fb9ebbb228 100644 --- a/modules/charset/escape_status.go +++ b/modules/charset/escape_status.go @@ -3,11 +3,9 @@ package charset -// EscapeStatus represents the findings of the unicode escaper +// EscapeStatus represents the findings of the Unicode escaper type EscapeStatus struct { - Escaped bool - HasError bool - HasBadRunes bool + Escaped bool // it means that some characters were escaped, and they can also be unescaped back HasInvisible bool HasAmbiguous bool } @@ -19,8 +17,6 @@ func (status *EscapeStatus) Or(other *EscapeStatus) *EscapeStatus { st = &EscapeStatus{} } st.Escaped = st.Escaped || other.Escaped - st.HasError = st.HasError || other.HasError - st.HasBadRunes = st.HasBadRunes || other.HasBadRunes st.HasAmbiguous = st.HasAmbiguous || other.HasAmbiguous st.HasInvisible = st.HasInvisible || other.HasInvisible return st diff --git a/modules/charset/escape_stream.go b/modules/charset/escape_stream.go index 29943eb858..11d09083fc 100644 --- a/modules/charset/escape_stream.go +++ b/modules/charset/escape_stream.go @@ -4,286 +4,415 @@ package charset import ( + "bytes" "fmt" - "regexp" - "strings" + "html" + "io" "unicode" "unicode/utf8" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/translation" - - "golang.org/x/net/html" ) -// VScode defaultWordRegexp -var defaultWordRegexp = regexp.MustCompile(`(-?\d*\.\d\w*)|([^\` + "`" + `\~\!\@\#\$\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s\x00-\x1f]+)`) - -func NewEscapeStreamer(locale translation.Locale, next HTMLStreamer, allowed ...rune) HTMLStreamer { - allowedM := make(map[rune]bool, len(allowed)) - for _, v := range allowed { - allowedM[v] = true - } - return &escapeStreamer{ - escaped: &EscapeStatus{}, - PassthroughHTMLStreamer: *NewPassthroughStreamer(next), - locale: locale, - ambiguousTables: AmbiguousTablesForLocale(locale), - allowed: allowedM, - } +type htmlChunkReader struct { + in io.Reader + readErr error + readBuf []byte + curInTag bool } type escapeStreamer struct { - PassthroughHTMLStreamer + htmlChunkReader + escaped *EscapeStatus locale translation.Locale ambiguousTables []*AmbiguousTable allowed map[rune]bool + + out io.Writer } -func (e *escapeStreamer) EscapeStatus() *EscapeStatus { - return e.escaped +func escapeStream(locale translation.Locale, in io.Reader, out io.Writer, opts ...EscapeOptions) (*EscapeStatus, error) { + es := &escapeStreamer{ + escaped: &EscapeStatus{}, + locale: locale, + ambiguousTables: AmbiguousTablesForLocale(locale), + htmlChunkReader: htmlChunkReader{ + in: in, + readBuf: make([]byte, 0, 32*1024), + }, + out: out, + } + + if len(opts) > 0 { + es.allowed = opts[0].Allowed + } + + readCount := 0 + lastIsTag := false + for { + parts, partInTag, err := es.readRunes() + readCount++ + if err == io.EOF { + return es.escaped, nil + } else if err != nil { + return nil, err + } + for i, part := range parts { + if partInTag[i] { + lastIsTag = true + if _, err := out.Write(part); err != nil { + return nil, err + } + } else { + // if last part is tag, then this part is content begin + // if the content is the first part of the first read, then it's also content begin + isContentBegin := lastIsTag || (readCount == 1 && i == 0) + lastIsTag = false + if isContentBegin { + if part, err = es.trimAndWriteBom(part); err != nil { + return nil, err + } + } + if err = es.detectAndWriteRunes(part); err != nil { + return nil, err + } + } + } + } } -// Text tells the next streamer there is a text -func (e *escapeStreamer) Text(data string) error { - sb := &strings.Builder{} - var until int - var next int +func (e *escapeStreamer) trimAndWriteBom(part []byte) ([]byte, error) { + remaining, ok := bytes.CutPrefix(part, globalVars().utf8Bom) + if ok { + part = remaining + if _, err := e.out.Write(globalVars().utf8Bom); err != nil { + return part, err + } + } + return part, nil +} + +const longSentenceDetectionLimit = 20 + +func (e *escapeStreamer) possibleLongSentence(results []detectResult, pos int) bool { + countBasic := 0 + countNonASCII := 0 + for i := max(pos-longSentenceDetectionLimit, 0); i < min(pos+longSentenceDetectionLimit, len(results)); i++ { + if results[i].runeType == runeTypeBasic && results[i].runeChar != ' ' { + countBasic++ + } + if results[i].runeType == runeTypeNonASCII || results[i].runeType == runeTypeAmbiguous { + countNonASCII++ + } + } + countChar := countBasic + countNonASCII + // many non-ASCII runes around, it seems to be a sentence, + // don't handle the invisible/ambiguous chars in it, otherwise it will be too noisy + return countChar != 0 && countNonASCII*100/countChar >= 50 +} + +func (e *escapeStreamer) analyzeDetectResults(results []detectResult) { + for i := range results { + res := &results[i] + if res.runeType == runeTypeInvisible || res.runeType == runeTypeAmbiguous { + leftIsNonASCII := i > 0 && (results[i-1].runeType == runeTypeNonASCII || results[i-1].runeType == runeTypeAmbiguous) + rightIsNonASCII := i < len(results)-1 && (results[i+1].runeType == runeTypeNonASCII || results[i+1].runeType == runeTypeAmbiguous) + surroundingNonASCII := leftIsNonASCII || rightIsNonASCII + if !surroundingNonASCII { + if len(results) < longSentenceDetectionLimit { + res.needEscape = setting.UI.AmbiguousUnicodeDetection + } else if !e.possibleLongSentence(results, i) { + res.needEscape = setting.UI.AmbiguousUnicodeDetection + } + } + } + } +} + +func (e *escapeStreamer) detectAndWriteRunes(part []byte) error { + results := e.detectRunes(part) + e.analyzeDetectResults(results) + return e.writeDetectResults(part, results) +} + +func (e *htmlChunkReader) readRunes() (parts [][]byte, partInTag []bool, _ error) { + // we have read everything, eof + if e.readErr != nil && len(e.readBuf) == 0 { + return nil, nil, e.readErr + } + + // not eof, and the there is space in the buffer, try to read more data + if e.readErr == nil && len(e.readBuf) <= cap(e.readBuf)*3/4 { + n, err := e.in.Read(e.readBuf[len(e.readBuf):cap(e.readBuf)]) + e.readErr = err + e.readBuf = e.readBuf[:len(e.readBuf)+n] + } + if len(e.readBuf) == 0 { + return nil, nil, e.readErr + } + + // try to exact tag parts and content parts pos := 0 - if len(data) > len(UTF8BOM) && data[:len(UTF8BOM)] == string(UTF8BOM) { - _, _ = sb.WriteString(data[:len(UTF8BOM)]) - pos = len(UTF8BOM) - } - dataBytes := []byte(data) - for pos < len(data) { - nextIdxs := defaultWordRegexp.FindStringIndex(data[pos:]) - if nextIdxs == nil { - until = len(data) - next = until - } else { - until, next = nextIdxs[0]+pos, nextIdxs[1]+pos - } - - // 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) - - for pos < until { - r, sz := utf8.DecodeRune(dataBytes[pos:]) - positions = positions[:0] - positions = append(positions, pos, pos+sz) - types, confusables, _ := e.runeTypes(r) - if err := e.handleRunes(dataBytes, []rune{r}, positions, types, confusables, sb); err != nil { - return err - } - pos += sz - } - - for i := pos; i < next; { - r, sz := utf8.DecodeRune(dataBytes[i:]) - runes = append(runes, r) - positions = append(positions, i) - i += sz - } - positions = append(positions, next) - types, confusables, runeCounts := e.runeTypes(runes...) - if runeCounts.needsEscape() { - if err := e.handleRunes(dataBytes, runes, positions, types, confusables, sb); err != nil { - return err + for pos < len(e.readBuf) { + var curPartEnd int + nextInTag := e.curInTag + if e.curInTag { + // if cur part is in tag, try to find the tag close char '>' + idx := bytes.IndexByte(e.readBuf[pos:], '>') + if idx == -1 { + // if no tag close char, then the whole buffer is in tag + curPartEnd = len(e.readBuf) + } else { + // tag part ends, switch to content part + curPartEnd = pos + idx + 1 + nextInTag = !nextInTag } } else { - _, _ = sb.Write(dataBytes[pos:next]) + // if cur part is in content, try to find the tag open char '<' + idx := bytes.IndexByte(e.readBuf[pos:], '<') + if idx == -1 { + // if no tag open char, then the whole buffer is in content + curPartEnd = len(e.readBuf) + } else { + // content part ends, switch to tag part + curPartEnd = pos + idx + nextInTag = !nextInTag + } + } + + curPartLen := curPartEnd - pos + if curPartLen == 0 { + // if cur part is empty, only need to switch the part type + if e.curInTag == nextInTag { + panic("impossible, curPartLen is 0 but the part in tag status is not switched") + } + e.curInTag = nextInTag + continue + } + + // now, curPartLen can't be 0 + curPart := make([]byte, curPartLen) + copy(curPart, e.readBuf[pos:curPartEnd]) + // now we get the curPart bytes, but we can't directly use it, the last rune in it might have been cut + // try to decode the last rune, if it's invalid, then we cut the last byte and try again until we get a valid rune or no byte left + for i := curPartLen - 1; i >= 0; i-- { + last, lastSize := utf8.DecodeRune(curPart[i:]) + if last == utf8.RuneError && lastSize == 1 { + curPartLen-- + } else { + curPartLen += lastSize - 1 + break + } + } + if curPartLen == 0 { + // actually it's impossible that the part doesn't contain any valid rune, + // the only case is that the cap(readBuf) is too small, or the origin contain indeed doesn't contain any valid rune + // * try to leave the last 4 bytes (possible longest utf-8 encoding) to next round + // * at least consume 1 byte to avoid infinite loop + curPartLen = max(len(curPart)-utf8.UTFMax, 1) + } + + // if curPartLen is not the same as curPart, it means we have cut some bytes, + // need to wait for more data if not eof + trailingCorrupted := curPartLen != len(curPart) + + // finally, we get the real part we need + curPart = curPart[:curPartLen] + parts = append(parts, curPart) + partInTag = append(partInTag, e.curInTag) + + pos += curPartLen + e.curInTag = nextInTag + + if trailingCorrupted && e.readErr == nil { + // if the last part is corrupted, and we haven't reach eof, then we need to wait for more data to get the complete part + break } - pos = next } - if sb.Len() > 0 { - if err := e.PassthroughHTMLStreamer.Text(sb.String()); err != nil { + + copy(e.readBuf, e.readBuf[pos:]) + e.readBuf = e.readBuf[:len(e.readBuf)-pos] + return parts, partInTag, nil +} + +func (e *escapeStreamer) writeDetectResults(data []byte, results []detectResult) error { + lastWriteRawIdx := -1 + for idx := range results { + res := &results[idx] + if !res.needEscape { + if lastWriteRawIdx == -1 { + lastWriteRawIdx = idx + } + continue + } + + if lastWriteRawIdx != -1 { + if _, err := e.out.Write(data[results[lastWriteRawIdx].position:res.position]); err != nil { + return err + } + lastWriteRawIdx = -1 + } + switch res.runeType { + case runeTypeBroken: + if err := e.writeBrokenRune(data[res.position : res.position+res.runeSize]); err != nil { + return err + } + case runeTypeAmbiguous: + if err := e.writeAmbiguousRune(res.runeChar, res.confusable); err != nil { + return err + } + case runeTypeInvisible: + if err := e.writeInvisibleRune(res.runeChar); err != nil { + return err + } + case runeTypeControlChar: + if err := e.writeControlRune(res.runeChar); err != nil { + return err + } + default: + panic("unreachable") + } + } + if lastWriteRawIdx != -1 { + lastResult := results[len(results)-1] + if _, err := e.out.Write(data[results[lastWriteRawIdx].position : lastResult.position+lastResult.runeSize]); err != nil { return err } } return nil } -func (e *escapeStreamer) handleRunes(data []byte, runes []rune, positions []int, types []runeType, confusables []rune, sb *strings.Builder) error { - for i, r := range runes { - switch types[i] { - case brokenRuneType: - if sb.Len() > 0 { - if err := e.PassthroughHTMLStreamer.Text(sb.String()); err != nil { - return err - } - sb.Reset() - } - end := positions[i+1] - start := positions[i] - if err := e.brokenRune(data[start:end]); err != nil { - return err - } - case ambiguousRuneType: - if sb.Len() > 0 { - if err := e.PassthroughHTMLStreamer.Text(sb.String()); err != nil { - return err - } - sb.Reset() - } - if err := e.ambiguousRune(r, confusables[0]); err != nil { - return err - } - confusables = confusables[1:] - case invisibleRuneType: - if sb.Len() > 0 { - if err := e.PassthroughHTMLStreamer.Text(sb.String()); err != nil { - return err - } - sb.Reset() - } - if err := e.invisibleRune(r); err != nil { - return err - } - default: - _, _ = sb.WriteRune(r) - } - } - return nil +func (e *escapeStreamer) writeBrokenRune(_ []byte) (err error) { + // Although we'd like to use the original bytes to display (show the real broken content to users), + // however, when this "escape stream" module is applied to the content, the content has already been processed by other modules. + // So the invalid bytes just can't be kept till this step, in most (all) cases, the only thing we see here is utf8.RuneError + _, err = io.WriteString(e.out, ``) + return err } -func (e *escapeStreamer) brokenRune(bs []byte) error { - e.escaped.Escaped = true - e.escaped.HasBadRunes = true - - if err := e.PassthroughHTMLStreamer.StartTag("span", html.Attribute{ - Key: "class", - Val: "broken-code-point", - }); err != nil { +func (e *escapeStreamer) writeEscapedCharHTML(tag1, attr, tag2, content, tag3 string) (err error) { + _, err = io.WriteString(e.out, tag1) + if err != nil { return err } - if err := e.PassthroughHTMLStreamer.Text(fmt.Sprintf("<%X>", bs)); err != nil { + _, err = io.WriteString(e.out, html.EscapeString(attr)) + if err != nil { return err } - - return e.PassthroughHTMLStreamer.EndTag("span") + _, err = io.WriteString(e.out, tag2) + if err != nil { + return err + } + _, err = io.WriteString(e.out, html.EscapeString(content)) + if err != nil { + return err + } + _, err = io.WriteString(e.out, tag3) + return err } -func (e *escapeStreamer) ambiguousRune(r, c rune) error { +func runeToHex(r rune) string { + return fmt.Sprintf("[U+%04X]", r) +} + +func (e *escapeStreamer) writeAmbiguousRune(r, c rune) (err error) { e.escaped.Escaped = true e.escaped.HasAmbiguous = true - - if err := e.PassthroughHTMLStreamer.StartTag("span", html.Attribute{ - Key: "class", - Val: "ambiguous-code-point", - }, html.Attribute{ - Key: "data-tooltip-content", - Val: e.locale.TrString("repo.ambiguous_character", r, c), - }); err != nil { - return err - } - if err := e.PassthroughHTMLStreamer.StartTag("span", html.Attribute{ - Key: "class", - Val: "char", - }); err != nil { - return err - } - if err := e.PassthroughHTMLStreamer.Text(string(r)); err != nil { - return err - } - if err := e.PassthroughHTMLStreamer.EndTag("span"); err != nil { - return err - } - - return e.PassthroughHTMLStreamer.EndTag("span") + return e.writeEscapedCharHTML( + ``, + string(r), + ``, + ) } -func (e *escapeStreamer) invisibleRune(r rune) error { +func (e *escapeStreamer) writeInvisibleRune(r rune) error { e.escaped.Escaped = true e.escaped.HasInvisible = true - - if err := e.PassthroughHTMLStreamer.StartTag("span", html.Attribute{ - Key: "class", - Val: "escaped-code-point", - }, html.Attribute{ - Key: "data-escaped", - Val: fmt.Sprintf("[U+%04X]", r), - }); err != nil { - return err - } - if err := e.PassthroughHTMLStreamer.StartTag("span", html.Attribute{ - Key: "class", - Val: "char", - }); err != nil { - return err - } - if err := e.PassthroughHTMLStreamer.Text(string(r)); err != nil { - return err - } - if err := e.PassthroughHTMLStreamer.EndTag("span"); err != nil { - return err - } - - return e.PassthroughHTMLStreamer.EndTag("span") + return e.writeEscapedCharHTML( + ``, + string(r), + ``, + ) } -type runeCountType struct { - numBasicRunes int - numNonConfusingNonBasicRunes int - numAmbiguousRunes int - numInvisibleRunes int - numBrokenRunes int +func (e *escapeStreamer) writeControlRune(r rune) error { + var display string + if r >= 0 && r <= 0x1f { + display = string(0x2400 + r) + } else if r == 0x7f { + display = string(rune(0x2421)) + } else { + display = runeToHex(r) + } + return e.writeEscapedCharHTML( + ``, + string(r), + ``, + ) } -func (counts runeCountType) needsEscape() bool { - if counts.numBrokenRunes > 0 { - return true - } - if counts.numBasicRunes == 0 && - counts.numNonConfusingNonBasicRunes > 0 { - return false - } - return counts.numAmbiguousRunes > 0 || counts.numInvisibleRunes > 0 +type detectResult struct { + runeChar rune + runeType int + runeSize int + position int + confusable rune + needEscape bool } -type runeType int - const ( - basicASCIIRuneType runeType = iota // <- This is technically deadcode but its self-documenting so it should stay - brokenRuneType - nonBasicASCIIRuneType - ambiguousRuneType - invisibleRuneType + runeTypeBasic int = iota + runeTypeBroken + runeTypeNonASCII + runeTypeAmbiguous + runeTypeInvisible + runeTypeControlChar ) -func (e *escapeStreamer) runeTypes(runes ...rune) (types []runeType, confusables []rune, runeCounts runeCountType) { - types = make([]runeType, len(runes)) - for i, r := range runes { - var confusable rune +func (e *escapeStreamer) detectRunes(data []byte) []detectResult { + runeCount := utf8.RuneCount(data) + results := make([]detectResult, runeCount) + invisibleRangeTable := globalVars().invisibleRangeTable + var i int + var confusable rune + for pos := 0; pos < len(data); i++ { + r, runeSize := utf8.DecodeRune(data[pos:]) + results[i].runeChar = r + results[i].runeSize = runeSize + results[i].position = pos + pos += runeSize + switch { case r == utf8.RuneError: - types[i] = brokenRuneType - runeCounts.numBrokenRunes++ - case r == ' ' || r == '\t' || r == '\n': - runeCounts.numBasicRunes++ - case e.allowed[r]: - if r > 0x7e || r < 0x20 { - types[i] = nonBasicASCIIRuneType - runeCounts.numNonConfusingNonBasicRunes++ - } else { - runeCounts.numBasicRunes++ + results[i].runeType = runeTypeBroken + results[i].needEscape = true + case r == ' ' || r == '\t' || r == '\n' || e.allowed[r]: + results[i].runeType = runeTypeBasic + if r >= 0x80 { + results[i].runeType = runeTypeNonASCII } - case unicode.Is(InvisibleRanges, r): - types[i] = invisibleRuneType - runeCounts.numInvisibleRunes++ - case unicode.IsControl(r): - types[i] = invisibleRuneType - runeCounts.numInvisibleRunes++ + case r < 0x20 || r == 0x7f: + results[i].runeType = runeTypeControlChar + results[i].needEscape = true + case unicode.Is(invisibleRangeTable, r): + results[i].runeType = runeTypeInvisible + // not sure about results[i].needEscape, will be detected separately case isAmbiguous(r, &confusable, e.ambiguousTables...): - confusables = append(confusables, confusable) - types[i] = ambiguousRuneType - runeCounts.numAmbiguousRunes++ - case r > 0x7e || r < 0x20: - types[i] = nonBasicASCIIRuneType - runeCounts.numNonConfusingNonBasicRunes++ - default: - runeCounts.numBasicRunes++ + results[i].runeType = runeTypeAmbiguous + results[i].confusable = confusable + // not sure about results[i].needEscape, will be detected separately + case r >= 0x80: + results[i].runeType = runeTypeNonASCII + default: // details to basic runes } } - return types, confusables, runeCounts + return results } diff --git a/modules/charset/escape_test.go b/modules/charset/escape_test.go index 9d796a0c18..4e1ff0fcf4 100644 --- a/modules/charset/escape_test.go +++ b/modules/charset/escape_test.go @@ -4,7 +4,6 @@ package charset import ( - "regexp" "strings" "testing" @@ -13,6 +12,7 @@ import ( "code.gitea.io/gitea/modules/translation" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type escapeControlTest struct { @@ -57,24 +57,24 @@ var escapeControlTests = []escapeControlTest{ status: EscapeStatus{}, }, { - name: "hebrew", + name: "hebrew", // old test was wrong, such text shouldn't be escaped text: "עד תקופת יוון העתיקה היה העיסוק במתמטיקה תכליתי בלבד: היא שימשה כאוסף של נוסחאות לחישוב קרקע, אוכלוסין וכו'. פריצת הדרך של היוונים, פרט לתרומותיהם הגדולות לידע המתמטי, הייתה בלימוד המתמטיקה כשלעצמה, מתוקף ערכה הרוחני. יחסם של חלק מהיוונים הקדמונים למתמטיקה היה דתי - למשל, הכת שאסף סביבו פיתגורס האמינה כי המתמטיקה היא הבסיס לכל הדברים. היוונים נחשבים ליוצרי מושג ההוכחה המתמטית, וכן לראשונים שעסקו במתמטיקה לשם עצמה, כלומר כתחום מחקרי עיוני ומופשט ולא רק כעזר שימושי. עם זאת, לצדה", - result: `עד תקופת יוון העתיקה היה העיסוק במתמטיקה תכליתי בלבד: היא שימשה כאוסף של נוסחאות לחישוב קרקע, אוכלוסין וכו'. פריצת הדרך של היוונים, פרט לתרומותיהם הגדולות לידע המתמטי, הייתה בלימוד המתמטיקה כשלעצמה, מתוקף ערכה הרוחני. יחסם של חלק מהיוונים הקדמונים למתמטיקה היה דתי - למשל, הכת שאסף סביבו פיתגורס האמינה כי המתמטיקה היא הבסיס לכל הדברים. היוונים נחשבים ליוצרי מושג ההוכחה המתמטית, וכן לראשונים שעסקו במתמטיקה לשם עצמה, כלומר כתחום מחקרי עיוני ומופשט ולא רק כעזר שימושי. עם זאת, לצדה`, - status: EscapeStatus{Escaped: true, HasAmbiguous: true}, + result: "עד תקופת יוון העתיקה היה העיסוק במתמטיקה תכליתי בלבד: היא שימשה כאוסף של נוסחאות לחישוב קרקע, אוכלוסין וכו'. פריצת הדרך של היוונים, פרט לתרומותיהם הגדולות לידע המתמטי, הייתה בלימוד המתמטיקה כשלעצמה, מתוקף ערכה הרוחני. יחסם של חלק מהיוונים הקדמונים למתמטיקה היה דתי - למשל, הכת שאסף סביבו פיתגורס האמינה כי המתמטיקה היא הבסיס לכל הדברים. היוונים נחשבים ליוצרי מושג ההוכחה המתמטית, וכן לראשונים שעסקו במתמטיקה לשם עצמה, כלומר כתחום מחקרי עיוני ומופשט ולא רק כעזר שימושי. עם זאת, לצדה", + status: EscapeStatus{}, }, { - name: "more hebrew", + name: "more hebrew", // old test was wrong, such text shouldn't be escaped text: `בתקופה מאוחרת יותר, השתמשו היוונים בשיטת סימון מתקדמת יותר, שבה הוצגו המספרים לפי 22 אותיות האלפבית היווני. לסימון המספרים בין 1 ל-9 נקבעו תשע האותיות הראשונות, בתוספת גרש ( ' ) בצד ימין של האות, למעלה; תשע האותיות הבאות ייצגו את העשרות מ-10 עד 90, והבאות את המאות. לסימון הספרות בין 1000 ל-900,000, השתמשו היוונים באותן אותיות, אך הוסיפו לאותיות את הגרש דווקא מצד שמאל של האותיות, למטה. ממיליון ומעלה, כנראה השתמשו היוונים בשני תגים במקום אחד. המתמטיקאי הבולט הראשון ביוון העתיקה, ויש האומרים בתולדות האנושות, הוא תאלס (624 לפנה"ס - 546 לפנה"ס בקירוב).[1] לא יהיה זה משולל יסוד להניח שהוא האדם הראשון שהוכיח משפט מתמטי, ולא רק גילה אותו. תאלס הוכיח שישרים מקבילים חותכים מצד אחד של שוקי זווית קטעים בעלי יחסים שווים (משפט תאלס הראשון), שהזווית המונחת על קוטר במעגל היא זווית ישרה (משפט תאלס השני), שהקוטר מחלק את המעגל לשני חלקים שווים, ושזוויות הבסיס במשולש שווה-שוקיים שוות זו לזו. מיוחסות לו גם שיטות למדידת גובהן של הפירמידות בעזרת מדידת צילן ולקביעת מיקומה של ספינה הנראית מן החוף. בשנים 582 לפנה"ס עד 496 לפנה"ס, בקירוב, חי מתמטיקאי חשוב במיוחד - פיתגורס. המקורות הראשוניים עליו מועטים, וההיסטוריונים מתקשים להפריד את העובדות משכבת המסתורין והאגדות שנקשרו בו. ידוע שסביבו התקבצה האסכולה הפיתגוראית מעין כת פסבדו-מתמטית שהאמינה ש"הכל מספר", או ליתר דיוק הכל ניתן לכימות, וייחסה למספרים משמעויות מיסטיות. ככל הנראה הפיתגוראים ידעו לבנות את הגופים האפלטוניים, הכירו את הממוצע האריתמטי, הממוצע הגאומטרי והממוצע ההרמוני והגיעו להישגים חשובים נוספים. ניתן לומר שהפיתגוראים גילו את היותו של השורש הריבועי של 2, שהוא גם האלכסון בריבוע שאורך צלעותיו 1, אי רציונלי, אך תגליתם הייתה למעשה רק שהקטעים "חסרי מידה משותפת", ומושג המספר האי רציונלי מאוחר יותר.[2] אזכור ראשון לקיומם של קטעים חסרי מידה משותפת מופיע בדיאלוג "תאיטיטוס" של אפלטון, אך רעיון זה היה מוכר עוד קודם לכן, במאה החמישית לפנה"ס להיפאסוס, בן האסכולה הפיתגוראית, ואולי לפיתגורס עצמו.[3]`, - result: `בתקופה מאוחרת יותר, השתמשו היוונים בשיטת סימון מתקדמת יותר, שבה הוצגו המספרים לפי 22 אותיות האלפבית היווני. לסימון המספרים בין 1 ל-9 נקבעו תשע האותיות הראשונות, בתוספת גרש ( ' ) בצד ימין של האות, למעלה; תשע האותיות הבאות ייצגו את העשרות מ-10 עד 90, והבאות את המאות. לסימון הספרות בין 1000 ל-900,000, השתמשו היוונים באותן אותיות, אך הוסיפו לאותיות את הגרש דווקא מצד שמאל של האותיות, למטה. ממיליון ומעלה, כנראה השתמשו היוונים בשני תגים במקום אחד. + result: `בתקופה מאוחרת יותר, השתמשו היוונים בשיטת סימון מתקדמת יותר, שבה הוצגו המספרים לפי 22 אותיות האלפבית היווני. לסימון המספרים בין 1 ל-9 נקבעו תשע האותיות הראשונות, בתוספת גרש ( ' ) בצד ימין של האות, למעלה; תשע האותיות הבאות ייצגו את העשרות מ-10 עד 90, והבאות את המאות. לסימון הספרות בין 1000 ל-900,000, השתמשו היוונים באותן אותיות, אך הוסיפו לאותיות את הגרש דווקא מצד שמאל של האותיות, למטה. ממיליון ומעלה, כנראה השתמשו היוונים בשני תגים במקום אחד. - המתמטיקאי הבולט הראשון ביוון העתיקה, ויש האומרים בתולדות האנושות, הוא תאלס (624 לפנה"ס - 546 לפנה"ס בקירוב).[1] לא יהיה זה משולל יסוד להניח שהוא האדם הראשון שהוכיח משפט מתמטי, ולא רק גילה אותו. תאלס הוכיח שישרים מקבילים חותכים מצד אחד של שוקי זווית קטעים בעלי יחסים שווים (משפט תאלס הראשון), שהזווית המונחת על קוטר במעגל היא זווית ישרה (משפט תאלס השני), שהקוטר מחלק את המעגל לשני חלקים שווים, ושזוויות הבסיס במשולש שווה-שוקיים שוות זו לזו. מיוחסות לו גם שיטות למדידת גובהן של הפירמידות בעזרת מדידת צילן ולקביעת מיקומה של ספינה הנראית מן החוף. + המתמטיקאי הבולט הראשון ביוון העתיקה, ויש האומרים בתולדות האנושות, הוא תאלס (624 לפנה"ס - 546 לפנה"ס בקירוב).[1] לא יהיה זה משולל יסוד להניח שהוא האדם הראשון שהוכיח משפט מתמטי, ולא רק גילה אותו. תאלס הוכיח שישרים מקבילים חותכים מצד אחד של שוקי זווית קטעים בעלי יחסים שווים (משפט תאלס הראשון), שהזווית המונחת על קוטר במעגל היא זווית ישרה (משפט תאלס השני), שהקוטר מחלק את המעגל לשני חלקים שווים, ושזוויות הבסיס במשולש שווה-שוקיים שוות זו לזו. מיוחסות לו גם שיטות למדידת גובהן של הפירמידות בעזרת מדידת צילן ולקביעת מיקומה של ספינה הנראית מן החוף. - בשנים 582 לפנה"ס עד 496 לפנה"ס, בקירוב, חי מתמטיקאי חשוב במיוחד - פיתגורס. המקורות הראשוניים עליו מועטים, וההיסטוריונים מתקשים להפריד את העובדות משכבת המסתורין והאגדות שנקשרו בו. ידוע שסביבו התקבצה האסכולה הפיתגוראית מעין כת פסבדו-מתמטית שהאמינה ש"הכל מספר", או ליתר דיוק הכל ניתן לכימות, וייחסה למספרים משמעויות מיסטיות. ככל הנראה הפיתגוראים ידעו לבנות את הגופים האפלטוניים, הכירו את הממוצע האריתמטי, הממוצע הגאומטרי והממוצע ההרמוני והגיעו להישגים חשובים נוספים. ניתן לומר שהפיתגוראים גילו את היותו של השורש הריבועי של 2, שהוא גם האלכסון בריבוע שאורך צלעותיו 1, אי רציונלי, אך תגליתם הייתה למעשה רק שהקטעים "חסרי מידה משותפת", ומושג המספר האי רציונלי מאוחר יותר.[2] אזכור ראשון לקיומם של קטעים חסרי מידה משותפת מופיע בדיאלוג "תאיטיטוס" של אפלטון, אך רעיון זה היה מוכר עוד קודם לכן, במאה החמישית לפנה"ס להיפאסוס, בן האסכולה הפיתגוראית, ואולי לפיתגורס עצמו.[3]`, - status: EscapeStatus{Escaped: true, HasAmbiguous: true}, + בשנים 582 לפנה"ס עד 496 לפנה"ס, בקירוב, חי מתמטיקאי חשוב במיוחד - פיתגורס. המקורות הראשוניים עליו מועטים, וההיסטוריונים מתקשים להפריד את העובדות משכבת המסתורין והאגדות שנקשרו בו. ידוע שסביבו התקבצה האסכולה הפיתגוראית מעין כת פסבדו-מתמטית שהאמינה ש"הכל מספר", או ליתר דיוק הכל ניתן לכימות, וייחסה למספרים משמעויות מיסטיות. ככל הנראה הפיתגוראים ידעו לבנות את הגופים האפלטוניים, הכירו את הממוצע האריתמטי, הממוצע הגאומטרי והממוצע ההרמוני והגיעו להישגים חשובים נוספים. ניתן לומר שהפיתגוראים גילו את היותו של השורש הריבועי של 2, שהוא גם האלכסון בריבוע שאורך צלעותיו 1, אי רציונלי, אך תגליתם הייתה למעשה רק שהקטעים "חסרי מידה משותפת", ומושג המספר האי רציונלי מאוחר יותר.[2] אזכור ראשון לקיומם של קטעים חסרי מידה משותפת מופיע בדיאלוג "תאיטיטוס" של אפלטון, אך רעיון זה היה מוכר עוד קודם לכן, במאה החמישית לפנה"ס להיפאסוס, בן האסכולה הפיתגוראית, ואולי לפיתגורס עצמו.[3]`, + status: EscapeStatus{}, }, { name: "Mixed RTL+LTR", @@ -111,7 +111,7 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`, { name: "CVE testcase", text: "if access_level != \"user\u202E \u2066// Check if admin\u2069 \u2066\" {", - result: `if access_level != "user` + "\u202e" + ` ` + "\u2066" + `// Check if admin` + "\u2069" + ` ` + "\u2066" + `" {`, + result: `if access_level != "user` + "\u202e" + ` ` + "\u2066" + `// Check if admin` + "\u2069" + ` ` + "\u2066" + `" {`, status: EscapeStatus{Escaped: true, HasInvisible: true}, }, { @@ -123,7 +123,7 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`, result: `Many computer programs fail to display bidirectional text correctly. For example, the Hebrew name Sarah ` + "\u2067" + `שרה` + "\u2066\n" + `sin (ש) (which appears rightmost), then resh (ר), and finally heh (ה) (which should appear leftmost).` + - "\n" + `if access_level != "user` + "\u202e" + ` ` + "\u2066" + `// Check if admin` + "\u2069" + ` ` + "\u2066" + `" {` + "\n", + "\n" + `if access_level != "user` + "\u202e" + ` ` + "\u2066" + `// Check if admin` + "\u2069" + ` ` + "\u2066" + `" {` + "\n", status: EscapeStatus{Escaped: true, HasInvisible: true}, }, { @@ -134,38 +134,22 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`, result: "\xef\xbb\xbftest", status: EscapeStatus{}, }, + { + name: "ambiguous", + text: "O𝐾", + result: `O𝐾`, + status: EscapeStatus{Escaped: true, HasAmbiguous: true}, + }, } func TestEscapeControlReader(t *testing.T) { - // add some control characters to the tests - tests := make([]escapeControlTest, 0, len(escapeControlTests)*3) - copy(tests, escapeControlTests) - - // if there is a BOM, we should keep the BOM - addPrefix := func(prefix, s string) string { - if strings.HasPrefix(s, "\xef\xbb\xbf") { - return s[:3] + prefix + s[3:] - } - return prefix + s - } - for _, test := range escapeControlTests { - test.name += " (+Control)" - test.text = addPrefix("\u001E", test.text) - test.result = addPrefix(``+"\u001e"+``, test.result) - test.status.Escaped = true - test.status.HasInvisible = true - tests = append(tests, test) - } - - re := regexp.MustCompile(`repo.ambiguous_character:\d+,\d+`) // simplify the output for the tests, remove the translation variants - for _, tt := range tests { + for _, tt := range escapeControlTests { t.Run(tt.name, func(t *testing.T) { output := &strings.Builder{} status, err := EscapeControlReader(strings.NewReader(tt.text), output, &translation.MockLocale{}) assert.NoError(t, err) assert.Equal(t, tt.status, *status) outStr := output.String() - outStr = re.ReplaceAllString(outStr, "repo.ambiguous_character") assert.Equal(t, tt.result, outStr) }) } @@ -179,3 +163,50 @@ func TestSettingAmbiguousUnicodeDetection(t *testing.T) { _, out = EscapeControlHTML("a test", &translation.MockLocale{}) assert.EqualValues(t, `a test`, out) } + +func TestHTMLChunkReader(t *testing.T) { + type textPart struct { + text string + isTag bool + } + testReadChunks := func(t *testing.T, chunkSize int, input string, expected []textPart) { + r := &htmlChunkReader{in: strings.NewReader(input), readBuf: make([]byte, 0, chunkSize)} + var results []textPart + for { + parts, partIsTag, err := r.readRunes() + if err != nil { + break + } + for i, part := range parts { + results = append(results, textPart{string(part), partIsTag[i]}) + } + } + assert.Equal(t, expected, results, "chunk size: %d, input: %s", chunkSize, input) + } + + testReadChunks(t, 10, "abcghi", []textPart{ + {text: "abc", isTag: false}, + {text: "", isTag: true}, + {text: "gh", isTag: false}, + // -- chunk + {text: "i", isTag: false}, + }) + + testReadChunks(t, 10, "ghi", []textPart{ + {text: "", isTag: true}, + {text: "", isTag: true}, + // -- chunk + {text: "ghi", isTag: false}, + }) + + rune1, rune2, rune3, rune4 := "A", "é", "啊", "🌞" + require.Len(t, rune1, 1) + require.Len(t, rune2, 2) + require.Len(t, rune3, 3) + require.Len(t, rune4, 4) + input := "<" + rune1 + rune2 + rune3 + rune4 + ">" + rune1 + rune2 + rune3 + rune4 + testReadChunks(t, 4, input, []textPart{{"", true}, {"Aé", false}, {"啊", false}, {"🌞", false}}) + testReadChunks(t, 5, input, []textPart{{"", true}, {"Aé", false}, {"啊", false}, {"🌞", false}}) + testReadChunks(t, 6, input, []textPart{{"", true}, {"A", false}, {"é啊", false}, {"🌞", false}}) + testReadChunks(t, 7, input, []textPart{{"", true}, {"A", false}, {"é啊", false}, {"🌞", false}}) +} diff --git a/modules/charset/ambiguous/ambiguous.json b/modules/charset/generate/ambiguous.json similarity index 100% rename from modules/charset/ambiguous/ambiguous.json rename to modules/charset/generate/ambiguous.json diff --git a/modules/charset/invisible/generate.go b/modules/charset/generate/generate.go similarity index 57% rename from modules/charset/invisible/generate.go rename to modules/charset/generate/generate.go index bd57dd6c4d..16ea53fda1 100644 --- a/modules/charset/invisible/generate.go +++ b/modules/charset/generate/generate.go @@ -5,37 +5,86 @@ package main import ( "bytes" - "flag" "fmt" "go/format" + "log" "os" + "sort" "text/template" + "unicode" + + "code.gitea.io/gitea/modules/json" "golang.org/x/text/unicode/rangetable" ) +// ambiguous.json provides a one to one mapping of ambiguous characters to other characters +// See https://github.com/hediet/vscode-unicode-data/blob/main/out/ambiguous.json + +type AmbiguousTable struct { + Confusable []rune + With []rune + Locale string + RangeTable *unicode.RangeTable +} + +type RunePair struct { + Confusable rune + With rune +} + // InvisibleRunes these are runes that vscode has assigned to be invisible // See https://github.com/hediet/vscode-unicode-data var InvisibleRunes = []rune{ 9, 10, 11, 12, 13, 32, 127, 160, 173, 847, 1564, 4447, 4448, 6068, 6069, 6155, 6156, 6157, 6158, 7355, 7356, 8192, 8193, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201, 8202, 8203, 8204, 8205, 8206, 8207, 8234, 8235, 8236, 8237, 8238, 8239, 8287, 8288, 8289, 8290, 8291, 8292, 8293, 8294, 8295, 8296, 8297, 8298, 8299, 8300, 8301, 8302, 8303, 10240, 12288, 12644, 65024, 65025, 65026, 65027, 65028, 65029, 65030, 65031, 65032, 65033, 65034, 65035, 65036, 65037, 65038, 65039, 65279, 65440, 65520, 65521, 65522, 65523, 65524, 65525, 65526, 65527, 65528, 65532, 78844, 119155, 119156, 119157, 119158, 119159, 119160, 119161, 119162, 917504, 917505, 917506, 917507, 917508, 917509, 917510, 917511, 917512, 917513, 917514, 917515, 917516, 917517, 917518, 917519, 917520, 917521, 917522, 917523, 917524, 917525, 917526, 917527, 917528, 917529, 917530, 917531, 917532, 917533, 917534, 917535, 917536, 917537, 917538, 917539, 917540, 917541, 917542, 917543, 917544, 917545, 917546, 917547, 917548, 917549, 917550, 917551, 917552, 917553, 917554, 917555, 917556, 917557, 917558, 917559, 917560, 917561, 917562, 917563, 917564, 917565, 917566, 917567, 917568, 917569, 917570, 917571, 917572, 917573, 917574, 917575, 917576, 917577, 917578, 917579, 917580, 917581, 917582, 917583, 917584, 917585, 917586, 917587, 917588, 917589, 917590, 917591, 917592, 917593, 917594, 917595, 917596, 917597, 917598, 917599, 917600, 917601, 917602, 917603, 917604, 917605, 917606, 917607, 917608, 917609, 917610, 917611, 917612, 917613, 917614, 917615, 917616, 917617, 917618, 917619, 917620, 917621, 917622, 917623, 917624, 917625, 917626, 917627, 917628, 917629, 917630, 917631, 917760, 917761, 917762, 917763, 917764, 917765, 917766, 917767, 917768, 917769, 917770, 917771, 917772, 917773, 917774, 917775, 917776, 917777, 917778, 917779, 917780, 917781, 917782, 917783, 917784, 917785, 917786, 917787, 917788, 917789, 917790, 917791, 917792, 917793, 917794, 917795, 917796, 917797, 917798, 917799, 917800, 917801, 917802, 917803, 917804, 917805, 917806, 917807, 917808, 917809, 917810, 917811, 917812, 917813, 917814, 917815, 917816, 917817, 917818, 917819, 917820, 917821, 917822, 917823, 917824, 917825, 917826, 917827, 917828, 917829, 917830, 917831, 917832, 917833, 917834, 917835, 917836, 917837, 917838, 917839, 917840, 917841, 917842, 917843, 917844, 917845, 917846, 917847, 917848, 917849, 917850, 917851, 917852, 917853, 917854, 917855, 917856, 917857, 917858, 917859, 917860, 917861, 917862, 917863, 917864, 917865, 917866, 917867, 917868, 917869, 917870, 917871, 917872, 917873, 917874, 917875, 917876, 917877, 917878, 917879, 917880, 917881, 917882, 917883, 917884, 917885, 917886, 917887, 917888, 917889, 917890, 917891, 917892, 917893, 917894, 917895, 917896, 917897, 917898, 917899, 917900, 917901, 917902, 917903, 917904, 917905, 917906, 917907, 917908, 917909, 917910, 917911, 917912, 917913, 917914, 917915, 917916, 917917, 917918, 917919, 917920, 917921, 917922, 917923, 917924, 917925, 917926, 917927, 917928, 917929, 917930, 917931, 917932, 917933, 917934, 917935, 917936, 917937, 917938, 917939, 917940, 917941, 917942, 917943, 917944, 917945, 917946, 917947, 917948, 917949, 917950, 917951, 917952, 917953, 917954, 917955, 917956, 917957, 917958, 917959, 917960, 917961, 917962, 917963, 917964, 917965, 917966, 917967, 917968, 917969, 917970, 917971, 917972, 917973, 917974, 917975, 917976, 917977, 917978, 917979, 917980, 917981, 917982, 917983, 917984, 917985, 917986, 917987, 917988, 917989, 917990, 917991, 917992, 917993, 917994, 917995, 917996, 917997, 917998, 917999, } -var verbose bool - -func main() { - flag.Usage = func() { - fmt.Fprintf(os.Stderr, `%s: Generate InvisibleRunesRange - -Usage: %[1]s [-v] [-o output.go] -`, os.Args[0]) - flag.PrintDefaults() +func generateAmbiguous() { + bs, err := os.ReadFile("ambiguous.json") + if err != nil { + log.Fatalf("Unable to read, err: %v", err) } - output := "" - flag.BoolVar(&verbose, "v", false, "verbose output") - flag.StringVar(&output, "o", "invisible_gen.go", "file to output to") - flag.Parse() + var unwrapped string + if err := json.Unmarshal(bs, &unwrapped); err != nil { + log.Fatalf("Unable to unwrap content in, err: %v", err) + } + fromJSON := map[string][]uint32{} + if err := json.Unmarshal([]byte(unwrapped), &fromJSON); err != nil { + log.Fatalf("Unable to unmarshal content in, err: %v", err) + } + + tables := make([]*AmbiguousTable, 0, len(fromJSON)) + for locale, chars := range fromJSON { + table := &AmbiguousTable{Locale: locale} + table.Confusable = make([]rune, 0, len(chars)/2) + table.With = make([]rune, 0, len(chars)/2) + pairs := make([]RunePair, len(chars)/2) + for i := 0; i < len(chars); i += 2 { + pairs[i/2].Confusable, pairs[i/2].With = rune(chars[i]), rune(chars[i+1]) + } + sort.Slice(pairs, func(i, j int) bool { + return pairs[i].Confusable < pairs[j].Confusable + }) + for _, pair := range pairs { + table.Confusable = append(table.Confusable, pair.Confusable) + table.With = append(table.With, pair.With) + } + table.RangeTable = rangetable.New(table.Confusable...) + tables = append(tables, table) + } + sort.Slice(tables, func(i, j int) bool { + return tables[i].Locale < tables[j].Locale + }) + data := map[string]any{"Tables": tables} + + if err := runTemplate(templateAmbiguous, "../ambiguous_gen.go", &data); err != nil { + log.Fatalf("Unable to run template: %v", err) + } +} + +func generateInvisible() { // First we filter the runes to remove // filtered := make([]rune, 0, len(InvisibleRunes)) @@ -47,8 +96,8 @@ Usage: %[1]s [-v] [-o output.go] } table := rangetable.New(filtered...) - if err := runTemplate(generatorTemplate, output, table); err != nil { - fatalf("Unable to run template: %v", err) + if err := runTemplate(generatorInvisible, "../invisible_gen.go", table); err != nil { + log.Fatalf("Unable to run template: %v", err) } } @@ -59,7 +108,7 @@ func runTemplate(t *template.Template, filename string, data any) error { } bs, err := format.Source(buf.Bytes()) if err != nil { - verbosef("Bad source:\n%s", buf.String()) + log.Printf("Bad source:\n%s", buf.String()) return fmt.Errorf("unable to format source: %w", err) } @@ -85,37 +134,68 @@ func runTemplate(t *template.Template, filename string, data any) error { return nil } -var generatorTemplate = template.Must(template.New("invisibleTemplate").Parse(`// This file is generated by modules/charset/invisible/generate.go DO NOT EDIT -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT +func main() { + generateAmbiguous() + generateInvisible() +} +var templateAmbiguous = template.Must(template.New("ambiguousTemplate").Parse(`// This file is generated by modules/charset/generate/generate.go DO NOT EDIT +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT package charset import "unicode" -var InvisibleRanges = &unicode.RangeTable{ - R16: []unicode.Range16{ -{{range .R16 }} {Lo:{{.Lo}}, Hi:{{.Hi}}, Stride: {{.Stride}}}, -{{end}} }, - R32: []unicode.Range32{ -{{range .R32}} {Lo:{{.Lo}}, Hi:{{.Hi}}, Stride: {{.Stride}}}, -{{end}} }, - LatinOffset: {{.LatinOffset}}, +// This file is generated from https://github.com/hediet/vscode-unicode-data/blob/main/out/ambiguous.json + +// AmbiguousTable matches a confusable rune with its partner for the Locale +type AmbiguousTable struct { + Confusable []rune + With []rune + Locale string + RangeTable *unicode.RangeTable +} + +func newAmbiguousTableMap() map[string]*AmbiguousTable { + return map[string]*AmbiguousTable { + {{- range .Tables}} + {{printf "%q" .Locale}}: { + Confusable: []rune{ {{range .Confusable}}{{.}},{{end}} }, + With: []rune{ {{range .With}}{{.}},{{end}} }, + Locale: {{printf "%q" .Locale}}, + RangeTable: &unicode.RangeTable{ + R16: []unicode.Range16{ + {{range .RangeTable.R16 }} {Lo:{{.Lo}}, Hi:{{.Hi}}, Stride: {{.Stride}}}, + {{end}} }, + R32: []unicode.Range32{ + {{range .RangeTable.R32}} {Lo:{{.Lo}}, Hi:{{.Hi}}, Stride: {{.Stride}}}, + {{end}} }, + LatinOffset: {{.RangeTable.LatinOffset}}, + }, + }, + {{end}} + } } `)) -func logf(format string, args ...any) { - fmt.Fprintf(os.Stderr, format+"\n", args...) -} +var generatorInvisible = template.Must(template.New("invisibleTemplate").Parse(`// This file is generated by modules/charset/generate/generate.go DO NOT EDIT +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT -func verbosef(format string, args ...any) { - if verbose { - logf(format, args...) +package charset + +import "unicode" + +func newInvisibleRangeTable() *unicode.RangeTable { + return &unicode.RangeTable{ + R16: []unicode.Range16{ +{{range .R16 }} {Lo:{{.Lo}}, Hi:{{.Hi}}, Stride: {{.Stride}}}, + {{end}}}, + R32: []unicode.Range32{ +{{range .R32}} {Lo:{{.Lo}}, Hi:{{.Hi}}, Stride: {{.Stride}}}, + {{end}}}, + LatinOffset: {{.LatinOffset}}, } } - -func fatalf(format string, args ...any) { - logf("fatal: "+format+"\n", args...) - os.Exit(1) -} +`)) diff --git a/modules/charset/htmlstream.go b/modules/charset/htmlstream.go deleted file mode 100644 index 61f29120a6..0000000000 --- a/modules/charset/htmlstream.go +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package charset - -import ( - "fmt" - "io" - - "golang.org/x/net/html" -) - -// HTMLStreamer represents a SAX-like interface for HTML -type HTMLStreamer interface { - Error(err error) error - Doctype(data string) error - Comment(data string) error - StartTag(data string, attrs ...html.Attribute) error - SelfClosingTag(data string, attrs ...html.Attribute) error - EndTag(data string) error - Text(data string) error -} - -// PassthroughHTMLStreamer is a passthrough streamer -type PassthroughHTMLStreamer struct { - next HTMLStreamer -} - -func NewPassthroughStreamer(next HTMLStreamer) *PassthroughHTMLStreamer { - return &PassthroughHTMLStreamer{next: next} -} - -var _ (HTMLStreamer) = &PassthroughHTMLStreamer{} - -// Error tells the next streamer in line that there is an error -func (p *PassthroughHTMLStreamer) Error(err error) error { - return p.next.Error(err) -} - -// Doctype tells the next streamer what the doctype is -func (p *PassthroughHTMLStreamer) Doctype(data string) error { - return p.next.Doctype(data) -} - -// Comment tells the next streamer there is a comment -func (p *PassthroughHTMLStreamer) Comment(data string) error { - return p.next.Comment(data) -} - -// StartTag tells the next streamer there is a starting tag -func (p *PassthroughHTMLStreamer) StartTag(data string, attrs ...html.Attribute) error { - return p.next.StartTag(data, attrs...) -} - -// SelfClosingTag tells the next streamer there is a self-closing tag -func (p *PassthroughHTMLStreamer) SelfClosingTag(data string, attrs ...html.Attribute) error { - return p.next.SelfClosingTag(data, attrs...) -} - -// EndTag tells the next streamer there is a end tag -func (p *PassthroughHTMLStreamer) EndTag(data string) error { - return p.next.EndTag(data) -} - -// Text tells the next streamer there is a text -func (p *PassthroughHTMLStreamer) Text(data string) error { - return p.next.Text(data) -} - -// HTMLStreamWriter acts as a writing sink -type HTMLStreamerWriter struct { - io.Writer - err error -} - -// Write implements io.Writer -func (h *HTMLStreamerWriter) Write(data []byte) (int, error) { - if h.err != nil { - return 0, h.err - } - return h.Writer.Write(data) -} - -// Write implements io.StringWriter -func (h *HTMLStreamerWriter) WriteString(data string) (int, error) { - if h.err != nil { - return 0, h.err - } - return h.Writer.Write([]byte(data)) -} - -// Error tells the next streamer in line that there is an error -func (h *HTMLStreamerWriter) Error(err error) error { - if h.err == nil { - h.err = err - } - return h.err -} - -// Doctype tells the next streamer what the doctype is -func (h *HTMLStreamerWriter) Doctype(data string) error { - _, h.err = h.WriteString("") - return h.err -} - -// Comment tells the next streamer there is a comment -func (h *HTMLStreamerWriter) Comment(data string) error { - _, h.err = h.WriteString("") - return h.err -} - -// StartTag tells the next streamer there is a starting tag -func (h *HTMLStreamerWriter) StartTag(data string, attrs ...html.Attribute) error { - return h.startTag(data, attrs, false) -} - -// SelfClosingTag tells the next streamer there is a self-closing tag -func (h *HTMLStreamerWriter) SelfClosingTag(data string, attrs ...html.Attribute) error { - return h.startTag(data, attrs, true) -} - -func (h *HTMLStreamerWriter) startTag(data string, attrs []html.Attribute, selfclosing bool) error { - if _, h.err = h.WriteString("<" + data); h.err != nil { - return h.err - } - for _, attr := range attrs { - if _, h.err = h.WriteString(" " + attr.Key + "=\"" + html.EscapeString(attr.Val) + "\""); h.err != nil { - return h.err - } - } - if selfclosing { - if _, h.err = h.WriteString("/>"); h.err != nil { - return h.err - } - } else { - if _, h.err = h.WriteString(">"); h.err != nil { - return h.err - } - } - return h.err -} - -// EndTag tells the next streamer there is a end tag -func (h *HTMLStreamerWriter) EndTag(data string) error { - _, h.err = h.WriteString("") - return h.err -} - -// Text tells the next streamer there is a text -func (h *HTMLStreamerWriter) Text(data string) error { - _, h.err = h.WriteString(html.EscapeString(data)) - return h.err -} - -// StreamHTML streams an html to a provided streamer -func StreamHTML(source io.Reader, streamer HTMLStreamer) error { - tokenizer := html.NewTokenizer(source) - for { - tt := tokenizer.Next() - switch tt { - case html.ErrorToken: - if tokenizer.Err() != io.EOF { - return tokenizer.Err() - } - return nil - case html.DoctypeToken: - token := tokenizer.Token() - if err := streamer.Doctype(token.Data); err != nil { - return err - } - case html.CommentToken: - token := tokenizer.Token() - if err := streamer.Comment(token.Data); err != nil { - return err - } - case html.StartTagToken: - token := tokenizer.Token() - if err := streamer.StartTag(token.Data, token.Attr...); err != nil { - return err - } - case html.SelfClosingTagToken: - token := tokenizer.Token() - if err := streamer.StartTag(token.Data, token.Attr...); err != nil { - return err - } - case html.EndTagToken: - token := tokenizer.Token() - if err := streamer.EndTag(token.Data); err != nil { - return err - } - case html.TextToken: - token := tokenizer.Token() - if err := streamer.Text(token.Data); err != nil { - return err - } - default: - return fmt.Errorf("unknown type of token: %d", tt) - } - } -} diff --git a/modules/charset/invisible_gen.go b/modules/charset/invisible_gen.go index 812f0e34b3..ddda875a9f 100644 --- a/modules/charset/invisible_gen.go +++ b/modules/charset/invisible_gen.go @@ -1,36 +1,38 @@ -// This file is generated by modules/charset/invisible/generate.go DO NOT EDIT -// Copyright 2022 The Gitea Authors. All rights reserved. +// This file is generated by modules/charset/generate/generate.go DO NOT EDIT +// Copyright 2026 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package charset import "unicode" -var InvisibleRanges = &unicode.RangeTable{ - R16: []unicode.Range16{ - {Lo: 11, Hi: 13, Stride: 1}, - {Lo: 127, Hi: 160, Stride: 33}, - {Lo: 173, Hi: 847, Stride: 674}, - {Lo: 1564, Hi: 4447, Stride: 2883}, - {Lo: 4448, Hi: 6068, Stride: 1620}, - {Lo: 6069, Hi: 6155, Stride: 86}, - {Lo: 6156, Hi: 6158, Stride: 1}, - {Lo: 7355, Hi: 7356, Stride: 1}, - {Lo: 8192, Hi: 8207, Stride: 1}, - {Lo: 8234, Hi: 8239, Stride: 1}, - {Lo: 8287, Hi: 8303, Stride: 1}, - {Lo: 10240, Hi: 12288, Stride: 2048}, - {Lo: 12644, Hi: 65024, Stride: 52380}, - {Lo: 65025, Hi: 65039, Stride: 1}, - {Lo: 65279, Hi: 65440, Stride: 161}, - {Lo: 65520, Hi: 65528, Stride: 1}, - {Lo: 65532, Hi: 65532, Stride: 1}, - }, - R32: []unicode.Range32{ - {Lo: 78844, Hi: 119155, Stride: 40311}, - {Lo: 119156, Hi: 119162, Stride: 1}, - {Lo: 917504, Hi: 917631, Stride: 1}, - {Lo: 917760, Hi: 917999, Stride: 1}, - }, - LatinOffset: 2, +func newInvisibleRangeTable() *unicode.RangeTable { + return &unicode.RangeTable{ + R16: []unicode.Range16{ + {Lo: 11, Hi: 13, Stride: 1}, + {Lo: 127, Hi: 160, Stride: 33}, + {Lo: 173, Hi: 847, Stride: 674}, + {Lo: 1564, Hi: 4447, Stride: 2883}, + {Lo: 4448, Hi: 6068, Stride: 1620}, + {Lo: 6069, Hi: 6155, Stride: 86}, + {Lo: 6156, Hi: 6158, Stride: 1}, + {Lo: 7355, Hi: 7356, Stride: 1}, + {Lo: 8192, Hi: 8207, Stride: 1}, + {Lo: 8234, Hi: 8239, Stride: 1}, + {Lo: 8287, Hi: 8303, Stride: 1}, + {Lo: 10240, Hi: 12288, Stride: 2048}, + {Lo: 12644, Hi: 65024, Stride: 52380}, + {Lo: 65025, Hi: 65039, Stride: 1}, + {Lo: 65279, Hi: 65440, Stride: 161}, + {Lo: 65520, Hi: 65528, Stride: 1}, + {Lo: 65532, Hi: 65532, Stride: 1}, + }, + R32: []unicode.Range32{ + {Lo: 78844, Hi: 119155, Stride: 40311}, + {Lo: 119156, Hi: 119162, Stride: 1}, + {Lo: 917504, Hi: 917631, Stride: 1}, + {Lo: 917760, Hi: 917999, Stride: 1}, + }, + LatinOffset: 2, + } } diff --git a/modules/commitstatus/commit_status.go b/modules/commitstatus/commit_status.go index a0ab4e7186..9c6994249b 100644 --- a/modules/commitstatus/commit_status.go +++ b/modules/commitstatus/commit_status.go @@ -61,16 +61,17 @@ type CommitStatusStates []CommitStatusState //nolint:revive // export stutter // According to https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#get-the-combined-status-for-a-specific-reference // > Additionally, a combined state is returned. The state is one of: // > failure if any of the contexts report as error or failure +// > failure if any of the contexts report as warning (Gitea specific behavior) // > pending if there are no statuses or a context is pending // > success if the latest status for all contexts is success func (css CommitStatusStates) Combine() CommitStatusState { successCnt := 0 for _, state := range css { switch { - case state.IsError() || state.IsFailure(): + case state.IsError() || state.IsFailure() || state.IsWarning(): return CommitStatusFailure case state.IsPending(): - case state.IsSuccess() || state.IsWarning() || state.IsSkipped(): + case state.IsSuccess() || state.IsSkipped(): successCnt++ } } diff --git a/modules/commitstatus/commit_status_test.go b/modules/commitstatus/commit_status_test.go index 10d8f20aa4..dc8f9ce1a5 100644 --- a/modules/commitstatus/commit_status_test.go +++ b/modules/commitstatus/commit_status_test.go @@ -41,7 +41,7 @@ func TestCombine(t *testing.T) { { name: "warning", states: CommitStatusStates{CommitStatusWarning}, - expected: CommitStatusSuccess, + expected: CommitStatusFailure, }, // 2 states { @@ -62,7 +62,7 @@ func TestCombine(t *testing.T) { { name: "pending and warning", states: CommitStatusStates{CommitStatusPending, CommitStatusWarning}, - expected: CommitStatusPending, + expected: CommitStatusFailure, }, { name: "success and error", @@ -77,7 +77,7 @@ func TestCombine(t *testing.T) { { name: "success and warning", states: CommitStatusStates{CommitStatusSuccess, CommitStatusWarning}, - expected: CommitStatusSuccess, + expected: CommitStatusFailure, }, { name: "error and failure", @@ -98,7 +98,7 @@ func TestCombine(t *testing.T) { { name: "pending, success and warning", states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusWarning}, - expected: CommitStatusPending, + expected: CommitStatusFailure, }, { name: "pending, success and error", @@ -133,7 +133,7 @@ func TestCombine(t *testing.T) { { name: "success, warning and skipped", states: CommitStatusStates{CommitStatusSuccess, CommitStatusWarning, CommitStatusSkipped}, - expected: CommitStatusSuccess, + expected: CommitStatusFailure, }, // All success { @@ -181,12 +181,12 @@ func TestCombine(t *testing.T) { { name: "mixed states with all success", states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusPending, CommitStatusWarning}, - expected: CommitStatusPending, + expected: CommitStatusFailure, }, { name: "all success with warning", states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusSuccess, CommitStatusWarning}, - expected: CommitStatusSuccess, + expected: CommitStatusFailure, }, } diff --git a/modules/generate/generate.go b/modules/generate/generate.go index 2d9a3dd902..ac84504492 100644 --- a/modules/generate/generate.go +++ b/modules/generate/generate.go @@ -54,13 +54,13 @@ func DecodeJwtSecretBase64(src string) ([]byte, error) { } // NewJwtSecretWithBase64 generates a jwt secret with its base64 encoded value intended to be used for saving into config file -func NewJwtSecretWithBase64() ([]byte, string, error) { +func NewJwtSecretWithBase64() ([]byte, string) { bytes := make([]byte, defaultJwtSecretLen) - _, err := io.ReadFull(rand.Reader, bytes) + _, err := rand.Read(bytes) if err != nil { - return nil, "", err + panic(err) // rand.Read never fails } - return bytes, base64.RawURLEncoding.EncodeToString(bytes), nil + return bytes, base64.RawURLEncoding.EncodeToString(bytes) } // NewSecretKey generate a new value intended to be used by SECRET_KEY. diff --git a/modules/generate/generate_test.go b/modules/generate/generate_test.go index af640a60c1..f9dd20cc7f 100644 --- a/modules/generate/generate_test.go +++ b/modules/generate/generate_test.go @@ -25,10 +25,12 @@ func TestDecodeJwtSecretBase64(t *testing.T) { } func TestNewJwtSecretWithBase64(t *testing.T) { - secret, encoded, err := NewJwtSecretWithBase64() - assert.NoError(t, err) + secret, encoded := NewJwtSecretWithBase64() assert.Len(t, secret, 32) decoded, err := DecodeJwtSecretBase64(encoded) assert.NoError(t, err) assert.Equal(t, secret, decoded) + + secret2, _ := NewJwtSecretWithBase64() + assert.NotEqual(t, secret, secret2) } diff --git a/modules/git/catfile_batch_reader.go b/modules/git/catfile_batch_reader.go index 8a0b342079..5727c4a8ac 100644 --- a/modules/git/catfile_batch_reader.go +++ b/modules/git/catfile_batch_reader.go @@ -13,55 +13,45 @@ import ( "strconv" "strings" "sync/atomic" - "time" "code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" ) -var catFileBatchDebugWaitClose atomic.Int64 - type catFileBatchCommunicator struct { - cancel context.CancelFunc + closeFunc atomic.Pointer[func(err error)] reqWriter io.Writer respReader *bufio.Reader debugGitCmd *gitcmd.Command } -func (b *catFileBatchCommunicator) Close() { - if b.cancel != nil { - b.cancel() - b.cancel = nil +func (b *catFileBatchCommunicator) Close(err ...error) { + if fn := b.closeFunc.Swap(nil); fn != nil { + (*fn)(util.OptionalArg(err)) } } -// newCatFileBatch opens git cat-file --batch in the provided repo and returns a stdin pipe, a stdout reader and cancel function -func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Command) (ret *catFileBatchCommunicator) { +// newCatFileBatch opens git cat-file --batch/--batch-check/--batch-command command and prepares the stdin/stdout pipes for communication. +func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Command) *catFileBatchCommunicator { ctx, ctxCancel := context.WithCancelCause(ctx) - - // We often want to feed the commits in order into cat-file --batch, followed by their trees and subtrees as necessary. stdinWriter, stdoutReader, stdPipeClose := cmdCatFile.MakeStdinStdoutPipe() - pipeClose := func() { - if delay := catFileBatchDebugWaitClose.Load(); delay > 0 { - time.Sleep(time.Duration(delay)) // for testing purpose only - } - stdPipeClose() - } - - ret = &catFileBatchCommunicator{ + ret := &catFileBatchCommunicator{ debugGitCmd: cmdCatFile, - cancel: func() { ctxCancel(nil) }, reqWriter: stdinWriter, respReader: bufio.NewReaderSize(stdoutReader, 32*1024), // use a buffered reader for rich operations } + ret.closeFunc.Store(new(func(err error) { + ctxCancel(err) + stdPipeClose() + })) err := cmdCatFile.WithDir(repoPath).StartWithStderr(ctx) if err != nil { log.Error("Unable to start git command %v: %v", cmdCatFile.LogString(), err) // ideally here it should return the error, but it would require refactoring all callers // so just return a dummy communicator that does nothing, almost the same behavior as before, not bad - ctxCancel(err) - pipeClose() + ret.Close(err) return ret } @@ -70,13 +60,33 @@ func newCatFileBatch(ctx context.Context, repoPath string, cmdCatFile *gitcmd.Co if err != nil && !errors.Is(err, context.Canceled) { log.Error("cat-file --batch command failed in repo %s, error: %v", repoPath, err) } - ctxCancel(err) - pipeClose() + ret.Close(err) }() return ret } +func (b *catFileBatchCommunicator) debugKill() (ret struct { + beforeClose chan struct{} + blockClose chan struct{} + afterClose chan struct{} +}, +) { + ret.beforeClose = make(chan struct{}) + ret.blockClose = make(chan struct{}) + ret.afterClose = make(chan struct{}) + oldCloseFunc := b.closeFunc.Load() + b.closeFunc.Store(new(func(err error) { + b.closeFunc.Store(nil) + close(ret.beforeClose) + <-ret.blockClose + (*oldCloseFunc)(err) + close(ret.afterClose) + })) + b.debugGitCmd.DebugKill() + return ret +} + // catFileBatchParseInfoLine reads the header line from cat-file --batch // We expect: SP SP LF // then leaving the rest of the stream " LF" to be read diff --git a/modules/git/catfile_batch_test.go b/modules/git/catfile_batch_test.go index 69662ffc1a..782d34d249 100644 --- a/modules/git/catfile_batch_test.go +++ b/modules/git/catfile_batch_test.go @@ -7,9 +7,7 @@ import ( "io" "os" "path/filepath" - "sync" "testing" - "time" "code.gitea.io/gitea/modules/test" @@ -39,13 +37,22 @@ func testCatFileBatch(t *testing.T) { require.Error(t, err) }) - simulateQueryTerminated := func(pipeCloseDelay, pipeReadDelay time.Duration) (errRead error) { - catFileBatchDebugWaitClose.Store(int64(pipeCloseDelay)) - defer catFileBatchDebugWaitClose.Store(0) + simulateQueryTerminated := func(t *testing.T, errBeforePipeClose, errAfterPipeClose error) { + readError := func(t *testing.T, r io.Reader, expectedErr error) { + if expectedErr == nil { + return // expectedErr == nil means this read should be skipped + } + n, err := r.Read(make([]byte, 100)) + assert.Zero(t, n) + assert.ErrorIs(t, err, expectedErr) + } + batch, err := NewBatch(t.Context(), filepath.Join(testReposDir, "repo1_bare")) require.NoError(t, err) defer batch.Close() - _, _ = batch.QueryInfo("e2129701f1a4d54dc44f03c93bca0a2aec7c5449") + _, err = batch.QueryInfo("e2129701f1a4d54dc44f03c93bca0a2aec7c5449") + require.NoError(t, err) + var c *catFileBatchCommunicator switch b := batch.(type) { case *catFileBatchLegacy: @@ -58,24 +65,18 @@ func testCatFileBatch(t *testing.T) { t.FailNow() } - wg := sync.WaitGroup{} - wg.Go(func() { - time.Sleep(pipeReadDelay) - var n int - n, errRead = c.respReader.Read(make([]byte, 100)) - assert.Zero(t, n) - }) - time.Sleep(10 * time.Millisecond) - c.debugGitCmd.DebugKill() - wg.Wait() - return errRead - } + require.NotEqual(t, errBeforePipeClose == nil, errAfterPipeClose == nil, "must set exactly one of the expected errors") + inceptor := c.debugKill() + <-inceptor.beforeClose // wait for the command's Close to be called, the pipe is not closed yet + readError(t, c.respReader, errBeforePipeClose) // then caller will read on an open pipe which will be closed soon + close(inceptor.blockClose) // continue to close the pipe + <-inceptor.afterClose // wait for the pipe to be closed + readError(t, c.respReader, errAfterPipeClose) // then caller will read on a closed pipe + } t.Run("QueryTerminated", func(t *testing.T) { - err := simulateQueryTerminated(0, 20*time.Millisecond) - assert.ErrorIs(t, err, os.ErrClosed) // pipes are closed faster - err = simulateQueryTerminated(40*time.Millisecond, 20*time.Millisecond) - assert.ErrorIs(t, err, io.EOF) // reader is faster + simulateQueryTerminated(t, io.EOF, nil) // reader is faster + simulateQueryTerminated(t, nil, os.ErrClosed) // pipes are closed faster }) batch, err := NewBatch(t.Context(), filepath.Join(testReposDir, "repo1_bare")) diff --git a/modules/git/commit.go b/modules/git/commit.go index b98d36d946..c3d23d6878 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -86,50 +86,6 @@ func (c *Commit) GetCommitByPath(relpath string) (*Commit, error) { return c.repo.getCommitByPathWithID(c.ID, relpath) } -// AddChanges marks local changes to be ready for commit. -func AddChanges(ctx context.Context, repoPath string, all bool, files ...string) error { - cmd := gitcmd.NewCommand().AddArguments("add") - if all { - cmd.AddArguments("--all") - } - cmd.AddDashesAndList(files...) - _, _, err := cmd.WithDir(repoPath).RunStdString(ctx) - return err -} - -// CommitChangesOptions the options when a commit created -type CommitChangesOptions struct { - Committer *Signature - Author *Signature - Message string -} - -// CommitChanges commits local changes with given committer, author and message. -// If author is nil, it will be the same as committer. -func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptions) error { - cmd := gitcmd.NewCommand() - if opts.Committer != nil { - cmd.AddOptionValues("-c", "user.name="+opts.Committer.Name) - cmd.AddOptionValues("-c", "user.email="+opts.Committer.Email) - } - cmd.AddArguments("commit") - - if opts.Author == nil { - opts.Author = opts.Committer - } - if opts.Author != nil { - cmd.AddOptionFormat("--author='%s <%s>'", opts.Author.Name, opts.Author.Email) - } - cmd.AddOptionFormat("--message=%s", opts.Message) - - _, _, err := cmd.WithDir(repoPath).RunStdString(ctx) - // No stderr but exit status 1 means nothing to commit. - if gitcmd.IsErrorExitCode(err, 1) { - return nil - } - return err -} - // CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize func (c *Commit) CommitsByRange(page, pageSize int, not, since, until string) ([]*Commit, error) { return c.repo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until) @@ -291,27 +247,6 @@ func (c *Commit) GetFileContent(filename string, limit int) (string, error) { return string(bytes), nil } -// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only') -func (c *Commit) GetBranchName() (string, error) { - cmd := gitcmd.NewCommand("name-rev") - if DefaultFeatures().CheckVersionAtLeast("2.13.0") { - cmd.AddArguments("--exclude", "refs/tags/*") - } - cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String()) - data, _, err := cmd.WithDir(c.repo.Path).RunStdString(c.repo.Ctx) - if err != nil { - // handle special case where git can not describe commit - if strings.Contains(err.Error(), "cannot describe") { - return "", nil - } - - return "", err - } - - // name-rev commitID output will be "master" or "master~12" - return strings.SplitN(strings.TrimSpace(data), "~", 2)[0], nil -} - // GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository. func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) { commitID, _, err := gitcmd.NewCommand("rev-parse"). diff --git a/modules/git/foreachref/format.go b/modules/git/foreachref/format.go index d2f9998fe8..87c1c9a4ff 100644 --- a/modules/git/foreachref/format.go +++ b/modules/git/foreachref/format.go @@ -53,7 +53,7 @@ func (f Format) Flag() string { var formatFlag strings.Builder for i, field := range f.fieldNames { // field key and field value - formatFlag.WriteString(fmt.Sprintf("%s %%(%s)", field, field)) + fmt.Fprintf(&formatFlag, "%s %%(%s)", field, field) if i < len(f.fieldNames)-1 { // note: escape delimiters to allow control characters as @@ -72,7 +72,7 @@ func (f Format) Parser(r io.Reader) *Parser { return NewParser(r, f) } -// hexEscaped produces hex-escpaed characters from a string. For example, "\n\0" +// hexEscaped produces hex-escaped characters from a string. For example, "\n\0" // would turn into "%0a%00". func (f Format) hexEscaped(delim []byte) string { var escaped strings.Builder diff --git a/modules/git/gitcmd/command_test.go b/modules/git/gitcmd/command_test.go index 662356bc3f..6e4214d995 100644 --- a/modules/git/gitcmd/command_test.go +++ b/modules/git/gitcmd/command_test.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "os" + "strings" "testing" "time" @@ -49,9 +50,11 @@ func TestRunWithContextStd(t *testing.T) { stdout, stderr, err := cmd.RunStdString(t.Context()) if assert.Error(t, err) { assert.Equal(t, stderr, err.Stderr()) - assert.Equal(t, "fatal: Not a valid object name no-such\n", err.Stderr()) + stderrLower := strings.ToLower(stderr) // see: IsStdErrorNotValidObjectName + assert.Equal(t, "fatal: not a valid object name no-such\n", stderrLower) // FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message - assert.Equal(t, "exit status 128 - fatal: Not a valid object name no-such", err.Error()) + errLower := strings.ToLower(err.Error()) + assert.Equal(t, "exit status 128 - fatal: not a valid object name no-such", errLower) assert.Empty(t, stdout) } } @@ -61,9 +64,11 @@ func TestRunWithContextStd(t *testing.T) { stdout, stderr, err := cmd.RunStdBytes(t.Context()) if assert.Error(t, err) { assert.Equal(t, string(stderr), err.Stderr()) - assert.Equal(t, "fatal: Not a valid object name no-such\n", err.Stderr()) + stderrLower := strings.ToLower(err.Stderr()) // see: IsStdErrorNotValidObjectName + assert.Equal(t, "fatal: not a valid object name no-such\n", stderrLower) // FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message - assert.Equal(t, "exit status 128 - fatal: Not a valid object name no-such", err.Error()) + errLower := strings.ToLower(err.Error()) + assert.Equal(t, "exit status 128 - fatal: not a valid object name no-such", errLower) assert.Empty(t, stdout) } } diff --git a/modules/git/gitcmd/error.go b/modules/git/gitcmd/error.go index 066b37f10d..b674068c40 100644 --- a/modules/git/gitcmd/error.go +++ b/modules/git/gitcmd/error.go @@ -77,6 +77,13 @@ func IsErrorCanceledOrKilled(err error) bool { return errors.Is(err, context.Canceled) || IsErrorSignalKilled(err) } +func IsStdErrorNotValidObjectName(err error) bool { + stderr, ok := ErrorAsStderr(err) + // Git is lowercasing the "fatal: Not a valid object name" error message + // ref: https://lore.kernel.org/git/pull.2052.git.1771836302101.gitgitgadget@gmail.com + return ok && strings.Contains(strings.ToLower(stderr), "fatal: not a valid object name") +} + type pipelineError struct { error } diff --git a/modules/git/grep.go b/modules/git/grep.go index 051a7a1d40..47f66b88b2 100644 --- a/modules/git/grep.go +++ b/modules/git/grep.go @@ -11,6 +11,7 @@ import ( "slices" "strconv" "strings" + "time" "code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/util" @@ -39,6 +40,10 @@ type GrepOptions struct { PathspecList []string } +// grepSearchTimeout is the timeout for git grep search, it should be long enough to get results +// but not too long to cause performance issues +const grepSearchTimeout = 30 * time.Second + func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) { /* The output is like this ( "^@" means \x00): @@ -76,6 +81,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe() defer stdoutReaderClose() err := cmd.WithDir(repo.Path). + WithTimeout(grepSearchTimeout). WithPipelineFunc(func(ctx gitcmd.Context) error { isInBlock := false rd := bufio.NewReaderSize(stdoutReader, util.IfZero(opts.MaxLineLength, 16*1024)) diff --git a/modules/git/object_id_test.go b/modules/git/object_id_test.go index 03d0c85d87..213a0cd341 100644 --- a/modules/git/object_id_test.go +++ b/modules/git/object_id_test.go @@ -22,4 +22,6 @@ func TestIsValidSHAPattern(t *testing.T) { assert.Equal(t, "2e65efe2a145dda7ee51d1741299f848e5bf752e", ComputeBlobHash(Sha1ObjectFormat, []byte("a")).String()) assert.Equal(t, "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813", ComputeBlobHash(Sha256ObjectFormat, nil).String()) assert.Equal(t, "eb337bcee2061c5313c9a1392116b6c76039e9e30d71467ae359b36277e17dc7", ComputeBlobHash(Sha256ObjectFormat, []byte("a")).String()) + assert.True(t, IsEmptyCommitID("")) + assert.True(t, IsEmptyCommitID("0000000000000000000000000000000000000000")) } diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index c10f73690c..8ba3f83386 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -362,39 +362,6 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout)) } -// CommitsBetweenNotBase returns a list that contains commits between [before, last), excluding commits in baseBranch. -// If before is detached (removed by reset + push) it is not included. -func (repo *Repository) CommitsBetweenNotBase(last, before *Commit, baseBranch string) ([]*Commit, error) { - var stdout []byte - var err error - if before == nil { - stdout, _, err = gitcmd.NewCommand("rev-list"). - AddDynamicArguments(last.ID.String()). - AddOptionValues("--not", baseBranch). - WithDir(repo.Path). - RunStdBytes(repo.Ctx) - } else { - stdout, _, err = gitcmd.NewCommand("rev-list"). - AddDynamicArguments(before.ID.String()+".."+last.ID.String()). - AddOptionValues("--not", baseBranch). - WithDir(repo.Path). - RunStdBytes(repo.Ctx) - if err != nil && strings.Contains(err.Error(), "no merge base") { - // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. - // previously it would return the results of git rev-list before last so let's try that... - stdout, _, err = gitcmd.NewCommand("rev-list"). - AddDynamicArguments(before.ID.String(), last.ID.String()). - AddOptionValues("--not", baseBranch). - WithDir(repo.Path). - RunStdBytes(repo.Ctx) - } - } - if err != nil { - return nil, err - } - return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout)) -} - // CommitsBetweenIDs return commits between twoe commits func (repo *Repository) CommitsBetweenIDs(last, before string) ([]*Commit, error) { lastCommit, err := repo.GetCommit(last) diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go index d50c1ad629..5d951dad0d 100644 --- a/modules/git/tree_nogogit.go +++ b/modules/git/tree_nogogit.go @@ -65,7 +65,7 @@ func (t *Tree) ListEntries() (Entries, error) { stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).WithDir(t.repo.Path).RunStdBytes(t.repo.Ctx) if runErr != nil { - if strings.Contains(runErr.Error(), "fatal: Not a valid object name") || strings.Contains(runErr.Error(), "fatal: not a tree object") { + if gitcmd.IsStdErrorNotValidObjectName(runErr) || strings.Contains(runErr.Error(), "fatal: not a tree object") { return nil, ErrNotExist{ ID: t.ID.String(), } diff --git a/modules/gitrepo/compare.go b/modules/gitrepo/compare.go index 06cf880d99..7e38d33e6f 100644 --- a/modules/gitrepo/compare.go +++ b/modules/gitrepo/compare.go @@ -42,3 +42,30 @@ func GetDivergingCommits(ctx context.Context, repo Repository, baseBranch, targe } return &DivergeObject{Ahead: ahead, Behind: behind}, nil } + +// GetCommitIDsBetweenReverse returns the last commit IDs between two commits in reverse order (from old to new) with limit. +// If the result exceeds the limit, the old commits IDs will be ignored +func GetCommitIDsBetweenReverse(ctx context.Context, repo Repository, startRef, endRef, notRef string, limit int) ([]string, error) { + genCmd := func(reversions ...string) *gitcmd.Command { + cmd := gitcmd.NewCommand("rev-list", "--reverse"). + AddArguments("-n").AddDynamicArguments(strconv.Itoa(limit)). + AddDynamicArguments(reversions...) + if notRef != "" { // --not should be kept as the last parameter of git command, otherwise the result will be wrong + cmd.AddOptionValues("--not", notRef) + } + return cmd + } + stdout, _, err := RunCmdString(ctx, repo, genCmd(startRef+".."+endRef)) + // example git error message: fatal: origin/main..HEAD: no merge base + if err != nil && strings.Contains(err.Stderr(), "no merge base") { + // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. + // previously it would return the results of git rev-list before last so let's try that... + stdout, _, err = RunCmdString(ctx, repo, genCmd(startRef, endRef)) + } + if err != nil { + return nil, err + } + + commitIDs := strings.Fields(strings.TrimSpace(stdout)) + return commitIDs, nil +} diff --git a/modules/gitrepo/compare_test.go b/modules/gitrepo/compare_test.go index f8661d9412..2d2af0934d 100644 --- a/modules/gitrepo/compare_test.go +++ b/modules/gitrepo/compare_test.go @@ -40,3 +40,75 @@ func TestRepoGetDivergingCommits(t *testing.T) { Behind: 2, }, do) } + +func TestGetCommitIDsBetweenReverse(t *testing.T) { + repo := &mockRepository{path: "repo1_bare"} + + // tests raw commit IDs + commitIDs, err := GetCommitIDsBetweenReverse(t.Context(), repo, + "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", + "ce064814f4a0d337b333e646ece456cd39fab612", + "", + 100, + ) + assert.NoError(t, err) + assert.Equal(t, []string{ + "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", + "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1", + "37991dec2c8e592043f47155ce4808d4580f9123", + "feaf4ba6bc635fec442f46ddd4512416ec43c2c2", + "ce064814f4a0d337b333e646ece456cd39fab612", + }, commitIDs) + + commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo, + "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", + "ce064814f4a0d337b333e646ece456cd39fab612", + "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1", + 100, + ) + assert.NoError(t, err) + assert.Equal(t, []string{ + "37991dec2c8e592043f47155ce4808d4580f9123", + "feaf4ba6bc635fec442f46ddd4512416ec43c2c2", + "ce064814f4a0d337b333e646ece456cd39fab612", + }, commitIDs) + + commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo, + "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", + "ce064814f4a0d337b333e646ece456cd39fab612", + "", + 3, + ) + assert.NoError(t, err) + assert.Equal(t, []string{ + "37991dec2c8e592043f47155ce4808d4580f9123", + "feaf4ba6bc635fec442f46ddd4512416ec43c2c2", + "ce064814f4a0d337b333e646ece456cd39fab612", + }, commitIDs) + + // test branch names instead of raw commit IDs. + commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo, + "test", + "master", + "", + 100, + ) + assert.NoError(t, err) + assert.Equal(t, []string{ + "feaf4ba6bc635fec442f46ddd4512416ec43c2c2", + "ce064814f4a0d337b333e646ece456cd39fab612", + }, commitIDs) + + // add notref to exclude test + commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo, + "test", + "master", + "test", + 100, + ) + assert.NoError(t, err) + assert.Equal(t, []string{ + "feaf4ba6bc635fec442f46ddd4512416ec43c2c2", + "ce064814f4a0d337b333e646ece456cd39fab612", + }, commitIDs) +} diff --git a/modules/graceful/server_http.go b/modules/graceful/server_http.go index 7c855ac64e..77a2c3b6f8 100644 --- a/modules/graceful/server_http.go +++ b/modules/graceful/server_http.go @@ -12,7 +12,12 @@ import ( func newHTTPServer(network, address, name string, handler http.Handler) (*Server, ServeFunction) { server := NewServer(network, address, name) + protocols := http.Protocols{} + protocols.SetHTTP1(true) + protocols.SetHTTP2(true) // HTTP/2 can only be used when Gitea is configured to use TLS + protocols.SetUnencryptedHTTP2(true) // Allow HTTP/2 without TLS, in case Gitea is behind a reverse proxy httpServer := http.Server{ + Protocols: &protocols, Handler: handler, BaseContext: func(net.Listener) context.Context { return GetManager().HammerContext() }, } diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go index c7416c7a10..dca28588e4 100644 --- a/modules/highlight/highlight.go +++ b/modules/highlight/highlight.go @@ -5,13 +5,9 @@ package highlight import ( - "bufio" "bytes" - "fmt" gohtml "html" "html/template" - "io" - "strings" "sync" "code.gitea.io/gitea/modules/log" @@ -19,11 +15,11 @@ import ( "code.gitea.io/gitea/modules/util" "github.com/alecthomas/chroma/v2" - "github.com/alecthomas/chroma/v2/formatters/html" + chromahtml "github.com/alecthomas/chroma/v2/formatters/html" "github.com/alecthomas/chroma/v2/styles" ) -// don't index files larger than this many bytes for performance purposes +// don't highlight files larger than this many bytes for performance purposes const sizeLimit = 1024 * 1024 type globalVarsType struct { @@ -80,6 +76,10 @@ func UnsafeSplitHighlightedLines(code template.HTML) (ret [][]byte) { } } +func htmlEscape(code string) template.HTML { + return template.HTML(gohtml.EscapeString(code)) +} + // RenderCodeSlowGuess tries to get a lexer by file name and language first, // if not found, it will try to guess the lexer by code content, which is slow (more than several hundreds of milliseconds). func RenderCodeSlowGuess(fileName, language, code string) (output template.HTML, lexer chroma.Lexer, lexerDisplayName string) { @@ -90,7 +90,7 @@ func RenderCodeSlowGuess(fileName, language, code string) (output template.HTML, } if len(code) > sizeLimit { - return template.HTML(template.HTMLEscapeString(code)), nil, "" + return htmlEscape(code), nil, "" } lexer = detectChromaLexerWithAnalyze(fileName, language, util.UnsafeStringToBytes(code)) // it is also slow @@ -99,91 +99,65 @@ func RenderCodeSlowGuess(fileName, language, code string) (output template.HTML, // RenderCodeByLexer returns a HTML version of code string with chroma syntax highlighting classes func RenderCodeByLexer(lexer chroma.Lexer, code string) template.HTML { - formatter := html.New(html.WithClasses(true), - html.WithLineNumbers(false), - html.PreventSurroundingPre(true), + formatter := chromahtml.New(chromahtml.WithClasses(true), + chromahtml.WithLineNumbers(false), + chromahtml.PreventSurroundingPre(true), ) - htmlbuf := bytes.Buffer{} - htmlw := bufio.NewWriter(&htmlbuf) - iterator, err := lexer.Tokenise(nil, code) if err != nil { log.Error("Can't tokenize code: %v", err) - return template.HTML(template.HTMLEscapeString(code)) - } - // style not used for live site but need to pass something - err = formatter.Format(htmlw, globalVars().githubStyles, iterator) - if err != nil { - log.Error("Can't format code: %v", err) - return template.HTML(template.HTMLEscapeString(code)) + return htmlEscape(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 template.HTML(strings.TrimSuffix(htmlbuf.String(), "\n")) + htmlBuf := &bytes.Buffer{} + // style not used for live site but need to pass something + err = formatter.Format(htmlBuf, globalVars().githubStyles, iterator) + if err != nil { + log.Error("Can't format code: %v", err) + return htmlEscape(code) + } + return template.HTML(util.UnsafeBytesToString(htmlBuf.Bytes())) } // RenderFullFile returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name -func RenderFullFile(fileName, language string, code []byte) ([]template.HTML, string, error) { - if len(code) > sizeLimit { - return RenderPlainText(code), "", nil +func RenderFullFile(fileName, language string, code []byte) ([]template.HTML, string) { + if language == LanguagePlaintext || len(code) > sizeLimit { + return renderPlainText(code), formatLexerName(LanguagePlaintext) } - - formatter := html.New(html.WithClasses(true), - html.WithLineNumbers(false), - html.PreventSurroundingPre(true), - ) - lexer := detectChromaLexerWithAnalyze(fileName, language, code) lexerName := formatLexerName(lexer.Config().Name) - - iterator, err := lexer.Tokenise(nil, string(code)) - if err != nil { - return nil, "", fmt.Errorf("can't tokenize code: %w", err) + rendered := RenderCodeByLexer(lexer, util.UnsafeBytesToString(code)) + unsafeLines := UnsafeSplitHighlightedLines(rendered) + lines := make([]template.HTML, len(unsafeLines)) + for idx, lineBytes := range unsafeLines { + lines[idx] = template.HTML(util.UnsafeBytesToString(lineBytes)) } - - tokensLines := chroma.SplitTokensIntoLines(iterator.Tokens()) - htmlBuf := &bytes.Buffer{} - - lines := make([]template.HTML, 0, len(tokensLines)) - for _, tokens := range tokensLines { - iterator = chroma.Literator(tokens...) - err = formatter.Format(htmlBuf, globalVars().githubStyles, iterator) - if err != nil { - return nil, "", fmt.Errorf("can't format code: %w", err) - } - lines = append(lines, template.HTML(htmlBuf.String())) - htmlBuf.Reset() - } - - return lines, lexerName, nil + return lines, lexerName } -// RenderPlainText returns non-highlighted HTML for code -func RenderPlainText(code []byte) []template.HTML { - r := bufio.NewReader(bytes.NewReader(code)) - m := make([]template.HTML, 0, bytes.Count(code, []byte{'\n'})+1) - for { - content, err := r.ReadString('\n') - if err != nil && err != io.EOF { - log.Error("failed to read string from buffer: %v", err) - break +// renderPlainText returns non-highlighted HTML for code +func renderPlainText(code []byte) []template.HTML { + lines := make([]template.HTML, 0, bytes.Count(code, []byte{'\n'})+1) + pos := 0 + for pos < len(code) { + var content []byte + nextPos := bytes.IndexByte(code[pos:], '\n') + if nextPos == -1 { + content = code[pos:] + pos = len(code) + } else { + content = code[pos : pos+nextPos+1] + pos += nextPos + 1 } - if content == "" && err == io.EOF { - break - } - s := template.HTML(gohtml.EscapeString(content)) - m = append(m, s) + lines = append(lines, htmlEscape(util.UnsafeBytesToString(content))) } - return m + return lines } func formatLexerName(name string) string { - if name == "fallback" { + if name == LanguagePlaintext || name == chromaLexerFallback { return "Plaintext" } - return util.ToTitleCaseNoLower(name) } diff --git a/modules/highlight/highlight_test.go b/modules/highlight/highlight_test.go index d026210475..211132b255 100644 --- a/modules/highlight/highlight_test.go +++ b/modules/highlight/highlight_test.go @@ -118,8 +118,7 @@ c=2 for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - out, lexerName, err := RenderFullFile(tt.name, "", []byte(tt.code)) - assert.NoError(t, err) + out, lexerName := RenderFullFile(tt.name, "", []byte(tt.code)) assert.Equal(t, tt.want, out) assert.Equal(t, tt.lexerName, lexerName) }) @@ -182,7 +181,7 @@ c=2`), for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - out := RenderPlainText([]byte(tt.code)) + out := renderPlainText([]byte(tt.code)) assert.Equal(t, tt.want, out) }) } diff --git a/modules/highlight/lexerdetect.go b/modules/highlight/lexerdetect.go index 5d98578f35..fe430f463f 100644 --- a/modules/highlight/lexerdetect.go +++ b/modules/highlight/lexerdetect.go @@ -16,7 +16,11 @@ import ( "github.com/go-enry/go-enry/v2" ) -const mapKeyLowerPrefix = "lower/" +const ( + mapKeyLowerPrefix = "lower/" + LanguagePlaintext = "plaintext" + chromaLexerFallback = "fallback" +) // chromaLexers is fully managed by us to do fast lookup for chroma lexers by file name or language name // Don't use lexers.Get because it is very slow in many cases (iterate all rules, filepath glob match, etc.) diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index dd3efab7a5..e23cf0a92a 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -60,21 +60,6 @@ func CacheControlForPrivateStatic() *CacheControlOptions { } } -// HandleGenericETagCache handles ETag-based caching for a HTTP request. -// It returns true if the request was handled. -func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag string) (handled bool) { - if len(etag) > 0 { - w.Header().Set("Etag", etag) - if checkIfNoneMatchIsValid(req, etag) { - w.WriteHeader(http.StatusNotModified) - return true - } - } - // not sure whether it is a public content, so just use "private" (old behavior) - SetCacheControlInHeader(w.Header(), CacheControlForPrivateStatic()) - return false -} - // checkIfNoneMatchIsValid tests if the header If-None-Match matches the ETag func checkIfNoneMatchIsValid(req *http.Request, etag string) bool { ifNoneMatch := req.Header.Get("If-None-Match") @@ -89,10 +74,18 @@ func checkIfNoneMatchIsValid(req *http.Request, etag string) bool { return false } -// HandleGenericETagTimeCache handles ETag-based caching with Last-Modified caching for a HTTP request. +func HandleGenericETagPublicCache(req *http.Request, w http.ResponseWriter, etag string, lastModified *time.Time) bool { + return handleGenericETagTimeCache(req, w, etag, lastModified, CacheControlForPublicStatic()) +} + +func HandleGenericETagPrivateCache(req *http.Request, w http.ResponseWriter, etag string, lastModified *time.Time) bool { + return handleGenericETagTimeCache(req, w, etag, lastModified, CacheControlForPrivateStatic()) +} + +// handleGenericETagTimeCache handles ETag-based caching with Last-Modified caching for the HTTP request. // It returns true if the request was handled. -func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag string, lastModified *time.Time) (handled bool) { - if len(etag) > 0 { +func handleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag string, lastModified *time.Time, cacheControlOpts *CacheControlOptions) (handled bool) { + if etag != "" { w.Header().Set("Etag", etag) } if lastModified != nil && !lastModified.IsZero() { @@ -100,7 +93,7 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s w.Header().Set("Last-Modified", lastModified.UTC().Format(http.TimeFormat)) } - if len(etag) > 0 { + if etag != "" { if checkIfNoneMatchIsValid(req, etag) { w.WriteHeader(http.StatusNotModified) return true @@ -117,7 +110,6 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s } } - // not sure whether it is a public content, so just use "private" (old behavior) - SetCacheControlInHeader(w.Header(), CacheControlForPrivateStatic()) + SetCacheControlInHeader(w.Header(), cacheControlOpts) return false } diff --git a/modules/httpcache/httpcache_test.go b/modules/httpcache/httpcache_test.go index d81f06097c..d510cbe4d7 100644 --- a/modules/httpcache/httpcache_test.go +++ b/modules/httpcache/httpcache_test.go @@ -6,92 +6,73 @@ package httpcache import ( "net/http" "net/http/httptest" - "strings" "testing" + "time" + + "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) -func countFormalHeaders(h http.Header) (c int) { - for k := range h { - // ignore our headers for internal usage - if strings.HasPrefix(k, "X-Gitea-") { - continue - } - c++ - } - return c -} - func TestHandleGenericETagCache(t *testing.T) { - etag := `"test"` + matchedEtag := `"matched-etag"` + lastModifiedTime := new(time.Date(2021, time.January, 2, 15, 4, 5, 0, time.FixedZone("test-zone", 8*3600))) + lastModified := lastModifiedTime.UTC().Format(http.TimeFormat) + cacheControl := "max-age=0, private, must-revalidate, no-transform" + type testCase struct { + name string + reqHeaders map[string]string + wantHandled bool + wantHeaders map[string]string + wantStatus int + } + cases := []testCase{ + { + name: "No If-None-Match", + wantHandled: false, + wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag}, + }, + { + name: "Mismatched If-None-Match", + reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag"`}, + wantHandled: false, + wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag}, + }, + { + name: "Matched If-None-Match", + reqHeaders: map[string]string{"If-None-Match": matchedEtag}, + wantHandled: true, + wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": matchedEtag}, + wantStatus: http.StatusNotModified, + }, + { + name: "Multiple Mismatched If-None-Match", + reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag1", "mismatched-etag2"`}, + wantHandled: false, + wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag}, + }, + { + name: "Multiple Matched If-None-Match", + reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag", ` + matchedEtag}, + wantHandled: true, + wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": matchedEtag}, + wantStatus: http.StatusNotModified, + }, + } - t.Run("No_If-None-Match", func(t *testing.T) { - req := &http.Request{Header: make(http.Header)} - w := httptest.NewRecorder() - - handled := HandleGenericETagCache(req, w, etag) - - assert.False(t, handled) - assert.Equal(t, 2, countFormalHeaders(w.Header())) - assert.Contains(t, w.Header(), "Cache-Control") - assert.Contains(t, w.Header(), "Etag") - assert.Equal(t, etag, w.Header().Get("Etag")) - }) - t.Run("Wrong_If-None-Match", func(t *testing.T) { - req := &http.Request{Header: make(http.Header)} - w := httptest.NewRecorder() - - req.Header.Set("If-None-Match", `"wrong etag"`) - - handled := HandleGenericETagCache(req, w, etag) - - assert.False(t, handled) - assert.Equal(t, 2, countFormalHeaders(w.Header())) - assert.Contains(t, w.Header(), "Cache-Control") - assert.Contains(t, w.Header(), "Etag") - assert.Equal(t, etag, w.Header().Get("Etag")) - }) - t.Run("Correct_If-None-Match", func(t *testing.T) { - req := &http.Request{Header: make(http.Header)} - w := httptest.NewRecorder() - - req.Header.Set("If-None-Match", etag) - - handled := HandleGenericETagCache(req, w, etag) - - assert.True(t, handled) - assert.Equal(t, 1, countFormalHeaders(w.Header())) - assert.Contains(t, w.Header(), "Etag") - assert.Equal(t, etag, w.Header().Get("Etag")) - assert.Equal(t, http.StatusNotModified, w.Code) - }) - t.Run("Multiple_Wrong_If-None-Match", func(t *testing.T) { - req := &http.Request{Header: make(http.Header)} - w := httptest.NewRecorder() - - req.Header.Set("If-None-Match", `"wrong etag", "wrong etag "`) - - handled := HandleGenericETagCache(req, w, etag) - - assert.False(t, handled) - assert.Equal(t, 2, countFormalHeaders(w.Header())) - assert.Contains(t, w.Header(), "Cache-Control") - assert.Contains(t, w.Header(), "Etag") - assert.Equal(t, etag, w.Header().Get("Etag")) - }) - t.Run("Multiple_Correct_If-None-Match", func(t *testing.T) { - req := &http.Request{Header: make(http.Header)} - w := httptest.NewRecorder() - - req.Header.Set("If-None-Match", `"wrong etag", `+etag) - - handled := HandleGenericETagCache(req, w, etag) - - assert.True(t, handled) - assert.Equal(t, 1, countFormalHeaders(w.Header())) - assert.Contains(t, w.Header(), "Etag") - assert.Equal(t, etag, w.Header().Get("Etag")) - assert.Equal(t, http.StatusNotModified, w.Code) - }) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://example.com/test", nil) + for k, v := range tc.reqHeaders { + req.Header.Set(k, v) + } + w := httptest.NewRecorder() + assert.Equal(t, tc.wantHandled, HandleGenericETagPrivateCache(req, w, matchedEtag, lastModifiedTime)) + resp := w.Result() + for k, v := range tc.wantHeaders { + assert.Equal(t, v, resp.Header.Get(k)) + } + assert.Equal(t, tc.wantStatus, util.Iif(resp.StatusCode == http.StatusOK, 0, resp.StatusCode)) + }) + } } diff --git a/modules/httplib/content_disposition.go b/modules/httplib/content_disposition.go new file mode 100644 index 0000000000..da23dae221 --- /dev/null +++ b/modules/httplib/content_disposition.go @@ -0,0 +1,65 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package httplib + +import ( + "mime" + "strings" + + "code.gitea.io/gitea/modules/setting" +) + +type ContentDispositionType string + +const ( + ContentDispositionInline ContentDispositionType = "inline" + ContentDispositionAttachment ContentDispositionType = "attachment" +) + +func needsEncodingRune(b rune) bool { + return (b < ' ' || b > '~') && b != '\t' +} + +// getSafeName replaces all invalid chars in the filename field by underscore +func getSafeName(s string) (_ string, needsEncoding bool) { + var out strings.Builder + for _, b := range s { + if needsEncodingRune(b) { + needsEncoding = true + out.WriteRune('_') + } else { + out.WriteRune(b) + } + } + return out.String(), needsEncoding +} + +func EncodeContentDispositionAttachment(filename string) string { + return encodeContentDisposition(ContentDispositionAttachment, filename) +} + +func EncodeContentDispositionInline(filename string) string { + return encodeContentDisposition(ContentDispositionInline, filename) +} + +// encodeContentDisposition encodes a correct Content-Disposition Header +func encodeContentDisposition(t ContentDispositionType, filename string) string { + safeFilename, needsEncoding := getSafeName(filename) + result := mime.FormatMediaType(string(t), map[string]string{"filename": safeFilename}) + // No need for the utf8 encoding + if !needsEncoding { + return result + } + utf8Result := mime.FormatMediaType(string(t), map[string]string{"filename": filename}) + + // The mime package might have unexpected results in other go versions + // Make tests instance fail, otherwise use the default behavior of the go mime package + if !strings.HasPrefix(result, string(t)+"; filename=") || !strings.HasPrefix(utf8Result, string(t)+"; filename*=") { + setting.PanicInDevOrTesting("Unexpected mime package result %s", result) + return utf8Result + } + + encodedFileName := strings.TrimPrefix(utf8Result, string(t)) + return result + encodedFileName +} diff --git a/modules/httplib/content_disposition_test.go b/modules/httplib/content_disposition_test.go new file mode 100644 index 0000000000..bf5040e107 --- /dev/null +++ b/modules/httplib/content_disposition_test.go @@ -0,0 +1,64 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package httplib + +import ( + "mime" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestContentDisposition(t *testing.T) { + type testEntry struct { + disposition ContentDispositionType + filename string + header string + } + table := []testEntry{ + {disposition: ContentDispositionInline, filename: "test.txt", header: "inline; filename=test.txt"}, + {disposition: ContentDispositionInline, filename: "test❌.txt", header: "inline; filename=test_.txt; filename*=utf-8''test%E2%9D%8C.txt"}, + {disposition: ContentDispositionInline, filename: "test ❌.txt", header: "inline; filename=\"test _.txt\"; filename*=utf-8''test%20%E2%9D%8C.txt"}, + {disposition: ContentDispositionInline, filename: "\"test.txt", header: "inline; filename=\"\\\"test.txt\""}, + {disposition: ContentDispositionInline, filename: "hello\tworld.txt", header: "inline; filename=\"hello\tworld.txt\""}, + {disposition: ContentDispositionAttachment, filename: "hello\tworld.txt", header: "attachment; filename=\"hello\tworld.txt\""}, + {disposition: ContentDispositionAttachment, filename: "hello\nworld.txt", header: "attachment; filename=hello_world.txt; filename*=utf-8''hello%0Aworld.txt"}, + {disposition: ContentDispositionAttachment, filename: "hello\rworld.txt", header: "attachment; filename=hello_world.txt; filename*=utf-8''hello%0Dworld.txt"}, + } + + // Check the needsEncodingRune replacer ranges except tab that is checked above + // Any change in behavior should fail here + for c := ' '; !needsEncodingRune(c); c++ { + var header string + switch { + case strings.ContainsAny(string(c), ` (),/:;<=>?@[]`): + header = "inline; filename=\"hello" + string(c) + "world.txt\"" + case strings.ContainsAny(string(c), `"\`): + // This document advises against for backslash in quoted form: + // https://datatracker.ietf.org/doc/html/rfc6266#appendix-D + // However the mime package is not generating the filename* in this scenario + header = "inline; filename=\"hello\\" + string(c) + "world.txt\"" + default: + header = "inline; filename=hello" + string(c) + "world.txt" + } + table = append(table, testEntry{ + disposition: ContentDispositionInline, + filename: "hello" + string(c) + "world.txt", + header: header, + }) + } + + for _, entry := range table { + t.Run(string(entry.disposition)+"_"+entry.filename, func(t *testing.T) { + encoded := encodeContentDisposition(entry.disposition, entry.filename) + assert.Equal(t, entry.header, encoded) + disposition, params, err := mime.ParseMediaType(encoded) + require.NoError(t, err) + assert.Equal(t, string(entry.disposition), disposition) + assert.Equal(t, entry.filename, params["filename"]) + }) + } +} diff --git a/modules/httplib/serve.go b/modules/httplib/serve.go index 2d66a86a8b..8abf6f1887 100644 --- a/modules/httplib/serve.go +++ b/modules/httplib/serve.go @@ -8,10 +8,9 @@ import ( "errors" "fmt" "io" + "io/fs" "net/http" - "net/url" "path" - "path/filepath" "strconv" "strings" "time" @@ -27,18 +26,19 @@ import ( ) type ServeHeaderOptions struct { - ContentType string // defaults to "application/octet-stream" - ContentTypeCharset string - ContentLength *int64 - Disposition string // defaults to "attachment" + ContentType string // defaults to "application/octet-stream" + ContentLength *int64 + Filename string - CacheIsPublic bool - CacheDuration time.Duration // defaults to 5 minutes - LastModified time.Time + ContentDisposition ContentDispositionType + + CacheIsPublic bool + CacheDuration time.Duration // defaults to 5 minutes + LastModified time.Time } // ServeSetHeaders sets necessary content serve headers -func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) { +func ServeSetHeaders(w http.ResponseWriter, opts ServeHeaderOptions) { header := w.Header() skipCompressionExts := container.SetOf(".gz", ".bz2", ".zip", ".xz", ".zst", ".deb", ".apk", ".jar", ".png", ".jpg", ".webp") @@ -46,14 +46,7 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) { w.Header().Add(gzhttp.HeaderNoCompression, "1") } - contentType := typesniffer.MimeTypeApplicationOctetStream - if opts.ContentType != "" { - if opts.ContentTypeCharset != "" { - contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset) - } else { - contentType = opts.ContentType - } - } + contentType := util.IfZero(opts.ContentType, typesniffer.MimeTypeApplicationOctetStream) header.Set("Content-Type", contentType) header.Set("X-Content-Type-Options", "nosniff") @@ -61,14 +54,18 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) { header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10)) } - if opts.Filename != "" { - disposition := opts.Disposition - if disposition == "" { - disposition = "attachment" - } + // Disable script execution of HTML/SVG files, since we serve the file from the same origin as Gitea server + header.Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox") + if strings.Contains(contentType, "application/pdf") { + // no sandbox attribute for PDF as it breaks rendering in at least safari. this + // should generally be safe as scripts inside PDF can not escape the PDF document + // see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion + // HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context + header.Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'") + } - backslashEscapedName := strings.ReplaceAll(strings.ReplaceAll(opts.Filename, `\`, `\\`), `"`, `\"`) // \ -> \\, " -> \" - header.Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"; filename*=UTF-8''%s`, disposition, backslashEscapedName, url.PathEscape(opts.Filename))) + if opts.Filename != "" && opts.ContentDisposition != "" { + header.Set("Content-Disposition", encodeContentDisposition(opts.ContentDisposition, path.Base(opts.Filename))) header.Set("Access-Control-Expose-Headers", "Content-Disposition") } @@ -84,50 +81,40 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) { } } -// ServeData download file from io.Reader -func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, mineBuf []byte, opts *ServeHeaderOptions) { - // do not set "Content-Length", because the length could only be set by callers, and it needs to support range requests - sniffedType := typesniffer.DetectContentType(mineBuf) - - // the "render" parameter came from year 2016: 638dd24c, it doesn't have clear meaning, so I think it could be removed later - isPlain := sniffedType.IsText() || r.FormValue("render") != "" +func serveSetHeadersByUserContent(w http.ResponseWriter, contentPrefetchBuf []byte, opts ServeHeaderOptions) { + var detectCharset bool if setting.MimeTypeMap.Enabled { - fileExtension := strings.ToLower(filepath.Ext(opts.Filename)) + fileExtension := strings.ToLower(path.Ext(opts.Filename)) opts.ContentType = setting.MimeTypeMap.Map[fileExtension] + detectCharset = strings.HasPrefix(opts.ContentType, "text/") && !strings.Contains(opts.ContentType, "charset=") } if opts.ContentType == "" { + sniffedType := typesniffer.DetectContentType(contentPrefetchBuf) if sniffedType.IsBrowsableBinaryType() { opts.ContentType = sniffedType.GetMimeType() - } else if isPlain { + } else if sniffedType.IsText() { + // intentionally do not render user's HTML content as a page, for safety, and avoid content spamming & abusing opts.ContentType = "text/plain" + detectCharset = true } else { opts.ContentType = typesniffer.MimeTypeApplicationOctetStream } } - if isPlain { - charset, _ := charsetModule.DetectEncoding(mineBuf) - opts.ContentTypeCharset = strings.ToLower(charset) + if detectCharset { + if charset, _ := charsetModule.DetectEncoding(contentPrefetchBuf); charset != "" { + opts.ContentType += "; charset=" + strings.ToLower(charset) + } } - isSVG := sniffedType.IsSvgImage() - - // serve types that can present a security risk with CSP - if isSVG { - w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox") - } else if sniffedType.IsPDF() { - // no sandbox attribute for pdf as it breaks rendering in at least safari. this - // should generally be safe as scripts inside PDF can not escape the PDF document - // see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion - // HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context - w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'") - } - - opts.Disposition = "inline" - if isSVG && !setting.UI.SVG.Enabled { - opts.Disposition = "attachment" + if opts.ContentDisposition == "" { + sniffedType := typesniffer.FromContentType(opts.ContentType) + opts.ContentDisposition = ContentDispositionInline + if sniffedType.IsSvgImage() && !setting.UI.SVG.Enabled { + opts.ContentDisposition = ContentDispositionAttachment + } } ServeSetHeaders(w, opts) @@ -135,7 +122,10 @@ func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, mineBuf []byt const mimeDetectionBufferLen = 1024 -func ServeContentByReader(r *http.Request, w http.ResponseWriter, size int64, reader io.Reader, opts *ServeHeaderOptions) { +func ServeUserContentByReader(r *http.Request, w http.ResponseWriter, size int64, reader io.Reader, opts ServeHeaderOptions) { + if opts.ContentLength != nil { + panic("do not set ContentLength, use size argument instead") + } buf := make([]byte, mimeDetectionBufferLen) n, err := util.ReadAtMost(reader, buf) if err != nil { @@ -145,7 +135,7 @@ func ServeContentByReader(r *http.Request, w http.ResponseWriter, size int64, re if n >= 0 { buf = buf[:n] } - setServeHeadersByFile(r, w, buf, opts) + serveSetHeadersByUserContent(w, buf, opts) // reset the reader to the beginning reader = io.MultiReader(bytes.NewReader(buf), reader) @@ -199,32 +189,29 @@ func ServeContentByReader(r *http.Request, w http.ResponseWriter, size int64, re partialLength := end - start + 1 w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, size)) w.Header().Set("Content-Length", strconv.FormatInt(partialLength, 10)) - if _, err = io.CopyN(io.Discard, reader, start); err != nil { - http.Error(w, "serve content: unable to skip", http.StatusInternalServerError) - return + + if seeker, ok := reader.(io.Seeker); ok { + if _, err = seeker.Seek(start, io.SeekStart); err != nil { + http.Error(w, "serve content: unable to seek", http.StatusInternalServerError) + return + } + } else { + if _, err = io.CopyN(io.Discard, reader, start); err != nil { + http.Error(w, "serve content: unable to skip", http.StatusInternalServerError) + return + } } w.WriteHeader(http.StatusPartialContent) _, _ = io.CopyN(w, reader, partialLength) // just like http.ServeContent, not necessary to handle the error } -func ServeContentByReadSeeker(r *http.Request, w http.ResponseWriter, modTime *time.Time, reader io.ReadSeeker, opts *ServeHeaderOptions) { - buf := make([]byte, mimeDetectionBufferLen) - n, err := util.ReadAtMost(reader, buf) +func ServeUserContentByFile(r *http.Request, w http.ResponseWriter, file fs.File, opts ServeHeaderOptions) { + info, err := file.Stat() if err != nil { - http.Error(w, "serve content: unable to read", http.StatusInternalServerError) + http.Error(w, "unable to serve file, stat error", http.StatusInternalServerError) return } - if _, err = reader.Seek(0, io.SeekStart); err != nil { - http.Error(w, "serve content: unable to seek", http.StatusInternalServerError) - return - } - if n >= 0 { - buf = buf[:n] - } - setServeHeadersByFile(r, w, buf, opts) - if modTime == nil { - modTime = &time.Time{} - } - http.ServeContent(w, r, opts.Filename, *modTime, reader) + opts.LastModified = info.ModTime() + ServeUserContentByReader(r, w, info.Size(), file, opts) } diff --git a/modules/httplib/serve_test.go b/modules/httplib/serve_test.go index 78b88c9b5f..38cf4c197f 100644 --- a/modules/httplib/serve_test.go +++ b/modules/httplib/serve_test.go @@ -16,7 +16,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestServeContentByReader(t *testing.T) { +func TestServeUserContentByReader(t *testing.T) { data := "0123456789abcdef" test := func(t *testing.T, expectedStatusCode int, expectedContent string) { @@ -27,7 +27,7 @@ func TestServeContentByReader(t *testing.T) { } reader := strings.NewReader(data) w := httptest.NewRecorder() - ServeContentByReader(r, w, int64(len(data)), reader, &ServeHeaderOptions{}) + ServeUserContentByReader(r, w, int64(len(data)), reader, ServeHeaderOptions{}) assert.Equal(t, expectedStatusCode, w.Code) if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK { assert.Equal(t, strconv.Itoa(len(expectedContent)), w.Header().Get("Content-Length")) @@ -58,7 +58,7 @@ func TestServeContentByReader(t *testing.T) { }) } -func TestServeContentByReadSeeker(t *testing.T) { +func TestServeUserContentByFile(t *testing.T) { data := "0123456789abcdef" tmpFile := t.TempDir() + "/test" err := os.WriteFile(tmpFile, []byte(data), 0o644) @@ -76,7 +76,7 @@ func TestServeContentByReadSeeker(t *testing.T) { defer seekReader.Close() w := httptest.NewRecorder() - ServeContentByReadSeeker(r, w, nil, seekReader, &ServeHeaderOptions{}) + ServeUserContentByFile(r, w, seekReader, ServeHeaderOptions{}) assert.Equal(t, expectedStatusCode, w.Code) if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK { assert.Equal(t, strconv.Itoa(len(expectedContent)), w.Header().Get("Content-Length")) diff --git a/modules/httplib/url.go b/modules/httplib/url.go index 5f35e66964..a689c9e98e 100644 --- a/modules/httplib/url.go +++ b/modules/httplib/url.go @@ -46,14 +46,14 @@ func IsRelativeURL(s string) bool { func getRequestScheme(req *http.Request) string { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto - if s := req.Header.Get("X-Forwarded-Proto"); s != "" { - return s + if proto, ok := parseForwardedProtoValue(req.Header.Get("X-Forwarded-Proto")); ok { + return proto } - if s := req.Header.Get("X-Forwarded-Protocol"); s != "" { - return s + if proto, ok := parseForwardedProtoValue(req.Header.Get("X-Forwarded-Protocol")); ok { + return proto } - if s := req.Header.Get("X-Url-Scheme"); s != "" { - return s + if proto, ok := parseForwardedProtoValue(req.Header.Get("X-Url-Scheme")); ok { + return proto } if s := req.Header.Get("Front-End-Https"); s != "" { return util.Iif(s == "on", "https", "http") @@ -64,6 +64,13 @@ func getRequestScheme(req *http.Request) string { return "" } +func parseForwardedProtoValue(val string) (string, bool) { + if val == "http" || val == "https" { + return val, true + } + return "", false +} + // GuessCurrentAppURL tries to guess the current full public URL (with sub-path) by http headers. It always has a '/' suffix, exactly the same as setting.AppURL // TODO: should rename it to GuessCurrentPublicURL in the future func GuessCurrentAppURL(ctx context.Context) string { @@ -72,6 +79,10 @@ func GuessCurrentAppURL(ctx context.Context) string { // GuessCurrentHostURL tries to guess the current full host URL (no sub-path) by http headers, there is no trailing slash. func GuessCurrentHostURL(ctx context.Context) string { + // "never" means always trust ROOT_URL and skip any request header detection. + if setting.PublicURLDetection == setting.PublicURLNever { + return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/") + } // Try the best guess to get the current host URL (will be used for public URL) by http headers. // At the moment, if site admin doesn't configure the proxy headers correctly, then Gitea would guess wrong. // There are some cases: diff --git a/modules/httplib/url_test.go b/modules/httplib/url_test.go index 4270d9fa89..373693693b 100644 --- a/modules/httplib/url_test.go +++ b/modules/httplib/url_test.go @@ -47,6 +47,7 @@ func TestGuessCurrentHostURL(t *testing.T) { defer test.MockVariableValue(&setting.AppURL, "http://cfg-host/sub/")() defer test.MockVariableValue(&setting.AppSubURL, "/sub")() headersWithProto := http.Header{"X-Forwarded-Proto": {"https"}} + maliciousProtoHeaders := http.Header{"X-Forwarded-Proto": {"http://attacker.host/?trash="}} t.Run("Legacy", func(t *testing.T) { defer test.MockVariableValue(&setting.PublicURLDetection, setting.PublicURLLegacy)() @@ -60,6 +61,9 @@ func TestGuessCurrentHostURL(t *testing.T) { // if "X-Forwarded-Proto" exists, then use it and "Host" header ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", Header: headersWithProto}) assert.Equal(t, "https://req-host:3000", GuessCurrentHostURL(ctx)) + + ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", Header: maliciousProtoHeaders}) + assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx)) }) t.Run("Auto", func(t *testing.T) { @@ -76,6 +80,24 @@ func TestGuessCurrentHostURL(t *testing.T) { ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", Header: headersWithProto}) assert.Equal(t, "https://req-host:3000", GuessCurrentHostURL(ctx)) + + ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", Header: maliciousProtoHeaders}) + assert.Equal(t, "http://req-host:3000", GuessCurrentHostURL(ctx)) + }) + + t.Run("Never", func(t *testing.T) { + defer test.MockVariableValue(&setting.PublicURLDetection, setting.PublicURLNever)() + + assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(t.Context())) + + ctx := context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000"}) + assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx)) + + ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", TLS: &tls.ConnectionState{}}) + assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx)) + + ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", Header: headersWithProto}) + assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx)) }) } diff --git a/modules/indexer/code/bleve/token/path/path.go b/modules/indexer/code/bleve/token/path/path.go index 6dfc12f146..e00bb695e2 100644 --- a/modules/indexer/code/bleve/token/path/path.go +++ b/modules/indexer/code/bleve/token/path/path.go @@ -27,7 +27,7 @@ func TokenFilterConstructor(config map[string]any, cache *registry.Cache) (analy func (s *TokenFilter) Filter(input analysis.TokenStream) analysis.TokenStream { if len(input) == 1 { - // if there is only one token, we dont need to generate the reversed chain + // if there is only one token, we don't need to generate the reversed chain return generatePathTokens(input, false) } diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go index 99f974b646..9d170528ad 100644 --- a/modules/indexer/code/elasticsearch/elasticsearch.go +++ b/modules/indexer/code/elasticsearch/elasticsearch.go @@ -393,7 +393,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int elastic.NewHighlight(). Field("content"). Field("filename"). - NumOfFragments(0). // return all highting content on fragments + NumOfFragments(0). // return all highlighting content on fragments HighlighterType("fvh"), ). Sort("_score", false). @@ -426,7 +426,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int elastic.NewHighlight(). Field("content"). Field("filename"). - NumOfFragments(0). // return all highting content on fragments + NumOfFragments(0). // return all highlighting content on fragments HighlighterType("fvh"), ). Sort("_score", false). diff --git a/modules/indexer/code/gitgrep/gitgrep.go b/modules/indexer/code/gitgrep/gitgrep.go index 6f6e0b47b9..5fbd7201ef 100644 --- a/modules/indexer/code/gitgrep/gitgrep.go +++ b/modules/indexer/code/gitgrep/gitgrep.go @@ -24,7 +24,7 @@ func indexSettingToGitGrepPathspecList() (list []string) { return list } -func PerformSearch(ctx context.Context, page int, repoID int64, gitRepo *git.Repository, ref git.RefName, keyword string, searchMode indexer.SearchModeType) (searchResults []*code_indexer.Result, total int, err error) { +func PerformSearch(ctx context.Context, page int, repoID int64, gitRepo *git.Repository, ref git.RefName, keyword string, searchMode indexer.SearchModeType) (searchResults []*code_indexer.Result, total int64, err error) { grepMode := git.GrepModeWords switch searchMode { case indexer.SearchModeExact: @@ -47,7 +47,7 @@ func PerformSearch(ctx context.Context, page int, repoID int64, gitRepo *git.Rep return nil, 0, fmt.Errorf("gitRepo.GetRefCommitID: %w", err) } - total = len(res) + total = int64(len(res)) pageStart := min((page-1)*setting.UI.RepoSearchPagingNum, len(res)) pageEnd := min(page*setting.UI.RepoSearchPagingNum, len(res)) res = res[pageStart:pageEnd] diff --git a/modules/indexer/code/search.go b/modules/indexer/code/search.go index eb20b70e71..b5eb5116b0 100644 --- a/modules/indexer/code/search.go +++ b/modules/indexer/code/search.go @@ -74,7 +74,7 @@ func HighlightSearchResultCode(filename, language string, lineNums []int, code s // we should highlight the whole code block first, otherwise it doesn't work well with multiple line highlighting lexer := highlight.DetectChromaLexerByFileName(filename, language) hl := highlight.RenderCodeByLexer(lexer, code) - highlightedLines := strings.Split(string(hl), "\n") + highlightedLines := highlight.UnsafeSplitHighlightedLines(hl) // The lineNums outputted by render might not match the original lineNums, because "highlight" removes the last `\n` lines := make([]*ResultLine, min(len(highlightedLines), len(lineNums))) @@ -130,7 +130,7 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res } // PerformSearch perform a search on a repository -func PerformSearch(ctx context.Context, opts *SearchOptions) (int, []*Result, []*SearchResultLanguages, error) { +func PerformSearch(ctx context.Context, opts *SearchOptions) (int64, []*Result, []*SearchResultLanguages, error) { if opts == nil || len(opts.Keyword) == 0 { return 0, nil, nil, nil } @@ -149,5 +149,5 @@ func PerformSearch(ctx context.Context, opts *SearchOptions) (int, []*Result, [] return 0, nil, nil, err } } - return int(total), displayResults, resultLanguages, nil + return total, displayResults, resultLanguages, nil } diff --git a/modules/json/jsonlegacy.go b/modules/json/jsonlegacy.go index 156e456041..99875b96f1 100644 --- a/modules/json/jsonlegacy.go +++ b/modules/json/jsonlegacy.go @@ -6,6 +6,7 @@ package json import ( + "encoding/json" //nolint:depguard // this package wraps it "io" ) @@ -20,3 +21,5 @@ func MarshalKeepOptionalEmpty(v any) ([]byte, error) { func NewDecoderCaseInsensitive(reader io.Reader) Decoder { return DefaultJSONHandler.NewDecoder(reader) } + +type Value = json.RawMessage diff --git a/modules/json/jsonv2.go b/modules/json/jsonv2.go index 0bba2783bc..c4afc9513b 100644 --- a/modules/json/jsonv2.go +++ b/modules/json/jsonv2.go @@ -8,6 +8,7 @@ package json import ( "bytes" jsonv1 "encoding/json" //nolint:depguard // this package wraps it + "encoding/json/jsontext" //nolint:depguard // this package wraps it jsonv2 "encoding/json/v2" //nolint:depguard // this package wraps it "io" ) @@ -90,3 +91,5 @@ func (d *jsonV2Decoder) Decode(v any) error { func NewDecoderCaseInsensitive(reader io.Reader) Decoder { return &jsonV2Decoder{reader: reader, opts: jsonV2.unmarshalCaseInsensitiveOptions} } + +type Value = jsontext.Value diff --git a/modules/lfs/content_store.go b/modules/lfs/content_store.go index 0d9c0c98ac..be1e6c8e90 100644 --- a/modules/lfs/content_store.go +++ b/modules/lfs/content_store.go @@ -104,7 +104,7 @@ func (s *ContentStore) Verify(pointer Pointer) (bool, error) { } // ReadMetaObject will read a git_model.LFSMetaObject and return a reader -func ReadMetaObject(pointer Pointer) (io.ReadSeekCloser, error) { +func ReadMetaObject(pointer Pointer) (storage.Object, error) { contentStore := NewContentStore() return contentStore.Get(pointer) } diff --git a/modules/lfstransfer/backend/backend.go b/modules/lfstransfer/backend/backend.go index f4e6157091..92f08eac13 100644 --- a/modules/lfstransfer/backend/backend.go +++ b/modules/lfstransfer/backend/backend.go @@ -95,7 +95,7 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans var respBody lfs.BatchResponse err = json.Unmarshal(respBytes, &respBody) if err != nil { - g.logger.Log("json umarshal error", err) + g.logger.Log("json unmarshal error", err) return nil, err } diff --git a/modules/lfstransfer/backend/lock.go b/modules/lfstransfer/backend/lock.go index 2c3c16a9bb..5bfa3a89c3 100644 --- a/modules/lfstransfer/backend/lock.go +++ b/modules/lfstransfer/backend/lock.go @@ -69,7 +69,7 @@ func (g *giteaLockBackend) Create(path, refname string) (transfer.Lock, error) { var respBody lfslock.LFSLockResponse err = json.Unmarshal(respBytes, &respBody) if err != nil { - g.logger.Log("json umarshal error", err) + g.logger.Log("json unmarshal error", err) return nil, err } @@ -202,7 +202,7 @@ func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, er var respBody lfslock.LFSLockList err = json.Unmarshal(respBytes, &respBody) if err != nil { - g.logger.Log("json umarshal error", err) + g.logger.Log("json unmarshal error", err) return nil, "", err } diff --git a/modules/lfstransfer/logger.go b/modules/lfstransfer/logger.go index 517c2d9ba1..dfb9c0bb66 100644 --- a/modules/lfstransfer/logger.go +++ b/modules/lfstransfer/logger.go @@ -17,5 +17,5 @@ func newLogger() transfer.Logger { } // Log implements transfer.Logger -func (g *GiteaLogger) Log(msg string, itms ...any) { +func (g *GiteaLogger) Log(msg string, items ...any) { } diff --git a/modules/markup/camo.go b/modules/markup/camo.go index 7e2583469d..f07d62d4f9 100644 --- a/modules/markup/camo.go +++ b/modules/markup/camo.go @@ -11,7 +11,6 @@ import ( "strings" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" ) // CamoEncode encodes a lnk to fit with the go-camo and camo proxy links. The purposes of camo-proxy are: @@ -27,7 +26,7 @@ func CamoEncode(link string) string { macSum := b64encode(mac.Sum(nil)) encodedURL := b64encode([]byte(link)) - return util.URLJoin(setting.Camo.ServerURL, macSum, encodedURL) + return strings.TrimSuffix(setting.Camo.ServerURL, "/") + "/" + macSum + "/" + encodedURL } func b64encode(data []byte) string { diff --git a/modules/markup/common/linkify.go b/modules/markup/common/linkify.go index 3eecb97eac..54e40a02d6 100644 --- a/modules/markup/common/linkify.go +++ b/modules/markup/common/linkify.go @@ -20,13 +20,13 @@ import ( ) type GlobalVarsType struct { - wwwURLRegxp *regexp.Regexp - LinkRegex *regexp.Regexp // fast matching a URL link, no any extra validation. + wwwURLRegexp *regexp.Regexp + LinkRegex *regexp.Regexp // fast matching a URL link, no any extra validation. } var GlobalVars = sync.OnceValue(func() *GlobalVarsType { v := &GlobalVarsType{} - v.wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`) + v.wwwURLRegexp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`) v.LinkRegex, _ = xurls.StrictMatchingScheme("https?://") return v }) @@ -75,7 +75,7 @@ func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont m = GlobalVars().LinkRegex.FindSubmatchIndex(line) } if m == nil && bytes.HasPrefix(line, domainWWW) { - m = GlobalVars().wwwURLRegxp.FindSubmatchIndex(line) + m = GlobalVars().wwwURLRegexp.FindSubmatchIndex(line) protocol = []byte("http") } if m != nil { @@ -96,6 +96,7 @@ func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont m[1] -= closing } } else if lastChar == ';' { + // exclude HTML entity reference, e.g.: exclude " " from "http://example.com?foo=1 " i := m[1] - 2 for ; i >= m[0]; i-- { if util.IsAlphaNumeric(line[i]) { @@ -105,7 +106,7 @@ func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont } if i != m[1]-2 { if line[i] == '&' { - m[1] -= m[1] - i + m[1] = i } } } diff --git a/modules/markup/external/openapi.go b/modules/markup/external/openapi.go index ac5eae53ff..91230e54d0 100644 --- a/modules/markup/external/openapi.go +++ b/modules/markup/external/openapi.go @@ -9,6 +9,7 @@ import ( "io" "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" ) @@ -46,34 +47,38 @@ func (p *openAPIRenderer) SanitizerRules() []setting.MarkupSanitizerRule { func (p *openAPIRenderer) GetExternalRendererOptions() (ret markup.ExternalRendererOptions) { ret.SanitizerDisabled = true ret.DisplayInIframe = true - ret.ContentSandbox = "" + ret.ContentSandbox = "allow-scripts allow-forms allow-modals allow-popups allow-downloads" return ret } func (p *openAPIRenderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { + if ctx.RenderOptions.StandalonePageOptions == nil { + opts := p.GetExternalRendererOptions() + return markup.RenderIFrame(ctx, &opts, output) + } + content, err := util.ReadWithLimit(input, int(setting.UI.MaxDisplayFileSize)) if err != nil { return err } - // TODO: can extract this to a tmpl file later + + // HINT: SWAGGER-OPENAPI-VIEWER: another place "templates/swagger/openapi-viewer.tmpl" _, err = io.WriteString(output, fmt.Sprintf( ` - +
- + `, - setting.StaticURLPrefix, - setting.AssetVersion, + public.AssetURI("css/swagger.css"), html.EscapeString(ctx.RenderOptions.RelativePath), html.EscapeString(util.UnsafeBytesToString(content)), - setting.StaticURLPrefix, - setting.AssetVersion, + public.AssetURI("js/swagger.js"), )) return err } diff --git a/modules/markup/html_commit.go b/modules/markup/html_commit.go index c319374a38..a2c5160674 100644 --- a/modules/markup/html_commit.go +++ b/modules/markup/html_commit.go @@ -4,13 +4,13 @@ package markup import ( + "fmt" "slices" "strings" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/references" - "code.gitea.io/gitea/modules/util" "golang.org/x/net/html" "golang.org/x/net/html/atom" @@ -219,7 +219,7 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) { continue } - link := "/:root/" + util.URLJoin(ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], "commit", hash) + link := fmt.Sprintf("/:root/%s/%s/commit/%s", ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], hash) replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit")) start = 0 node = node.NextSibling.NextSibling @@ -236,7 +236,7 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) { } refText := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha) - linkHref := "/:root/" + util.URLJoin(ref.Owner, ref.Name, "commit", ref.CommitSha) + linkHref := fmt.Sprintf("/:root/%s/%s/commit/%s", ref.Owner, ref.Name, ref.CommitSha) link := createLink(ctx, linkHref, refText, "commit") replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link) diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index ca2579c8ea..3a91797e25 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -23,12 +23,12 @@ const ( // externalIssueLink an HTML link to an alphanumeric-style issue func externalIssueLink(baseURL, class, name string) string { - return link(util.URLJoin(baseURL, name), class, name) + return link(strings.TrimSuffix(baseURL, "/")+"/"+name, class, name) } // numericLink an HTML to a numeric-style issue func numericIssueLink(baseURL, class string, index int, marker string) string { - return link(util.URLJoin(baseURL, strconv.Itoa(index)), class, fmt.Sprintf("%s%d", marker, index)) + return link(strings.TrimSuffix(baseURL, "/")+"/"+strconv.Itoa(index), class, fmt.Sprintf("%s%d", marker, index)) } // link an HTML link @@ -116,7 +116,7 @@ func TestRender_IssueIndexPattern2(t *testing.T) { links := make([]any, len(indices)) for i, index := range indices { - links[i] = numericIssueLink(util.URLJoin("/test-owner/test-repo", path), "ref-issue", index, marker) + links[i] = numericIssueLink("/test-owner/test-repo/"+path, "ref-issue", index, marker) } expectedNil := fmt.Sprintf(expectedFmt, links...) testRenderIssueIndexPattern(t, s, expectedNil, NewTestRenderContext(TestAppURL, localMetas)) @@ -210,7 +210,7 @@ func TestRender_IssueIndexPattern5(t *testing.T) { metas["regexp"] = pattern links := make([]any, len(ids)) for i, id := range ids { - links[i] = link(util.URLJoin("https://someurl.com/someUser/someRepo/", id), "ref-issue ref-external-issue", names[i]) + links[i] = link("https://someurl.com/someUser/someRepo/"+id, "ref-issue ref-external-issue", names[i]) } expected := fmt.Sprintf(expectedFmt, links...) @@ -288,11 +288,11 @@ func TestRender_AutoLink(t *testing.T) { } // render valid issue URLs - test(util.URLJoin(TestRepoURL, "issues", "3333"), - numericIssueLink(util.URLJoin(TestRepoURL, "issues"), "ref-issue", 3333, "#")) + test(TestRepoURL+"issues/3333", + numericIssueLink(TestRepoURL+"issues", "ref-issue", 3333, "#")) // render valid commit URLs - tmp := util.URLJoin(TestRepoURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae") + tmp := TestRepoURL + "commit/d8a994ef243349f321568f9e36d5c3f444b99cae" test(tmp, "d8a994ef24") tmp += "#diff-2" test(tmp, "d8a994ef24 (diff-2)") diff --git a/modules/markup/html_issue.go b/modules/markup/html_issue.go index 85bec5db20..a94abb3883 100644 --- a/modules/markup/html_issue.go +++ b/modules/markup/html_issue.go @@ -4,6 +4,7 @@ package markup import ( + "fmt" "strconv" "strings" @@ -162,7 +163,7 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { issueOwner := util.Iif(ref.Owner == "", ctx.RenderOptions.Metas["user"], ref.Owner) issueRepo := util.Iif(ref.Owner == "", ctx.RenderOptions.Metas["repo"], ref.Name) issuePath := util.Iif(ref.IsPull, "pulls", "issues") - linkHref := "/:root/" + util.URLJoin(issueOwner, issueRepo, issuePath, ref.Issue) + linkHref := fmt.Sprintf("/:root/%s/%s/%s/%s", issueOwner, issueRepo, issuePath, ref.Issue) // at the moment, only render the issue index in a full line (or simple line) as icon+title // otherwise it would be too noisy for "take #1 as an example" in a sentence diff --git a/modules/markup/html_link.go b/modules/markup/html_link.go index 1702950da8..c84e0b90d8 100644 --- a/modules/markup/html_link.go +++ b/modules/markup/html_link.go @@ -113,16 +113,17 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { } childNode.Parent = linkNode absoluteLink := IsFullURLString(link) - if !absoluteLink { + // FIXME: it should be fully refactored in the future, it uses various hacky approaches to guess how to encode a path for wiki + // When a link contains "/", then we assume that the user has provided a well-encoded link. + if !absoluteLink && !strings.Contains(link, "/") { + // So only guess for links without "/". if image { link = strings.ReplaceAll(link, " ", "+") } else { // the hacky wiki name encoding: space to "-" link = strings.ReplaceAll(link, " ", "-") // FIXME: it should support dashes in the link, eg: "the-dash-support.-" } - if !strings.Contains(link, "/") { - link = url.PathEscape(link) // FIXME: it doesn't seem right and it might cause double-escaping - } + link = url.PathEscape(link) } if image { title := props["title"] diff --git a/modules/markup/html_mention.go b/modules/markup/html_mention.go index f97c034cf3..00cd51ca94 100644 --- a/modules/markup/html_mention.go +++ b/modules/markup/html_mention.go @@ -4,6 +4,7 @@ package markup import ( + "fmt" "strings" "code.gitea.io/gitea/modules/references" @@ -26,14 +27,11 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) { loc.End += start mention := node.Data[loc.Start:loc.End] teams, ok := ctx.RenderOptions.Metas["teams"] - // FIXME: util.URLJoin may not be necessary here: - // - setting.AppURL is defined to have a terminal '/' so unless mention[1:] - // is an AppSubURL link we can probably fallback to concatenation. - // team mention should follow @orgName/teamName style + if ok && strings.Contains(mention, "/") { mentionOrgAndTeam := strings.Split(mention, "/") if mentionOrgAndTeam[0][1:] == ctx.RenderOptions.Metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") { - link := "/:root/" + util.URLJoin("org", ctx.RenderOptions.Metas["org"], "teams", mentionOrgAndTeam[1]) + link := fmt.Sprintf("/:root/org/%s/teams/%s", ctx.RenderOptions.Metas["org"], mentionOrgAndTeam[1]) replaceContent(node, loc.Start, loc.End, createLink(ctx, link, mention, "" /*mention*/)) node = node.NextSibling.NextSibling start = 0 diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 5f873d2985..e62747c724 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -34,15 +34,15 @@ func TestRender_Commits(t *testing.T) { sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d" repo := markup.TestAppURL + testRepoOwnerName + "/" + testRepoName + "/" - commit := util.URLJoin(repo, "commit", sha) + commit := repo + "commit/" + sha commitPath := "/user13/repo11/commit/" + sha - tree := util.URLJoin(repo, "tree", sha, "src") + tree := repo + "tree/" + sha + "/src" - file := util.URLJoin(repo, "commit", sha, "example.txt") + file := repo + "commit/" + sha + "/example.txt" fileWithExtra := file + ":" fileWithHash := file + "#L2" fileWithHasExtra := file + "#L2:" - commitCompare := util.URLJoin(repo, "compare", sha+"..."+sha) + commitCompare := repo + "compare/" + sha + "..." + sha commitCompareWithHash := commitCompare + "#L2" test(sha, `

65f1bf27bc

`) @@ -90,14 +90,14 @@ func TestRender_CrossReferences(t *testing.T) { "/home/gitea/go-gitea/gitea#12345", `

/home/gitea/go-gitea/gitea#12345

`) test( - util.URLJoin(markup.TestAppURL, "gogitea", "gitea", "issues", "12345"), - `

gogitea/gitea#12345

`) + markup.TestAppURL+"gogitea/gitea/issues/12345", + `

gogitea/gitea#12345

`) test( - util.URLJoin(markup.TestAppURL, "go-gitea", "gitea", "issues", "12345"), - `

go-gitea/gitea#12345

`) + markup.TestAppURL+"go-gitea/gitea/issues/12345", + `

go-gitea/gitea#12345

`) test( - util.URLJoin(markup.TestAppURL, "gogitea", "some-repo-name", "issues", "12345"), - `

gogitea/some-repo-name#12345

`) + markup.TestAppURL+"gogitea/some-repo-name/issues/12345", + `

gogitea/some-repo-name#12345

`) inputURL := setting.AppURL + "a/b/commit/0123456789012345678901234567890123456789/foo.txt?a=b#L2-L3" test( @@ -375,7 +375,7 @@ func TestRender_emoji(t *testing.T) { func TestRender_ShortLinks(t *testing.T) { setting.AppURL = markup.TestAppURL - tree := util.URLJoin(markup.TestRepoURL, "src", "master") + tree := markup.TestRepoURL + "src/master" test := func(input, expected string) { buffer, err := markdown.RenderString(markup.NewTestRenderContext(tree), input) @@ -383,15 +383,15 @@ func TestRender_ShortLinks(t *testing.T) { assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) } - url := util.URLJoin(tree, "Link") - otherURL := util.URLJoin(tree, "Other-Link") - encodedURL := util.URLJoin(tree, "Link%3F") - imgurl := util.URLJoin(tree, "Link.jpg") - otherImgurl := util.URLJoin(tree, "Link+Other.jpg") - encodedImgurl := util.URLJoin(tree, "Link+%23.jpg") - notencodedImgurl := util.URLJoin(tree, "some", "path", "Link+#.jpg") - renderableFileURL := util.URLJoin(tree, "markdown_file.md") - unrenderableFileURL := util.URLJoin(tree, "file.zip") + url := tree + "/Link" + otherURL := tree + "/Other-Link" + encodedURL := tree + "/Link%3F" + imgurl := tree + "/Link.jpg" + otherImgurl := tree + "/Link+Other.jpg" + encodedImgurl := tree + "/Link+%23.jpg" + notencodedImgurl := tree + "/some/path/Link%20#.jpg" + renderableFileURL := tree + "/markdown_file.md" + unrenderableFileURL := tree + "/file.zip" favicon := "http://google.com/favicon.ico" test( @@ -466,6 +466,8 @@ func TestRender_ShortLinks(t *testing.T) { "[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]", `

AltName

`, ) + // FIXME: it's unable to resolve: [[link?k=v]] + // FIXME: it is a wrong test case, it is not an image, but a link with anchor "#.jpg" test( "[[some/path/Link #.jpg]]", `

some/path/Link #.jpg

`, diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index 555a171685..4a560517f2 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -70,6 +70,8 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa } case *ast.CodeSpan: g.transformCodeSpan(ctx, v, reader) + case *ast.FencedCodeBlock: + g.transformFencedCodeblock(v, reader) case *ast.Blockquote: return g.transformBlockquote(v, reader) } diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index cca44a8774..f6a6cb26c6 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -21,7 +21,6 @@ import ( chromahtml "github.com/alecthomas/chroma/v2/formatters/html" "github.com/yuin/goldmark" highlighting "github.com/yuin/goldmark-highlighting/v2" - meta "github.com/yuin/goldmark-meta" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" @@ -166,7 +165,6 @@ func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender { ParseBlockDollar: setting.Markdown.MathCodeBlockOptions.ParseBlockDollar, ParseBlockSquareBrackets: setting.Markdown.MathCodeBlockOptions.ParseBlockSquareBrackets, // this is a bad syntax "\[ ... \]", it conflicts with normal markdown escaping }), - meta.Meta, ), goldmark.WithParserOptions( parser.WithAttribute(), diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 8d6b3b3c80..2f14a0fae9 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -14,7 +14,6 @@ import ( "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" - "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) @@ -23,7 +22,6 @@ const ( AppURL = "http://localhost:3000/" testRepoOwnerName = "user13" testRepoName = "repo11" - FullURL = AppURL + testRepoOwnerName + "/" + testRepoName + "/" ) // these values should match the const above @@ -47,8 +45,9 @@ func TestRender_StandardLinks(t *testing.T) { func TestRender_Images(t *testing.T) { setting.AppURL = AppURL + const baseLink = "http://localhost:3000/user13/repo11" render := func(input, expected string) { - buffer, err := markdown.RenderString(markup.NewTestRenderContext(FullURL), input) + buffer, err := markdown.RenderString(markup.NewTestRenderContext(baseLink), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) } @@ -56,7 +55,7 @@ func TestRender_Images(t *testing.T) { url := "../../.images/src/02/train.jpg" title := "Train" href := "https://gitea.io" - result := util.URLJoin(FullURL, url) + result := baseLink + "/.images/src/02/train.jpg" // resolved link should not go out of the base link // hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now render( @@ -88,6 +87,7 @@ func TestRender_Images(t *testing.T) { } func TestTotal_RenderString(t *testing.T) { + const FullURL = AppURL + testRepoOwnerName + "/" + testRepoName + "/" setting.AppURL = AppURL defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() @@ -429,9 +429,12 @@ test --- test `, - `- item1 -- item2 - + `
+
    +
  • item1
  • +
  • item2
  • +
+

test

`, }, @@ -443,8 +446,8 @@ anything --- test `, - `anything - + `
+

anything

test

`, }, @@ -471,14 +474,26 @@ foo: bar
  • task 1
+`, + }, + // we have our own frontmatter parser, don't need to use github.com/yuin/goldmark-meta + { + "InvalidFrontmatter", + `--- +foo +`, + `
+

foo

`, }, } - for _, test := range testcases { - res, err := markdown.RenderString(markup.NewTestRenderContext(), test.input) - assert.NoError(t, err, "Unexpected error in testcase: %q", test.name) - assert.Equal(t, test.expected, string(res), "Unexpected result in testcase %q", test.name) + for _, tt := range testcases { + t.Run(tt.name, func(t *testing.T) { + res, err := markdown.RenderString(markup.NewTestRenderContext(), tt.input) + assert.NoError(t, err, "Unexpected error in testcase: %q", tt.name) + assert.Equal(t, tt.expected, string(res), "Unexpected result in testcase %q", tt.name) + }) } } @@ -568,3 +583,39 @@ func TestMarkdownLink(t *testing.T) { assert.Equal(t, `

https://example.com/__init__.py

`, string(result)) } + +func TestMarkdownUlDir(t *testing.T) { + defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, false)() + result, err := markdown.RenderString(markup.NewTestRenderContext(), ` +* a + * b +`) + assert.NoError(t, err) + assert.Equal(t, `
    +
  • a +
      +
    • b
    • +
    +
  • +
+`, string(result)) +} + +func TestMarkdownFencedCodeBlock(t *testing.T) { + testRender := func(input, expected string) { + buffer, err := markdown.RenderString(markup.NewTestRenderContext(), input) + assert.NoError(t, err) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) + } + const nl = "\n" + const prefix = `
`
+	const suffix = `
` + + testRender("```\ncode\n```", prefix+`code`+nl+``+suffix) + + const jsCommon = prefix + `code` + nl + `` + suffix + testRender("```js\ncode\n```", jsCommon) + testRender("```js:app.ts\ncode\n```", jsCommon) + testRender("```js,ignore\ncode\n```", jsCommon) + testRender("```js ignore\ncode\n```", jsCommon) +} diff --git a/modules/markup/markdown/meta.go b/modules/markup/markdown/meta.go index 65352cee05..ecc0b81098 100644 --- a/modules/markup/markdown/meta.go +++ b/modules/markup/markdown/meta.go @@ -60,8 +60,8 @@ func ExtractMetadata(contents string, out any) (string, error) { return string(body), err } -// ExtractMetadata consumes a markdown file, parses YAML frontmatter, -// and returns the frontmatter metadata separated from the markdown content +// ExtractMetadataBytes consumes a Markdown content, parses YAML frontmatter, +// and returns the frontmatter metadata separated from the Markdown content func ExtractMetadataBytes(contents []byte, out any) ([]byte, error) { var front, body []byte diff --git a/modules/markup/markdown/transform_codeblock.go b/modules/markup/markdown/transform_codeblock.go new file mode 100644 index 0000000000..de9264c4c4 --- /dev/null +++ b/modules/markup/markdown/transform_codeblock.go @@ -0,0 +1,32 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markdown + +import ( + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/text" +) + +func (g *ASTTransformer) transformFencedCodeblock(v *ast.FencedCodeBlock, reader text.Reader) { + // * Some engines support a meta syntax for appending the filename after the language, separated by a colon + // * https://www.glukhov.org/documentation-tools/markdown/markdown-codeblocks/ + // * Some engines support additional "options" after the language, separated by a space or comma: ```rust,ignore``` + // * https://docs.readme.com/rdmd/docs/code-blocks + // * https://next-book.vercel.app/reference/fencedcode + if v.Info == nil { + return + } + info := v.Info.Segment.Value(reader.Source()) + newEnd := -1 + for i, b := range info { + if b == ' ' || b == ',' || b == ':' { + newEnd = i + break + } + } + if newEnd != -1 { + start := v.Info.Segment.Start + v.Info = ast.NewTextSegment(text.NewSegment(start, start+newEnd)) + } +} diff --git a/modules/markup/markdown/transform_list.go b/modules/markup/markdown/transform_list.go index c89ad2f2cf..6cafa8ff78 100644 --- a/modules/markup/markdown/transform_list.go +++ b/modules/markup/markdown/transform_list.go @@ -81,5 +81,16 @@ func (g *ASTTransformer) transformList(_ *markup.RenderContext, v *ast.List, rc v.AppendChild(v, newChild) } } - g.applyElementDir(v) + + nestedList := false + for p := v.Parent(); p != nil; p = p.Parent() { + if _, ok := p.(*ast.List); ok { + nestedList = true + break + } + } + if !nestedList { + // "dir=auto" should be only added to top-level "ul". https://github.com/go-gitea/gitea/issues/35058 + g.applyElementDir(v) + } } diff --git a/modules/markup/render.go b/modules/markup/render.go index 5785dc5ad5..caed3428e0 100644 --- a/modules/markup/render.go +++ b/modules/markup/render.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/htmlutil" "code.gitea.io/gitea/modules/markup/internal" + "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/util" @@ -37,6 +38,14 @@ var RenderBehaviorForTesting struct { DisableAdditionalAttributes bool } +type WebThemeInterface interface { + PublicAssetURI() string +} + +type StandalonePageOptions struct { + CurrentWebTheme WebThemeInterface +} + type RenderOptions struct { UseAbsoluteLink bool @@ -54,7 +63,7 @@ type RenderOptions struct { Metas map[string]string // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page - InStandalonePage bool + StandalonePageOptions *StandalonePageOptions // EnableHeadingIDGeneration controls whether to auto-generate IDs for HTML headings without id attribute. // This should be enabled for repository files and wiki pages, but disabled for comments to avoid duplicate IDs. @@ -126,8 +135,8 @@ func (ctx *RenderContext) WithMetas(metas map[string]string) *RenderContext { return ctx } -func (ctx *RenderContext) WithInStandalonePage(v bool) *RenderContext { - ctx.RenderOptions.InStandalonePage = v +func (ctx *RenderContext) WithStandalonePage(opts StandalonePageOptions) *RenderContext { + ctx.RenderOptions.StandalonePageOptions = &opts return ctx } @@ -196,20 +205,18 @@ func RenderString(ctx *RenderContext, content string) (string, error) { return buf.String(), nil } -func renderIFrame(ctx *RenderContext, sandbox string, output io.Writer) error { +func RenderIFrame(ctx *RenderContext, opts *ExternalRendererOptions, output io.Writer) error { src := fmt.Sprintf("%s/%s/%s/render/%s/%s", setting.AppSubURL, url.PathEscape(ctx.RenderOptions.Metas["user"]), url.PathEscape(ctx.RenderOptions.Metas["repo"]), util.PathEscapeSegments(ctx.RenderOptions.Metas["RefTypeNameSubURL"]), util.PathEscapeSegments(ctx.RenderOptions.RelativePath), ) - - var sandboxAttrValue template.HTML - if sandbox != "" { - sandboxAttrValue = htmlutil.HTMLFormat(`sandbox="%s"`, sandbox) + var extraAttrs template.HTML + if opts.ContentSandbox != "" { + extraAttrs = htmlutil.HTMLFormat(` sandbox="%s"`, opts.ContentSandbox) } - iframe := htmlutil.HTMLFormat(``, src, sandboxAttrValue) - _, err := io.WriteString(output, string(iframe)) + _, err := htmlutil.HTMLPrintf(output, ``, src, extraAttrs) return err } @@ -231,16 +238,17 @@ func getExternalRendererOptions(renderer Renderer) (ret ExternalRendererOptions, func RenderWithRenderer(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error { var extraHeadHTML template.HTML if extOpts, ok := getExternalRendererOptions(renderer); ok && extOpts.DisplayInIframe { - if !ctx.RenderOptions.InStandalonePage { + if ctx.RenderOptions.StandalonePageOptions == nil { // for an external "DisplayInIFrame" render, it could only output its content in a standalone page // otherwise, a `, ret) + + ret = render(ctx, ExternalRendererOptions{ContentSandbox: "allow"}) + assert.Equal(t, ``, ret) +} diff --git a/modules/markup/sanitizer_default.go b/modules/markup/sanitizer_default.go index 7fdf66c4bc..447cf4807e 100644 --- a/modules/markup/sanitizer_default.go +++ b/modules/markup/sanitizer_default.go @@ -56,6 +56,11 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy { policy.AllowAttrs("src", "autoplay", "controls").OnElements("video") + // Native support of "" + // ATTENTION: it only works with "auto" theme, because "media" query doesn't work with the theme chosen by end user manually. + // For example: browser's color scheme is "dark", but end user chooses "light" theme. Maybe it needs JS to help to make it work. + policy.AllowAttrs("media", "srcset").OnElements("source") + policy.AllowAttrs("loading").OnElements("img") // Allow generally safe attributes (reference: https://github.com/jch/html-pipeline) @@ -81,11 +86,12 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy { "data-markdown-generated-content", "data-attr-class", } generalSafeElements := []string{ - "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "br", "b", "i", "strong", "em", "a", "pre", "code", "img", "tt", + "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "br", "b", "center", "i", "strong", "em", "a", "pre", "code", "img", "tt", "div", "ins", "del", "sup", "sub", "p", "ol", "ul", "table", "thead", "tbody", "tfoot", "blockquote", "label", "dl", "dt", "dd", "kbd", "q", "samp", "var", "hr", "ruby", "rt", "rp", "li", "tr", "td", "th", "s", "strike", "summary", "details", "caption", "figure", "figcaption", "abbr", "bdo", "cite", "dfn", "mark", "small", "span", "time", "video", "wbr", + "picture", "source", } // FIXME: Need to handle longdesc in img but there is no easy way to do it policy.AllowAttrs(generalSafeAttrs...).OnElements(generalSafeElements...) diff --git a/modules/markup/sanitizer_default_test.go b/modules/markup/sanitizer_default_test.go index e5ba018e1b..e66f00c02f 100644 --- a/modules/markup/sanitizer_default_test.go +++ b/modules/markup/sanitizer_default_test.go @@ -58,6 +58,9 @@ func TestSanitizer(t *testing.T) { `my custom URL scheme`, `my custom URL scheme`, `my custom URL scheme`, `my custom URL scheme`, + // picture + `c`, `c`, + // Disallow dangerous url schemes `bad`, `bad`, `bad`, `bad`, diff --git a/modules/migration/file_format.go b/modules/migration/file_format.go index aca2885eef..86a1aa90a2 100644 --- a/modules/migration/file_format.go +++ b/modules/migration/file_format.go @@ -12,10 +12,17 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" - "github.com/santhosh-tekuri/jsonschema/v5" + "github.com/santhosh-tekuri/jsonschema/v6" "go.yaml.in/yaml/v4" ) +// schemaLoader implements jsonschema.URLLoader +type schemaLoader struct{} + +func (l *schemaLoader) Load(url string) (any, error) { + return openSchema(url) +} + // Load project data from file, with optional validation func Load(filename string, data any, validation bool) error { isJSON := strings.HasSuffix(filename, ".json") @@ -43,7 +50,7 @@ func unmarshal(bs []byte, data any, isJSON bool) error { func getSchema(filename string) (*jsonschema.Schema, error) { c := jsonschema.NewCompiler() - c.LoadURL = openSchema + c.UseLoader(&schemaLoader{}) return c.Compile(filename) } diff --git a/modules/migration/file_format_test.go b/modules/migration/file_format_test.go index da997f645b..fd7db9074b 100644 --- a/modules/migration/file_format_test.go +++ b/modules/migration/file_format_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/santhosh-tekuri/jsonschema/v5" + "github.com/santhosh-tekuri/jsonschema/v6" "github.com/stretchr/testify/assert" ) diff --git a/modules/migration/schemas_bindata.go b/modules/migration/schemas_bindata.go index 695c2c1135..2f0e254408 100644 --- a/modules/migration/schemas_bindata.go +++ b/modules/migration/schemas_bindata.go @@ -8,7 +8,6 @@ package migration import ( - "io" "io/fs" "path" "sync" @@ -16,6 +15,8 @@ import ( _ "embed" "code.gitea.io/gitea/modules/assetfs" + + "github.com/santhosh-tekuri/jsonschema/v6" ) //go:embed bindata.dat @@ -25,6 +26,11 @@ var BuiltinAssets = sync.OnceValue(func() fs.FS { return assetfs.NewEmbeddedFS(bindata) }) -func openSchema(filename string) (io.ReadCloser, error) { - return BuiltinAssets().Open(path.Base(filename)) +func openSchema(filename string) (any, error) { + f, err := BuiltinAssets().Open(path.Base(filename)) + if err != nil { + return nil, err + } + defer f.Close() + return jsonschema.UnmarshalJSON(f) } diff --git a/modules/migration/schemas_dynamic.go b/modules/migration/schemas_dynamic.go index dca109d6af..46db879285 100644 --- a/modules/migration/schemas_dynamic.go +++ b/modules/migration/schemas_dynamic.go @@ -6,14 +6,15 @@ package migration import ( - "io" "net/url" "os" "path" "path/filepath" + + "github.com/santhosh-tekuri/jsonschema/v6" ) -func openSchema(s string) (io.ReadCloser, error) { +func openSchema(s string) (any, error) { u, err := url.Parse(s) if err != nil { return nil, err @@ -34,5 +35,10 @@ func openSchema(s string) (io.ReadCloser, error) { filename = filepath.Join("modules/migration/schemas", basename) } } - return os.Open(filename) + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + return jsonschema.UnmarshalJSON(f) } diff --git a/modules/optional/option.go b/modules/optional/option.go index cbecf86987..a278723bef 100644 --- a/modules/optional/option.go +++ b/modules/optional/option.go @@ -67,3 +67,17 @@ func ParseBool(s string) Option[bool] { } return Some(v) } + +func AssignPtrValue[T comparable](changed *bool, target, src *T) { + if src != nil && *src != *target { + *target = *src + *changed = true + } +} + +func AssignPtrString[TO, FROM ~string](changed *bool, target *TO, src *FROM) { + if src != nil && string(*src) != string(*target) { + *target = TO(*src) + *changed = true + } +} diff --git a/modules/packages/content_store.go b/modules/packages/content_store.go index 57974515e2..4c61233b5e 100644 --- a/modules/packages/content_store.go +++ b/modules/packages/content_store.go @@ -36,8 +36,8 @@ func (s *ContentStore) ShouldServeDirect() bool { return setting.Packages.Storage.ServeDirect() } -func (s *ContentStore) GetServeDirectURL(key BlobHash256Key, filename, method string, reqParams url.Values) (*url.URL, error) { - return s.store.URL(KeyToRelativePath(key), filename, method, reqParams) +func (s *ContentStore) GetServeDirectURL(key BlobHash256Key, filename, method string, reqParams *storage.ServeDirectOptions) (*url.URL, error) { + return s.store.ServeDirectURL(KeyToRelativePath(key), filename, method, reqParams) } // FIXME: Workaround to be removed in v1.20 diff --git a/modules/packages/cran/metadata.go b/modules/packages/cran/metadata.go index 0b0bfb07c6..0856565e10 100644 --- a/modules/packages/cran/metadata.go +++ b/modules/packages/cran/metadata.go @@ -34,7 +34,7 @@ var ( var ( fieldPattern = regexp.MustCompile(`\A\S+:`) namePattern = regexp.MustCompile(`\A[a-zA-Z][a-zA-Z0-9\.]*[a-zA-Z0-9]\z`) - versionPattern = regexp.MustCompile(`\A[0-9]+(?:[.\-][0-9]+){1,3}\z`) + versionPattern = regexp.MustCompile(`\A[0-9]+(?:[.\-][0-9]+)+\z`) authorReplacePattern = regexp.MustCompile(`[\[\(].+?[\]\)]`) ) diff --git a/modules/packages/cran/metadata_test.go b/modules/packages/cran/metadata_test.go index ff68c34c51..1d652a4a05 100644 --- a/modules/packages/cran/metadata_test.go +++ b/modules/packages/cran/metadata_test.go @@ -128,13 +128,22 @@ func TestParseDescription(t *testing.T) { }) t.Run("InvalidVersion", func(t *testing.T) { - for _, version := range []string{"1", "1 0", "1.2.3.4.5", "1-2-3-4-5", "1.", "1.0.", "1-", "1-0-"} { + for _, version := range []string{"1", "1 0", "1.", "1.0.", "1-", "1-0-"} { p, err := ParseDescription(createDescription(packageName, version)) assert.Nil(t, p) assert.ErrorIs(t, err, ErrInvalidVersion) } }) + t.Run("ValidVersionManyComponents", func(t *testing.T) { + for _, version := range []string{"0.3.4.0.2", "1.2.3.4.5", "1-2-3-4-5"} { + p, err := ParseDescription(createDescription(packageName, version)) + assert.NoError(t, err) + assert.NotNil(t, p) + assert.Equal(t, version, p.Version) + } + }) + t.Run("Valid", func(t *testing.T) { p, err := ParseDescription(createDescription(packageName, packageVersion)) assert.NoError(t, err) diff --git a/modules/packages/nuget/metadata.go b/modules/packages/nuget/metadata.go index 5124627395..ae15e4ec83 100644 --- a/modules/packages/nuget/metadata.go +++ b/modules/packages/nuget/metadata.go @@ -140,7 +140,7 @@ type nuspecPackage struct { func ParsePackageMetaData(r io.ReaderAt, size int64) (*Package, error) { archive, err := zip.NewReader(r, size) if err != nil { - return nil, err + return nil, util.NewInvalidArgumentErrorf("unable to parse package meta: %v", err) } for _, file := range archive.File { diff --git a/modules/packages/nuget/symbol_extractor.go b/modules/packages/nuget/symbol_extractor.go index 2eadee5463..5e398151e8 100644 --- a/modules/packages/nuget/symbol_extractor.go +++ b/modules/packages/nuget/symbol_extractor.go @@ -42,7 +42,7 @@ func (l PortablePdbList) Close() { func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) { archive, err := zip.NewReader(r, size) if err != nil { - return nil, err + return nil, util.NewInvalidArgumentErrorf("unable to extract portable pdb: %v", err) } var pdbs PortablePdbList diff --git a/modules/packages/rpm/metadata.go b/modules/packages/rpm/metadata.go index f4f78c2cab..d8ac7ea75f 100644 --- a/modules/packages/rpm/metadata.go +++ b/modules/packages/rpm/metadata.go @@ -46,10 +46,11 @@ type Package struct { } type VersionMetadata struct { - License string `json:"license,omitempty"` - ProjectURL string `json:"project_url,omitempty"` - Summary string `json:"summary,omitempty"` - Description string `json:"description,omitempty"` + License string `json:"license,omitempty"` + ProjectURL string `json:"project_url,omitempty"` + Summary string `json:"summary,omitempty"` + Description string `json:"description,omitempty"` + Updates []*Update `json:"updates,omitempty"` } type FileMetadata struct { @@ -296,3 +297,43 @@ func getChangelogs(h *rpmutils.RpmHeader) []*Changelog { } return changelogs } + +type DateAttr struct { + Date string `xml:"date,attr" json:"date"` +} + +type Update struct { + From string `xml:"from,attr" json:"from"` + Status string `xml:"status,attr" json:"status"` + Type string `xml:"type,attr" json:"type"` + Version string `xml:"version,attr" json:"version"` + ID string `xml:"id" json:"id"` + Title string `xml:"title" json:"title"` + Severity string `xml:"severity" json:"severity"` + Description string `xml:"description" json:"description"` + Issued *DateAttr `xml:"issued" json:"issued"` + Updated *DateAttr `xml:"updated" json:"updated"` + References []*Reference `xml:"references>reference" json:"references"` + PkgList []*Collection `xml:"pkglist>collection" json:"pkg_list"` +} + +type Reference struct { + Href string `xml:"href,attr" json:"href"` + ID string `xml:"id,attr" json:"id"` + Title string `xml:"title,attr" json:"title"` + Type string `xml:"type,attr" json:"type"` +} + +type Collection struct { + Short string `xml:"short,attr" json:"short"` + Packages []*UpdatePackage `xml:"package" json:"packages"` +} + +type UpdatePackage struct { + Arch string `xml:"arch,attr" json:"arch"` + Name string `xml:"name,attr" json:"name"` + Release string `xml:"release,attr" json:"release"` + Src string `xml:"src,attr" json:"src"` + Version string `xml:"version,attr" json:"version"` + Filename string `xml:"filename" json:"filename"` +} diff --git a/modules/packages/terraform/lock.go b/modules/packages/terraform/lock.go new file mode 100644 index 0000000000..3c326c04e9 --- /dev/null +++ b/modules/packages/terraform/lock.go @@ -0,0 +1,100 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package terraform + +import ( + "context" + "errors" + "io" + "time" + + "code.gitea.io/gitea/models/db" + packages_model "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/util" + + "xorm.io/builder" +) + +const LockFile = "terraform.lock" + +// LockInfo is the metadata for a terraform lock. +type LockInfo struct { + ID string `json:"ID"` + Operation string `json:"Operation"` + Info string `json:"Info"` + Who string `json:"Who"` + Version string `json:"Version"` + Created time.Time `json:"Created"` + Path string `json:"Path"` +} + +func (l *LockInfo) IsLocked() bool { + return l.ID != "" +} + +func ParseLockInfo(r io.Reader) (*LockInfo, error) { + var lock LockInfo + err := json.NewDecoder(r).Decode(&lock) + if err != nil { + return nil, err + } + // ID is required. Rest is less important. + if lock.ID == "" { + return nil, util.NewInvalidArgumentErrorf("terraform lock is missing an ID") + } + return &lock, nil +} + +// GetLock returns the terraform lock for the given package. +// Lock is empty if no lock exists. +func GetLock(ctx context.Context, packageID int64) (LockInfo, error) { + var lock LockInfo + locks, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypePackage, packageID, LockFile) + if err != nil { + return lock, err + } + if len(locks) == 0 || locks[0].Value == "" { + return lock, nil + } + + err = json.Unmarshal([]byte(locks[0].Value), &lock) + return lock, err +} + +// SetLock sets the terraform lock for the given package. +func SetLock(ctx context.Context, packageID int64, lock *LockInfo) error { + jsonBytes, err := json.Marshal(lock) + if err != nil { + return err + } + + return updateLock(ctx, packageID, string(jsonBytes), builder.Eq{"value": ""}) +} + +// RemoveLock removes the terraform lock for the given package. +func RemoveLock(ctx context.Context, packageID int64) error { + return updateLock(ctx, packageID, "", builder.Neq{"value": ""}) +} + +func updateLock(ctx context.Context, refID int64, value string, cond builder.Cond) error { + pp := packages_model.PackageProperty{RefType: packages_model.PropertyTypePackage, RefID: refID, Name: LockFile} + ok, err := db.GetEngine(ctx).Get(&pp) + if err != nil { + return err + } + if ok { + n, err := db.GetEngine(ctx).Where("ref_type=? AND ref_id=? AND name=?", packages_model.PropertyTypePackage, refID, LockFile).And(cond).Cols("value").Update(&packages_model.PackageProperty{Value: value}) + if err != nil { + return err + } + if n == 0 { + return errors.New("failed to update lock state") + } + + return nil + } + _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypePackage, refID, LockFile, value) + return err +} diff --git a/modules/packages/terraform/state.go b/modules/packages/terraform/state.go new file mode 100644 index 0000000000..5763128699 --- /dev/null +++ b/modules/packages/terraform/state.go @@ -0,0 +1,38 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package terraform + +import ( + "io" + + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/util" +) + +// Note: this is a subset of the Terraform state file format as the full one has two forms. +// If needed, it can be expanded in the future. + +type State struct { + Serial uint64 `json:"serial"` + Lineage string `json:"lineage"` +} + +// ParseState parses the required parts of Terraform state file +func ParseState(r io.Reader) (*State, error) { + var state State + err := json.NewDecoder(r).Decode(&state) + if err != nil { + return nil, err + } + // Serial starts at 1; 0 means it wasn't set in the state file + if state.Serial == 0 { + return nil, util.NewInvalidArgumentErrorf("state serial is missing") + } + // Lineage should always be set + if state.Lineage == "" { + return nil, util.NewInvalidArgumentErrorf("state lineage is missing") + } + + return &state, nil +} diff --git a/modules/private/hook.go b/modules/private/hook.go index 215996b9b9..ce87ccd801 100644 --- a/modules/private/hook.go +++ b/modules/private/hook.go @@ -37,7 +37,7 @@ type HookOptions struct { PushTrigger repository.PushTrigger DeployKeyID int64 // if the pusher is a DeployKey, then UserID is the repo's org user. IsWiki bool - ActionPerm int + ActionsTaskID int64 // if the pusher is an Actions user, the task ID } // SSHLogOption ssh log options diff --git a/modules/public/manifest.go b/modules/public/manifest.go new file mode 100644 index 0000000000..a07cabd6cf --- /dev/null +++ b/modules/public/manifest.go @@ -0,0 +1,162 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package public + +import ( + "io" + "path" + "sync" + "sync/atomic" + "time" + + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +type manifestEntry struct { + File string `json:"file"` + Name string `json:"name"` + IsEntry bool `json:"isEntry"` + CSS []string `json:"css"` +} + +type manifestDataStruct struct { + paths map[string]string // unhashed path -> hashed path + names map[string]string // hashed path -> entry name + modTime int64 + checkTime time.Time +} + +var ( + manifestData atomic.Pointer[manifestDataStruct] + manifestFS = sync.OnceValue(AssetFS) +) + +const manifestPath = "assets/.vite/manifest.json" + +func parseManifest(data []byte) (map[string]string, map[string]string) { + var manifest map[string]manifestEntry + if err := json.Unmarshal(data, &manifest); err != nil { + log.Error("Failed to parse frontend manifest: %v", err) + return nil, nil + } + + paths := make(map[string]string) + names := make(map[string]string) + for _, entry := range manifest { + if !entry.IsEntry || entry.Name == "" { + continue + } + // Build unhashed key from file path: "js/index.js", "css/theme-gitea-dark.css" + dir := path.Dir(entry.File) + ext := path.Ext(entry.File) + key := dir + "/" + entry.Name + ext + paths[key] = entry.File + names[entry.File] = entry.Name + // Map associated CSS files, e.g. "css/index.css" -> "css/index.B3zrQPqD.css" + for _, css := range entry.CSS { + cssKey := path.Dir(css) + "/" + entry.Name + path.Ext(css) + paths[cssKey] = css + names[css] = entry.Name + } + } + return paths, names +} + +func reloadManifest(existingData *manifestDataStruct) *manifestDataStruct { + now := time.Now() + data := existingData + if data != nil && now.Sub(data.checkTime) < time.Second { + // a single request triggers multiple calls to getHashedPath + // do not check the manifest file too frequently + return data + } + + f, err := manifestFS().Open(manifestPath) + if err != nil { + log.Error("Failed to open frontend manifest: %v", err) + return data + } + defer f.Close() + + fi, err := f.Stat() + if err != nil { + log.Error("Failed to stat frontend manifest: %v", err) + return data + } + + needReload := data == nil || fi.ModTime().UnixNano() != data.modTime + if !needReload { + return data + } + manifestContent, err := io.ReadAll(f) + if err != nil { + log.Error("Failed to read frontend manifest: %v", err) + return data + } + return storeManifestFromBytes(manifestContent, fi.ModTime().UnixNano(), now) +} + +func storeManifestFromBytes(manifestContent []byte, modTime int64, checkTime time.Time) *manifestDataStruct { + paths, names := parseManifest(manifestContent) + data := &manifestDataStruct{ + paths: paths, + names: names, + modTime: modTime, + checkTime: checkTime, + } + manifestData.Store(data) + return data +} + +func getManifestData() *manifestDataStruct { + data := manifestData.Load() + + // In production the manifest is immutable (embedded in the binary). + // In dev mode, check if it changed on disk (for watch-frontend). + if data == nil || !setting.IsProd { + data = reloadManifest(data) + } + if data == nil { + data = &manifestDataStruct{} + } + return data +} + +// AssetURI returns the URI for a frontend asset. +// It may return a relative path or a full URL depending on the StaticURLPrefix setting. +// In Vite dev mode, known entry points are mapped to their source paths +// so the reverse proxy serves them from the Vite dev server. +// In production, it resolves the content-hashed path from the manifest. +func AssetURI(originPath string) string { + if IsViteDevMode() { + if src := viteDevSourceURL(originPath); src != "" { + return src + } + // it should be caused by incorrect vite config + setting.PanicInDevOrTesting("Failed to locate local path for managed asset URI: %s", originPath) + } + + // Try to resolve an unhashed asset path (origin path) to its content-hashed path from the frontend manifest. + // Example: "js/index.js" -> "js/index.C6Z2MRVQ.js" + data := getManifestData() + assetPath := data.paths[originPath] + if assetPath == "" { + // it should be caused by either: "incorrect vite config" or "user's custom theme" + assetPath = originPath + if !setting.IsProd { + log.Warn("Failed to find managed asset URI for origin path: %s", originPath) + } + } + + return setting.StaticURLPrefix + "/assets/" + assetPath +} + +// AssetNameFromHashedPath returns the asset entry name for a given hashed asset path. +// Example: returns "theme-gitea-dark" for "css/theme-gitea-dark.CyAaQnn5.css". +// Returns empty string if the path is not found in the manifest. +func AssetNameFromHashedPath(hashedPath string) string { + return getManifestData().names[hashedPath] +} diff --git a/modules/public/manifest_test.go b/modules/public/manifest_test.go new file mode 100644 index 0000000000..acfeaa6dbe --- /dev/null +++ b/modules/public/manifest_test.go @@ -0,0 +1,80 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package public + +import ( + "testing" + "time" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" +) + +func TestViteManifest(t *testing.T) { + defer test.MockVariableValue(&setting.IsProd, true)() + + const testManifest = `{ + "web_src/js/index.ts": { + "file": "js/index.C6Z2MRVQ.js", + "name": "index", + "src": "web_src/js/index.ts", + "isEntry": true, + "css": ["css/index.B3zrQPqD.css"] + }, + "web_src/css/themes/theme-gitea-dark.css": { + "file": "css/theme-gitea-dark.CyAaQnn5.css", + "name": "theme-gitea-dark", + "src": "web_src/css/themes/theme-gitea-dark.css", + "isEntry": true + }, + "web_src/js/features/eventsource.sharedworker.ts": { + "file": "js/eventsource.sharedworker.Dug1twio.js", + "name": "eventsource.sharedworker", + "src": "web_src/js/features/eventsource.sharedworker.ts", + "isEntry": true + }, + "_chunk.js": { + "file": "js/chunk.abc123.js", + "name": "chunk" + } +}` + + t.Run("EmptyManifest", func(t *testing.T) { + storeManifestFromBytes([]byte(``), 0, time.Now()) + assert.Equal(t, "/assets/js/index.js", AssetURI("js/index.js")) + assert.Equal(t, "/assets/css/theme-gitea-dark.css", AssetURI("css/theme-gitea-dark.css")) + assert.Equal(t, "", AssetNameFromHashedPath("css/no-such-file.css")) + }) + + t.Run("ParseManifest", func(t *testing.T) { + storeManifestFromBytes([]byte(testManifest), 0, time.Now()) + paths, names := manifestData.Load().paths, manifestData.Load().names + + // JS entries + assert.Equal(t, "js/index.C6Z2MRVQ.js", paths["js/index.js"]) + assert.Equal(t, "js/eventsource.sharedworker.Dug1twio.js", paths["js/eventsource.sharedworker.js"]) + + // Associated CSS from JS entries + assert.Equal(t, "css/index.B3zrQPqD.css", paths["css/index.css"]) + + // CSS-only entries + assert.Equal(t, "css/theme-gitea-dark.CyAaQnn5.css", paths["css/theme-gitea-dark.css"]) + + // Non-entry chunks should not be included + assert.Empty(t, paths["js/chunk.js"]) + + // Names: hashed path -> entry name + assert.Equal(t, "index", names["js/index.C6Z2MRVQ.js"]) + assert.Equal(t, "index", names["css/index.B3zrQPqD.css"]) + assert.Equal(t, "theme-gitea-dark", names["css/theme-gitea-dark.CyAaQnn5.css"]) + assert.Equal(t, "eventsource.sharedworker", names["js/eventsource.sharedworker.Dug1twio.js"]) + + // Test Asset related functions + assert.Equal(t, "/assets/js/index.C6Z2MRVQ.js", AssetURI("js/index.js")) + assert.Equal(t, "/assets/css/theme-gitea-dark.CyAaQnn5.css", AssetURI("css/theme-gitea-dark.css")) + assert.Equal(t, "theme-gitea-dark", AssetNameFromHashedPath("css/theme-gitea-dark.CyAaQnn5.css")) + }) +} diff --git a/modules/public/mime_types.go b/modules/public/mime_types.go index 32bdf3bfa2..fa4691c6a9 100644 --- a/modules/public/mime_types.go +++ b/modules/public/mime_types.go @@ -3,38 +3,48 @@ package public -import "strings" +import ( + "mime" + "strings" + "sync" +) -// wellKnownMimeTypesLower comes from Golang's builtin mime package: `builtinTypesLower`, see the comment of detectWellKnownMimeType -var wellKnownMimeTypesLower = map[string]string{ - ".avif": "image/avif", - ".css": "text/css; charset=utf-8", - ".gif": "image/gif", - ".htm": "text/html; charset=utf-8", - ".html": "text/html; charset=utf-8", - ".jpeg": "image/jpeg", - ".jpg": "image/jpeg", - ".js": "text/javascript; charset=utf-8", - ".json": "application/json", - ".mjs": "text/javascript; charset=utf-8", - ".pdf": "application/pdf", - ".png": "image/png", - ".svg": "image/svg+xml", - ".wasm": "application/wasm", - ".webp": "image/webp", - ".xml": "text/xml; charset=utf-8", +// wellKnownMimeTypesLower comes from Golang's builtin mime package: `builtinTypesLower`, +// see the comment of DetectWellKnownMimeType +var wellKnownMimeTypesLower = sync.OnceValue(func() map[string]string { + return map[string]string{ + ".avif": "image/avif", + ".css": "text/css; charset=utf-8", + ".gif": "image/gif", + ".htm": "text/html; charset=utf-8", + ".html": "text/html; charset=utf-8", + ".jpeg": "image/jpeg", + ".jpg": "image/jpeg", + ".js": "text/javascript; charset=utf-8", + ".json": "application/json", + ".mjs": "text/javascript; charset=utf-8", + ".pdf": "application/pdf", + ".png": "image/png", + ".svg": "image/svg+xml", + ".wasm": "application/wasm", + ".webp": "image/webp", + ".xml": "text/xml; charset=utf-8", - // well, there are some types missing from the builtin list - ".txt": "text/plain; charset=utf-8", -} + // well, there are some types missing from the builtin list + ".txt": "text/plain; charset=utf-8", + } +}) -// detectWellKnownMimeType will return the mime-type for a well-known file ext name +// DetectWellKnownMimeType will return the mime-type for a well-known file ext name // The purpose of this function is to bypass the unstable behavior of Golang's mime.TypeByExtension // mime.TypeByExtension would use OS's mime-type config to overwrite the well-known types (see its document). // If the user's OS has incorrect mime-type config, it would make Gitea can not respond a correct Content-Type to browsers. // For example, if Gitea returns `text/plain` for a `.js` file, the browser couldn't run the JS due to security reasons. -// detectWellKnownMimeType makes the Content-Type for well-known files stable. -func detectWellKnownMimeType(ext string) string { +// DetectWellKnownMimeType makes the Content-Type for well-known files stable. +func DetectWellKnownMimeType(ext string) string { ext = strings.ToLower(ext) - return wellKnownMimeTypesLower[ext] + if s, ok := wellKnownMimeTypesLower()[ext]; ok { + return s + } + return mime.TypeByExtension(ext) } diff --git a/modules/public/public.go b/modules/public/public.go index 3a5a76637e..bb4721a48d 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -18,6 +18,8 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + + "github.com/go-chi/cors" ) func CustomAssets() *assetfs.Layer { @@ -28,6 +30,15 @@ func AssetFS() *assetfs.LayeredFS { return assetfs.Layered(CustomAssets(), BuiltinAssets()) } +func AssetsCors() func(next http.Handler) http.Handler { + // static assets need to be served for external renders (sandboxed) + return cors.Handler(cors.Options{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"HEAD", "GET"}, + MaxAge: 3600 * 24, + }) +} + // FileHandlerFunc implements the static handler for serving files in "public" assets func FileHandlerFunc() http.HandlerFunc { assetFS := AssetFS() @@ -51,9 +62,9 @@ func parseAcceptEncoding(val string) container.Set[string] { } // setWellKnownContentType will set the Content-Type if the file is a well-known type. -// See the comments of detectWellKnownMimeType +// See the comments of DetectWellKnownMimeType func setWellKnownContentType(w http.ResponseWriter, file string) { - mimeType := detectWellKnownMimeType(path.Ext(file)) + mimeType := DetectWellKnownMimeType(path.Ext(file)) if mimeType != "" { w.Header().Set("Content-Type", mimeType) } diff --git a/modules/public/vitedev.go b/modules/public/vitedev.go new file mode 100644 index 0000000000..7cfe692390 --- /dev/null +++ b/modules/public/vitedev.go @@ -0,0 +1,204 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package public + +import ( + "net/http" + "net/http/httputil" + "net/url" + "os" + "path/filepath" + "strings" + "sync/atomic" + "time" + + "code.gitea.io/gitea/modules/httplib" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web/routing" +) + +const viteDevPortFile = "public/assets/.vite/dev-port" + +var viteDevProxy atomic.Pointer[httputil.ReverseProxy] + +func getViteDevServerBaseURL() string { + portFile := filepath.Join(setting.StaticRootPath, viteDevPortFile) + portContent, _ := os.ReadFile(portFile) + port := strings.TrimSpace(string(portContent)) + if port == "" { + return "" + } + return "http://localhost:" + port +} + +func getViteDevProxy() *httputil.ReverseProxy { + if proxy := viteDevProxy.Load(); proxy != nil { + return proxy + } + + viteDevServerBaseURL := getViteDevServerBaseURL() + if viteDevServerBaseURL == "" { + return nil + } + + target, err := url.Parse(viteDevServerBaseURL) + if err != nil { + log.Error("Failed to parse Vite dev server base URL %s, err: %v", viteDevServerBaseURL, err) + return nil + } + + // there is a strange error log (from Golang's HTTP package) + // 2026/03/28 19:50:13 modules/log/misc.go:72:(*loggerToWriter).Write() [I] Unsolicited response received on idle HTTP channel starting with "HTTP/1.1 400 Bad Request\r\n\r\n"; err= + // maybe it is caused by that the Vite dev server doesn't support keep-alive connections? or different keep-alive timeouts? + transport := &http.Transport{ + IdleConnTimeout: 5 * time.Second, + ResponseHeaderTimeout: 5 * time.Second, + } + log.Info("Proxying Vite dev server requests to %s", target) + proxy := &httputil.ReverseProxy{ + Transport: transport, + Rewrite: func(r *httputil.ProxyRequest) { + r.SetURL(target) + r.Out.Host = target.Host + }, + ModifyResponse: func(resp *http.Response) error { + // add a header to indicate the Vite dev server port, + // make developers know that this request is proxied to Vite dev server and which port it is + resp.Header.Add("X-Gitea-Vite-Dev-Server", viteDevServerBaseURL) + return nil + }, + ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { + if r.Context().Err() != nil { + return // request cancelled (e.g. client disconnected), silently ignore + } + log.Error("Error proxying to Vite dev server: %v", err) + http.Error(w, "Error proxying to Vite dev server: "+err.Error(), http.StatusBadGateway) + }, + } + viteDevProxy.Store(proxy) + return proxy +} + +// ViteDevMiddleware proxies matching requests to the Vite dev server. +// It is registered as middleware in non-production mode and lazily discovers +// the Vite dev server port from the port file written by the viteDevServerPortPlugin. +// It is needed because there are container-based development, only Gitea web server's port is exposed. +func ViteDevMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + if !isViteDevRequest(req) { + next.ServeHTTP(resp, req) + return + } + proxy := getViteDevProxy() + if proxy == nil { + next.ServeHTTP(resp, req) + return + } + routing.MarkLongPolling(resp, req) + proxy.ServeHTTP(resp, req) + }) +} + +var viteDevModeCheck atomic.Pointer[struct { + isDev bool + time time.Time +}] + +// IsViteDevMode returns true if the Vite dev server port file exists and the server is alive +func IsViteDevMode() bool { + if setting.IsProd { + return false + } + + now := time.Now() + lastCheck := viteDevModeCheck.Load() + if lastCheck != nil && time.Now().Sub(lastCheck.time) < time.Second { + return lastCheck.isDev + } + + viteDevServerBaseURL := getViteDevServerBaseURL() + if viteDevServerBaseURL == "" { + return false + } + + req := httplib.NewRequest(viteDevServerBaseURL+"/web_src/js/__vite_dev_server_check", "GET") + resp, _ := req.Response() + if resp != nil { + _ = resp.Body.Close() + } + isDev := resp != nil && resp.StatusCode == http.StatusOK + viteDevModeCheck.Store(&struct { + isDev bool + time time.Time + }{ + isDev: isDev, + time: now, + }) + return isDev +} + +func detectWebSrcPath(webSrcPath string) string { + localPath := util.FilePathJoinAbs(setting.StaticRootPath, "web_src", webSrcPath) + if _, err := os.Stat(localPath); err == nil { + return setting.AppSubURL + "/web_src/" + webSrcPath + } + return "" +} + +func viteDevSourceURL(name string) string { + if strings.HasPrefix(name, "css/theme-") { + // Only redirect built-in themes to Vite source; custom themes are served from custom/public/assets/css/ + themeFilePath := "css/themes/" + strings.TrimPrefix(name, "css/") + if srcPath := detectWebSrcPath(themeFilePath); srcPath != "" { + return srcPath + } + } + // try to map ".js" files to ".ts" files + pathPrefix, ok := strings.CutSuffix(name, ".js") + if ok { + if srcPath := detectWebSrcPath(pathPrefix + ".ts"); srcPath != "" { + return srcPath + } + } + // for all others that the names match + return detectWebSrcPath(name) +} + +// isViteDevRequest returns true if the request should be proxied to the Vite dev server. +// Ref: Vite source packages/vite/src/node/constants.ts and packages/vite/src/shared/constants.ts +func isViteDevRequest(req *http.Request) bool { + if req.Header.Get("Upgrade") == "websocket" { + wsProtocol := req.Header.Get("Sec-WebSocket-Protocol") + return wsProtocol == "vite-hmr" || wsProtocol == "vite-ping" + } + path := req.URL.Path + + // vite internal requests + if strings.HasPrefix(path, "/@vite/") /* HMR client */ || + strings.HasPrefix(path, "/@fs/") /* out-of-root file access, see vite.config.ts: fs.allow */ || + strings.HasPrefix(path, "/@id/") /* virtual modules */ { + return true + } + + // local source requests (VITE-DEV-SERVER-SECURITY: don't serve sensitive files outside the allowed paths) + if strings.HasPrefix(path, "/node_modules/") || + strings.HasPrefix(path, "/public/assets/") || + strings.HasPrefix(path, "/web_src/") { + return true + } + + // Vite uses a path relative to project root and adds "?import" to non-JS/CSS asset imports: + // - {WebSite}/public/assets/... (e.g. SVG icons from "{RepoRoot}/public/assets/img/svg/") + // - {WebSite}/assets/emoji.json: it is an exception for the frontend assets, it is imported by JS code, but: + // - KEEP IN MIND: all static frontend assets are served from "{AssetFS}/assets" to "{WebSite}/assets" by Gitea Web Server + // - "{AssetFS}" is a layered filesystem from "{RepoRoot}/public" or embedded assets, and user's custom files in "{CustomPath}/public" + // - "{RepoRoot}/assets/emoji.json" just happens to have the dir name "assets", it is not related to frontend assets + // - BAD DESIGN: indeed it is a "conflicted and polluted name" sample + if path == "/assets/emoji.json" { + return true + } + return false +} diff --git a/modules/recaptcha/recaptcha.go b/modules/recaptcha/recaptcha.go index 1777d169c1..224fa38eee 100644 --- a/modules/recaptcha/recaptcha.go +++ b/modules/recaptcha/recaptcha.go @@ -13,7 +13,6 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" ) // Response is the structure of JSON returned from API @@ -24,17 +23,16 @@ type Response struct { ErrorCodes []ErrorCode `json:"error-codes"` } -const apiURL = "api/siteverify" - // Verify calls Google Recaptcha API to verify token func Verify(ctx context.Context, response string) (bool, error) { post := url.Values{ "secret": {setting.Service.RecaptchaSecret}, "response": {response}, } + + reqURL := strings.TrimSuffix(setting.Service.RecaptchaURL, "/") + "/api/siteverify" // Basically a copy of http.PostForm, but with a context - req, err := http.NewRequestWithContext(ctx, http.MethodPost, - util.URLJoin(setting.Service.RecaptchaURL, apiURL), strings.NewReader(post.Encode())) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqURL, strings.NewReader(post.Encode())) if err != nil { return false, fmt.Errorf("Failed to create CAPTCHA request: %w", err) } diff --git a/modules/repository/env.go b/modules/repository/env.go index 55a81f006e..ed2c6fef81 100644 --- a/modules/repository/env.go +++ b/modules/repository/env.go @@ -11,25 +11,26 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" ) // env keys for git hooks need const ( - EnvRepoName = "GITEA_REPO_NAME" - EnvRepoUsername = "GITEA_REPO_USER_NAME" - EnvRepoID = "GITEA_REPO_ID" - EnvRepoIsWiki = "GITEA_REPO_IS_WIKI" - EnvPusherName = "GITEA_PUSHER_NAME" - EnvPusherEmail = "GITEA_PUSHER_EMAIL" - EnvPusherID = "GITEA_PUSHER_ID" - EnvKeyID = "GITEA_KEY_ID" // public key ID - EnvDeployKeyID = "GITEA_DEPLOY_KEY_ID" - EnvPRID = "GITEA_PR_ID" - EnvPRIndex = "GITEA_PR_INDEX" // not used by Gitea at the moment, it is for custom git hooks - EnvPushTrigger = "GITEA_PUSH_TRIGGER" - EnvIsInternal = "GITEA_INTERNAL_PUSH" - EnvAppURL = "GITEA_ROOT_URL" - EnvActionPerm = "GITEA_ACTION_PERM" + EnvRepoName = "GITEA_REPO_NAME" + EnvRepoUsername = "GITEA_REPO_USER_NAME" + EnvRepoID = "GITEA_REPO_ID" + EnvRepoIsWiki = "GITEA_REPO_IS_WIKI" + EnvPusherName = "GITEA_PUSHER_NAME" + EnvPusherEmail = "GITEA_PUSHER_EMAIL" + EnvPusherID = "GITEA_PUSHER_ID" + EnvKeyID = "GITEA_KEY_ID" // public key ID + EnvDeployKeyID = "GITEA_DEPLOY_KEY_ID" + EnvPRID = "GITEA_PR_ID" + EnvPRIndex = "GITEA_PR_INDEX" // not used by Gitea at the moment, it is for custom git hooks + EnvPushTrigger = "GITEA_PUSH_TRIGGER" + EnvIsInternal = "GITEA_INTERNAL_PUSH" + EnvAppURL = "GITEA_ROOT_URL" + EnvActionsTaskID = "GITEA_ACTIONS_TASK_ID" ) type PushTrigger string @@ -54,36 +55,39 @@ func PushingEnvironment(doer *user_model.User, repo *repo_model.Repository) []st return FullPushingEnvironment(doer, doer, repo, repo.Name, 0, 0) } +func DoerPushingEnvironment(doer *user_model.User, repo *repo_model.Repository, isWiki bool) []string { + env := []string{ + EnvAppURL + "=" + setting.AppURL, + EnvRepoName + "=" + repo.Name + util.Iif(isWiki, ".wiki", ""), + EnvRepoUsername + "=" + repo.OwnerName, + EnvRepoID + "=" + strconv.FormatInt(repo.ID, 10), + EnvRepoIsWiki + "=" + strconv.FormatBool(isWiki), + EnvPusherName + "=" + doer.Name, + EnvPusherID + "=" + strconv.FormatInt(doer.ID, 10), + } + if !doer.KeepEmailPrivate { + env = append(env, EnvPusherEmail+"="+doer.Email) + } + if taskID, isActionsUser := user_model.GetActionsUserTaskID(doer); isActionsUser { + env = append(env, EnvActionsTaskID+"="+strconv.FormatInt(taskID, 10)) + } + return env +} + // FullPushingEnvironment returns an os environment to allow hooks to work on push func FullPushingEnvironment(author, committer *user_model.User, repo *repo_model.Repository, repoName string, prID, prIndex int64) []string { - isWiki := "false" - if strings.HasSuffix(repoName, ".wiki") { - isWiki = "true" - } - + isWiki := strings.HasSuffix(repoName, ".wiki") authorSig := author.NewGitSig() committerSig := committer.NewGitSig() - environ := append(os.Environ(), "GIT_AUTHOR_NAME="+authorSig.Name, "GIT_AUTHOR_EMAIL="+authorSig.Email, "GIT_COMMITTER_NAME="+committerSig.Name, "GIT_COMMITTER_EMAIL="+committerSig.Email, - EnvRepoName+"="+repoName, - EnvRepoUsername+"="+repo.OwnerName, - EnvRepoIsWiki+"="+isWiki, - EnvPusherName+"="+committer.Name, - EnvPusherID+"="+strconv.FormatInt(committer.ID, 10), - EnvRepoID+"="+strconv.FormatInt(repo.ID, 10), EnvPRID+"="+strconv.FormatInt(prID, 10), EnvPRIndex+"="+strconv.FormatInt(prIndex, 10), - EnvAppURL+"="+setting.AppURL, "SSH_ORIGINAL_COMMAND=gitea-internal", ) - - if !committer.KeepEmailPrivate { - environ = append(environ, EnvPusherEmail+"="+committer.Email) - } - + environ = append(environ, DoerPushingEnvironment(committer, repo, isWiki)...) return environ } diff --git a/modules/setting/config.go b/modules/setting/config.go index fb99325a95..a41734a843 100644 --- a/modules/setting/config.go +++ b/modules/setting/config.go @@ -12,8 +12,8 @@ import ( ) type PictureStruct struct { - DisableGravatar *config.Value[bool] - EnableFederatedAvatar *config.Value[bool] + DisableGravatar *config.Option[bool] + EnableFederatedAvatar *config.Option[bool] } type OpenWithEditorApp struct { @@ -23,6 +23,9 @@ type OpenWithEditorApp struct { type OpenWithEditorAppsType []OpenWithEditorApp +// ToTextareaString is only used in templates, for help prompt only +// TODO: OPEN-WITH-EDITOR-APP-JSON: Because there is no "rich UI", a plain text editor is used to manage the list of apps +// Maybe we can use some better formats like Yaml in the future, then a simple textarea can manage the config clearly func (t OpenWithEditorAppsType) ToTextareaString() string { var ret strings.Builder for _, app := range t { @@ -31,7 +34,7 @@ func (t OpenWithEditorAppsType) ToTextareaString() string { return ret.String() } -func DefaultOpenWithEditorApps() OpenWithEditorAppsType { +func openWithEditorAppsDefaultValue() OpenWithEditorAppsType { return OpenWithEditorAppsType{ { DisplayName: "VS Code", @@ -49,13 +52,14 @@ func DefaultOpenWithEditorApps() OpenWithEditorAppsType { } type RepositoryStruct struct { - OpenWithEditorApps *config.Value[OpenWithEditorAppsType] - GitGuideRemoteName *config.Value[string] + OpenWithEditorApps *config.Option[OpenWithEditorAppsType] + GitGuideRemoteName *config.Option[string] } type ConfigStruct struct { Picture *PictureStruct Repository *RepositoryStruct + Instance *InstanceStruct } var ( @@ -67,12 +71,16 @@ func initDefaultConfig() { config.SetCfgSecKeyGetter(&cfgSecKeyGetter{}) defaultConfig = &ConfigStruct{ Picture: &PictureStruct{ - DisableGravatar: config.ValueJSON[bool]("picture.disable_gravatar").WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "DISABLE_GRAVATAR"}), - EnableFederatedAvatar: config.ValueJSON[bool]("picture.enable_federated_avatar").WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "ENABLE_FEDERATED_AVATAR"}), + DisableGravatar: config.NewOption[bool]("picture.disable_gravatar").WithDefaultSimple(true).WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "DISABLE_GRAVATAR"}), + EnableFederatedAvatar: config.NewOption[bool]("picture.enable_federated_avatar").WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "ENABLE_FEDERATED_AVATAR"}), }, Repository: &RepositoryStruct{ - OpenWithEditorApps: config.ValueJSON[OpenWithEditorAppsType]("repository.open-with.editor-apps"), - GitGuideRemoteName: config.ValueJSON[string]("repository.git-guide-remote-name").WithDefault("origin"), + OpenWithEditorApps: config.NewOption[OpenWithEditorAppsType]("repository.open-with.editor-apps").WithEmptyAsDefault().WithDefaultFunc(openWithEditorAppsDefaultValue), + GitGuideRemoteName: config.NewOption[string]("repository.git-guide-remote-name").WithEmptyAsDefault().WithDefaultSimple("origin"), + }, + Instance: &InstanceStruct{ + WebBanner: config.NewOption[WebBannerType]("instance.web_banner"), + MaintenanceMode: config.NewOption[MaintenanceModeType]("instance.maintenance_mode"), }, } } diff --git a/modules/setting/config/value.go b/modules/setting/config/value.go index 301c60f5e8..c01f41303c 100644 --- a/modules/setting/config/value.go +++ b/modules/setting/config/value.go @@ -5,6 +5,7 @@ package config import ( "context" + "reflect" "sync" "code.gitea.io/gitea/modules/json" @@ -16,18 +17,31 @@ type CfgSecKey struct { Sec, Key string } -type Value[T any] struct { +// OptionInterface is used to overcome Golang's generic interface limitation +type OptionInterface interface { + GetDefaultValue() any +} + +type Option[T any] struct { mu sync.RWMutex cfgSecKey CfgSecKey dynKey string - def, value T + value T + defSimple T + defFunc func() T + emptyAsDef bool + has bool revision int } -func (value *Value[T]) parse(key, valStr string) (v T) { - v = value.def +func (opt *Option[T]) GetDefaultValue() any { + return opt.DefaultValue() +} + +func (opt *Option[T]) parse(key, valStr string) (v T) { + v = opt.DefaultValue() if valStr != "" { if err := json.Unmarshal(util.UnsafeStringToBytes(valStr), &v); err != nil { log.Error("Unable to unmarshal json config for key %q, err: %v", key, err) @@ -36,7 +50,35 @@ func (value *Value[T]) parse(key, valStr string) (v T) { return v } -func (value *Value[T]) Value(ctx context.Context) (v T) { +func (opt *Option[T]) HasValue(ctx context.Context) bool { + _, _, has := opt.ValueRevision(ctx) + return has +} + +func (opt *Option[T]) Value(ctx context.Context) (v T) { + v, _, _ = opt.ValueRevision(ctx) + return v +} + +func isZeroOrEmpty(v any) bool { + if v == nil { + return true // interface itself is nil + } + r := reflect.ValueOf(v) + if r.IsZero() { + return true + } + + if r.Kind() == reflect.Slice || r.Kind() == reflect.Map { + if r.IsNil() { + return true + } + return r.Len() == 0 + } + return false +} + +func (opt *Option[T]) ValueRevision(ctx context.Context) (v T, rev int, has bool) { dg := GetDynGetter() if dg == nil { // this is an edge case: the database is not initialized but the system setting is going to be used @@ -44,55 +86,96 @@ func (value *Value[T]) Value(ctx context.Context) (v T) { panic("no config dyn value getter") } - rev := dg.GetRevision(ctx) + rev = dg.GetRevision(ctx) // if the revision in the database doesn't change, use the last value - value.mu.RLock() - if rev == value.revision { - v = value.value - value.mu.RUnlock() - return v + opt.mu.RLock() + if rev == opt.revision { + v = opt.value + has = opt.has + opt.mu.RUnlock() + return v, rev, has } - value.mu.RUnlock() + opt.mu.RUnlock() // try to parse the config and cache it var valStr *string - if dynVal, has := dg.GetValue(ctx, value.dynKey); has { + if dynVal, hasDbValue := dg.GetValue(ctx, opt.dynKey); hasDbValue { valStr = &dynVal - } else if cfgVal, has := GetCfgSecKeyGetter().GetValue(value.cfgSecKey.Sec, value.cfgSecKey.Key); has { + } else if cfgVal, hasCfgValue := GetCfgSecKeyGetter().GetValue(opt.cfgSecKey.Sec, opt.cfgSecKey.Key); hasCfgValue { valStr = &cfgVal } if valStr == nil { - v = value.def + v = opt.DefaultValue() + has = false } else { - v = value.parse(value.dynKey, *valStr) + v = opt.parse(opt.dynKey, *valStr) + if opt.emptyAsDef && isZeroOrEmpty(v) { + v = opt.DefaultValue() + } else { + has = true + } } - value.mu.Lock() - value.value = v - value.revision = rev - value.mu.Unlock() + opt.mu.Lock() + opt.value = v + opt.revision = rev + opt.has = has + opt.mu.Unlock() + return v, rev, has +} + +func (opt *Option[T]) DynKey() string { + return opt.dynKey +} + +// WithDefaultFunc sets the default value with a function +// The "def" value might be changed during runtime (e.g.: Unmarshal with default), so it shouldn't use the same pointer or slice +func (opt *Option[T]) WithDefaultFunc(f func() T) *Option[T] { + opt.defFunc = f + return opt +} + +func (opt *Option[T]) WithDefaultSimple(def T) *Option[T] { + v := any(def) + switch v.(type) { + case string, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + default: + // TODO: use reflect to support convertible basic types like `type State string` + r := reflect.ValueOf(v) + if r.Kind() != reflect.Struct { + panic("invalid type for default value, use WithDefaultFunc instead") + } + } + opt.defSimple = def + return opt +} + +func (opt *Option[T]) WithEmptyAsDefault() *Option[T] { + opt.emptyAsDef = true + return opt +} + +func (opt *Option[T]) DefaultValue() T { + if opt.defFunc != nil { + return opt.defFunc() + } + return opt.defSimple +} + +func (opt *Option[T]) WithFileConfig(cfgSecKey CfgSecKey) *Option[T] { + opt.cfgSecKey = cfgSecKey + return opt +} + +var allConfigOptions = map[string]OptionInterface{} + +func NewOption[T any](dynKey string) *Option[T] { + v := &Option[T]{dynKey: dynKey} + allConfigOptions[dynKey] = v return v } -func (value *Value[T]) DynKey() string { - return value.dynKey -} - -func (value *Value[T]) WithDefault(def T) *Value[T] { - value.def = def - return value -} - -func (value *Value[T]) DefaultValue() T { - return value.def -} - -func (value *Value[T]) WithFileConfig(cfgSecKey CfgSecKey) *Value[T] { - value.cfgSecKey = cfgSecKey - return value -} - -func ValueJSON[T any](dynKey string) *Value[T] { - return &Value[T]{dynKey: dynKey} +func GetConfigOption(dynKey string) OptionInterface { + return allConfigOptions[dynKey] } diff --git a/modules/setting/config_option_instance.go b/modules/setting/config_option_instance.go new file mode 100644 index 0000000000..6d97055a75 --- /dev/null +++ b/modules/setting/config_option_instance.go @@ -0,0 +1,58 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "time" + + "code.gitea.io/gitea/modules/setting/config" +) + +// WebBannerType fields are directly used in templates, +// do remember to update the template if you change the fields +type WebBannerType struct { + DisplayEnabled bool + ContentMessage string + StartTimeUnix int64 + EndTimeUnix int64 +} + +func (b WebBannerType) ShouldDisplay() bool { + if !b.DisplayEnabled || b.ContentMessage == "" { + return false + } + now := time.Now().Unix() + if b.StartTimeUnix > 0 && now < b.StartTimeUnix { + return false + } + if b.EndTimeUnix > 0 && now > b.EndTimeUnix { + return false + } + return true +} + +type MaintenanceModeType struct { + AdminWebAccessOnly bool + StartTimeUnix int64 + EndTimeUnix int64 +} + +func (m MaintenanceModeType) IsActive() bool { + if !m.AdminWebAccessOnly { + return false + } + now := time.Now().Unix() + if m.StartTimeUnix > 0 && now < m.StartTimeUnix { + return false + } + if m.EndTimeUnix > 0 && now > m.EndTimeUnix { + return false + } + return true +} + +type InstanceStruct struct { + WebBanner *config.Option[WebBannerType] + MaintenanceMode *config.Option[MaintenanceModeType] +} diff --git a/modules/setting/lfs.go b/modules/setting/lfs.go index 7f2d0ae159..3ec21860ae 100644 --- a/modules/setting/lfs.go +++ b/modules/setting/lfs.go @@ -81,10 +81,7 @@ func loadLFSFrom(rootCfg ConfigProvider) error { jwtSecretBase64 := loadSecret(rootCfg.Section("server"), "LFS_JWT_SECRET_URI", "LFS_JWT_SECRET") LFS.JWTSecretBytes, err = generate.DecodeJwtSecretBase64(jwtSecretBase64) if err != nil { - LFS.JWTSecretBytes, jwtSecretBase64, err = generate.NewJwtSecretWithBase64() - if err != nil { - return fmt.Errorf("error generating JWT Secret for custom config: %v", err) - } + LFS.JWTSecretBytes, jwtSecretBase64 = generate.NewJwtSecretWithBase64() // Save secret saveCfg, err := rootCfg.PrepareSaving() diff --git a/modules/setting/mailer_test.go b/modules/setting/mailer_test.go index f281715973..7e1c988568 100644 --- a/modules/setting/mailer_test.go +++ b/modules/setting/mailer_test.go @@ -6,6 +6,8 @@ package setting import ( "testing" + "code.gitea.io/gitea/modules/test" + "github.com/stretchr/testify/assert" ) @@ -39,3 +41,30 @@ func Test_loadMailerFrom(t *testing.T) { }) } } + +func TestLoadSettingsForInstallMailServiceFlags(t *testing.T) { + defer test.MockVariableValue(&Service)() + defer test.MockVariableValue(&MailService)() + + cfg, err := NewConfigProviderFromData(` +[database] +DB_TYPE = postgres + +[mailer] +ENABLED = true +SMTP_ADDR = 127.0.0.1 +SMTP_PORT = 465 +FROM = noreply@example.com + +[service] +REGISTER_EMAIL_CONFIRM = true +ENABLE_NOTIFY_MAIL = true +`) + assert.NoError(t, err) + loadDBSetting(cfg) + loadServiceFrom(cfg) + loadMailsFrom(cfg) + + assert.True(t, Service.RegisterEmailConfirm) + assert.True(t, Service.EnableNotifyMail) +} diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index 2dfe77dda9..8e0210aa51 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -139,10 +139,7 @@ func loadOAuth2From(rootCfg ConfigProvider) { if InstallLock { jwtSecretBytes, err := generate.DecodeJwtSecretBase64(jwtSecretBase64) if err != nil { - jwtSecretBytes, jwtSecretBase64, err = generate.NewJwtSecretWithBase64() - if err != nil { - log.Fatal("error generating JWT secret: %v", err) - } + jwtSecretBytes, jwtSecretBase64 = generate.NewJwtSecretWithBase64() saveCfg, err := rootCfg.PrepareSaving() if err != nil { log.Fatal("save oauth2.JWT_SECRET failed: %v", err) @@ -162,10 +159,7 @@ 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: %v", err) - } + jwtSecret, _ := generate.NewJwtSecretWithBase64() if generalSigningSecret.CompareAndSwap(old, &jwtSecret) { return jwtSecret } diff --git a/modules/setting/packages.go b/modules/setting/packages.go index b598424064..38ee2ad55e 100644 --- a/modules/setting/packages.go +++ b/modules/setting/packages.go @@ -16,30 +16,31 @@ var ( Storage *Storage Enabled bool - LimitTotalOwnerCount int64 - LimitTotalOwnerSize int64 - LimitSizeAlpine int64 - LimitSizeArch int64 - LimitSizeCargo int64 - LimitSizeChef int64 - LimitSizeComposer int64 - LimitSizeConan int64 - LimitSizeConda int64 - LimitSizeContainer int64 - LimitSizeCran int64 - LimitSizeDebian int64 - LimitSizeGeneric int64 - LimitSizeGo int64 - LimitSizeHelm int64 - LimitSizeMaven int64 - LimitSizeNpm int64 - LimitSizeNuGet int64 - LimitSizePub int64 - LimitSizePyPI int64 - LimitSizeRpm int64 - LimitSizeRubyGems int64 - LimitSizeSwift int64 - LimitSizeVagrant int64 + LimitTotalOwnerCount int64 + LimitTotalOwnerSize int64 + LimitSizeAlpine int64 + LimitSizeArch int64 + LimitSizeCargo int64 + LimitSizeChef int64 + LimitSizeComposer int64 + LimitSizeConan int64 + LimitSizeConda int64 + LimitSizeContainer int64 + LimitSizeCran int64 + LimitSizeDebian int64 + LimitSizeGeneric int64 + LimitSizeGo int64 + LimitSizeHelm int64 + LimitSizeMaven int64 + LimitSizeNpm int64 + LimitSizeNuGet int64 + LimitSizePub int64 + LimitSizePyPI int64 + LimitSizeRpm int64 + LimitSizeRubyGems int64 + LimitSizeSwift int64 + LimitSizeTerraformState int64 + LimitSizeVagrant int64 DefaultRPMSignEnabled bool }{ @@ -86,6 +87,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) { Packages.LimitSizeRpm = mustBytes(sec, "LIMIT_SIZE_RPM") Packages.LimitSizeRubyGems = mustBytes(sec, "LIMIT_SIZE_RUBYGEMS") Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT") + Packages.LimitSizeTerraformState = mustBytes(sec, "LIMIT_SIZE_TERRAFORM_STATE") Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT") Packages.DefaultRPMSignEnabled = sec.Key("DEFAULT_RPM_SIGN_ENABLED").MustBool(false) return nil diff --git a/modules/setting/picture.go b/modules/setting/picture.go index fafae45bab..d20a110b6c 100644 --- a/modules/setting/picture.go +++ b/modules/setting/picture.go @@ -22,9 +22,7 @@ var ( RenderedSizeFactor: 2, } - GravatarSource string - DisableGravatar bool // Depreciated: migrated to database - EnableFederatedAvatar bool // Depreciated: migrated to database + GravatarSource string RepoAvatar = struct { Storage *Storage @@ -65,29 +63,12 @@ func loadAvatarsFrom(rootCfg ConfigProvider) error { GravatarSource = source } - DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool(GetDefaultDisableGravatar()) - deprecatedSettingDB(rootCfg, "", "DISABLE_GRAVATAR") - EnableFederatedAvatar = sec.Key("ENABLE_FEDERATED_AVATAR").MustBool(GetDefaultEnableFederatedAvatar(DisableGravatar)) - deprecatedSettingDB(rootCfg, "", "ENABLE_FEDERATED_AVATAR") + deprecatedSettingDB(rootCfg, "picture", "DISABLE_GRAVATAR") + deprecatedSettingDB(rootCfg, "picture", "ENABLE_FEDERATED_AVATAR") return nil } -func GetDefaultDisableGravatar() bool { - return OfflineMode -} - -func GetDefaultEnableFederatedAvatar(disableGravatar bool) bool { - v := !InstallLock - if OfflineMode { - v = false - } - if disableGravatar { - v = false - } - return v -} - func loadRepoAvatarFrom(rootCfg ConfigProvider) error { sec := rootCfg.Section("picture") diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 662e03598b..9195b7ee50 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -86,9 +86,9 @@ var ( DefaultMergeMessageOfficialApproversOnly bool PopulateSquashCommentWithCommitMessages bool AddCoCommitterTrailers bool - TestConflictingPatchesWithGitApply bool RetargetChildrenOnMerge bool DelayCheckForInactiveDays int + DefaultDeleteBranchAfterMerge bool } `ini:"repository.pull-request"` // Issue Setting @@ -210,9 +210,9 @@ var ( DefaultMergeMessageOfficialApproversOnly bool PopulateSquashCommentWithCommitMessages bool AddCoCommitterTrailers bool - TestConflictingPatchesWithGitApply bool RetargetChildrenOnMerge bool DelayCheckForInactiveDays int + DefaultDeleteBranchAfterMerge bool }{ WorkInProgressPrefixes: []string{"WIP:", "[WIP]"}, // Same as GitHub. See diff --git a/modules/setting/security.go b/modules/setting/security.go index 743df61681..152bcffd9f 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -31,6 +31,7 @@ var ( ReverseProxyAuthEmail string ReverseProxyAuthFullName string ReverseProxyLimit int + ReverseProxyLogoutRedirect string ReverseProxyTrustedProxies []string MinPasswordLength int ImportLocalPaths bool @@ -109,7 +110,6 @@ func generateSaveInternalToken(rootCfg ConfigProvider) { func loadSecurityFrom(rootCfg ConfigProvider) { sec := rootCfg.Section("security") - InstallLock = HasInstallLock(rootCfg) LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(31) SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY") if SecretKey == "" { @@ -125,6 +125,7 @@ func loadSecurityFrom(rootCfg ConfigProvider) { ReverseProxyAuthFullName = sec.Key("REVERSE_PROXY_AUTHENTICATION_FULL_NAME").MustString("X-WEBAUTH-FULLNAME") ReverseProxyLimit = sec.Key("REVERSE_PROXY_LIMIT").MustInt(1) + ReverseProxyLogoutRedirect = sec.Key("REVERSE_PROXY_LOGOUT_REDIRECT").String() ReverseProxyTrustedProxies = sec.Key("REVERSE_PROXY_TRUSTED_PROXIES").Strings(",") if len(ReverseProxyTrustedProxies) == 0 { ReverseProxyTrustedProxies = []string{"127.0.0.0/8", "::1/128"} diff --git a/modules/setting/server.go b/modules/setting/server.go index a865e942a6..1085e052a3 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -15,7 +15,6 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" ) // Scheme describes protocol types @@ -44,6 +43,7 @@ const ( const ( PublicURLAuto = "auto" PublicURLLegacy = "legacy" + PublicURLNever = "never" ) // Server settings @@ -72,9 +72,6 @@ var ( // It maps to ini:"LOCAL_ROOT_URL" in [server] LocalURL string - // AssetVersion holds an opaque value that is used for cache-busting assets - AssetVersion string - // appTempPathInternal is the temporary path for the app, it is only an internal variable // DO NOT use it directly, always use AppDataTempDir appTempPathInternal string @@ -91,7 +88,6 @@ var ( RedirectOtherPort bool RedirectorUseProxyProtocol bool PortToRedirect string - OfflineMode bool CertFile string KeyFile string StaticRootPath string @@ -163,7 +159,7 @@ func MakeManifestData(appName, appURL, absoluteAssetURL string) []byte { } // MakeAbsoluteAssetURL returns the absolute asset url prefix without a trailing slash -func MakeAbsoluteAssetURL(appURL, staticURLPrefix string) string { +func MakeAbsoluteAssetURL(appURL *url.URL, staticURLPrefix string) string { parsedPrefix, err := url.Parse(strings.TrimSuffix(staticURLPrefix, "/")) if err != nil { log.Fatal("Unable to parse STATIC_URL_PREFIX: %v", err) @@ -171,11 +167,12 @@ func MakeAbsoluteAssetURL(appURL, staticURLPrefix string) string { if err == nil && parsedPrefix.Hostname() == "" { if staticURLPrefix == "" { - return strings.TrimSuffix(appURL, "/") + return strings.TrimSuffix(appURL.String(), "/") } // StaticURLPrefix is just a path - return util.URLJoin(appURL, strings.TrimSuffix(staticURLPrefix, "/")) + appHostURL := &url.URL{Scheme: appURL.Scheme, Host: appURL.Host} + return appHostURL.String() + "/" + strings.Trim(staticURLPrefix, "/") } return strings.TrimSuffix(staticURLPrefix, "/") @@ -286,8 +283,8 @@ func loadServerFrom(rootCfg ConfigProvider) { defaultAppURL := string(Protocol) + "://" + Domain + ":" + HTTPPort AppURL = sec.Key("ROOT_URL").MustString(defaultAppURL) - PublicURLDetection = sec.Key("PUBLIC_URL_DETECTION").MustString(PublicURLLegacy) - if PublicURLDetection != PublicURLAuto && PublicURLDetection != PublicURLLegacy { + PublicURLDetection = sec.Key("PUBLIC_URL_DETECTION").MustString(PublicURLAuto) + if PublicURLDetection != PublicURLAuto && PublicURLDetection != PublicURLLegacy && PublicURLDetection != PublicURLNever { log.Fatal("Invalid PUBLIC_URL_DETECTION value: %s", PublicURLDetection) } @@ -316,9 +313,7 @@ func loadServerFrom(rootCfg ConfigProvider) { Domain = urlHostname } - AbsoluteAssetURL = MakeAbsoluteAssetURL(AppURL, StaticURLPrefix) - AssetVersion = strings.ReplaceAll(AppVer, "+", "~") // make sure the version string is clear (no real escaping is needed) - + AbsoluteAssetURL = MakeAbsoluteAssetURL(appURL, StaticURLPrefix) manifestBytes := MakeManifestData(AppName, AppURL, AbsoluteAssetURL) ManifestData = `application/json;base64,` + base64.StdEncoding.EncodeToString(manifestBytes) @@ -346,7 +341,6 @@ func loadServerFrom(rootCfg ConfigProvider) { RedirectOtherPort = sec.Key("REDIRECT_OTHER_PORT").MustBool(false) PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80") RedirectorUseProxyProtocol = sec.Key("REDIRECTOR_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol) - OfflineMode = sec.Key("OFFLINE_MODE").MustBool(true) if len(StaticRootPath) == 0 { StaticRootPath = AppWorkPath } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index dc60d99bd6..2009be0bbd 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -28,8 +28,10 @@ var ( CfgProvider ConfigProvider IsWindows bool - // IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing - // TODO: this is only a temporary solution, we should make the test code more reliable + // IsInTesting indicates whether the testing is running (unit test or integration test). It can be used for: + // * Skip nonsense error logs during testing caused by unreliable code (TODO: this is only a temporary solution, we should make the test code more reliable) + // * Panic in dev or testing mode to make the problem more obvious and easier to debug + // * Mock some functions or options to make testing easier (eg: session store, time, URL detection, etc.) IsInTesting = false ) @@ -57,6 +59,10 @@ func IsRunUserMatchCurrentUser(runUser string) (string, bool) { return currentUser, runUser == currentUser } +func IsInE2eTesting() bool { + return os.Getenv("GITEA_TEST_E2E") == "true" +} + // PrepareAppDataPath creates app data directory if necessary func PrepareAppDataPath() error { // FIXME: There are too many calls to MkdirAll in old code. It is incorrect. @@ -108,6 +114,9 @@ func LoadCommonSettings() { // loadCommonSettingsFrom loads common configurations from a configuration provider. func loadCommonSettingsFrom(cfg ConfigProvider) error { + // a lot of logic depends on InstallLock value, so it must be loaded before any other settings + InstallLock = HasInstallLock(cfg) + // WARNING: don't change the sequence except you know what you are doing. loadRunModeFrom(cfg) loadLogGlobalFrom(cfg) @@ -223,7 +232,7 @@ func LoadSettings() { func LoadSettingsForInstall() { loadDBSetting(CfgProvider) loadServiceFrom(CfgProvider) - loadMailerFrom(CfgProvider) + loadMailsFrom(CfgProvider) } var configuredPaths = make(map[string]string) diff --git a/modules/setting/setting_test.go b/modules/setting/setting_test.go index f77ee65974..13575f52a6 100644 --- a/modules/setting/setting_test.go +++ b/modules/setting/setting_test.go @@ -4,6 +4,7 @@ package setting import ( + "net/url" "testing" "code.gitea.io/gitea/modules/json" @@ -12,18 +13,26 @@ import ( ) func TestMakeAbsoluteAssetURL(t *testing.T) { - assert.Equal(t, "https://localhost:2345", MakeAbsoluteAssetURL("https://localhost:1234", "https://localhost:2345")) - assert.Equal(t, "https://localhost:2345", MakeAbsoluteAssetURL("https://localhost:1234/", "https://localhost:2345")) - assert.Equal(t, "https://localhost:2345", MakeAbsoluteAssetURL("https://localhost:1234/", "https://localhost:2345/")) - assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL("https://localhost:1234", "/foo")) - assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL("https://localhost:1234/", "/foo")) - assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL("https://localhost:1234/", "/foo/")) - assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL("https://localhost:1234/foo", "/foo")) - assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL("https://localhost:1234/foo/", "/foo")) - assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL("https://localhost:1234/foo/", "/foo/")) - assert.Equal(t, "https://localhost:1234/bar", MakeAbsoluteAssetURL("https://localhost:1234/foo", "/bar")) - assert.Equal(t, "https://localhost:1234/bar", MakeAbsoluteAssetURL("https://localhost:1234/foo/", "/bar")) - assert.Equal(t, "https://localhost:1234/bar", MakeAbsoluteAssetURL("https://localhost:1234/foo/", "/bar/")) + appURL1, _ := url.Parse("https://localhost:1234") + appURL2, _ := url.Parse("https://localhost:1234/") + appURLSub1, _ := url.Parse("https://localhost:1234/foo") + appURLSub2, _ := url.Parse("https://localhost:1234/foo/") + + // static URL is an absolute URL, so should be used + assert.Equal(t, "https://localhost:2345", MakeAbsoluteAssetURL(appURL1, "https://localhost:2345")) + assert.Equal(t, "https://localhost:2345", MakeAbsoluteAssetURL(appURL1, "https://localhost:2345/")) + + assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL(appURL1, "/foo")) + assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL(appURL2, "/foo")) + assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL(appURL1, "/foo/")) + + assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL(appURLSub1, "/foo")) + assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL(appURLSub2, "/foo")) + assert.Equal(t, "https://localhost:1234/foo", MakeAbsoluteAssetURL(appURLSub1, "/foo/")) + + assert.Equal(t, "https://localhost:1234/bar", MakeAbsoluteAssetURL(appURLSub1, "/bar")) + assert.Equal(t, "https://localhost:1234/bar", MakeAbsoluteAssetURL(appURLSub2, "/bar")) + assert.Equal(t, "https://localhost:1234/bar", MakeAbsoluteAssetURL(appURLSub1, "/bar/")) } func TestMakeManifestData(t *testing.T) { diff --git a/modules/setting/ui.go b/modules/setting/ui.go index 77a5b45d0a..722341a71e 100644 --- a/modules/setting/ui.go +++ b/modules/setting/ui.go @@ -25,7 +25,6 @@ var UI = struct { ReactionMaxUserNum int MaxDisplayFileSize int64 ShowUserEmail bool - DefaultShowFullName bool DefaultTheme string Themes []string FileIconTheme string @@ -43,6 +42,15 @@ var UI = struct { AmbiguousUnicodeDetection bool + // TODO: DefaultShowFullName is introduced by https://github.com/go-gitea/gitea/pull/6710 + // But there are still many edge cases: + // * Many places still use "username", not respecting this setting + // * Many places use "Full Name" if it is not empty, cause inconsistent UI for users who have set their full name but some others don't + // * Even if DefaultShowFullName=false, many places still need to show the full name + // For most cases, either "username" or "username (Full Name)" should be used and are good enough. + // Only in very few cases (e.g.: unimportant lists, narrow layout), "username" or "Full Name" can be used. + DefaultShowFullName bool + Notification struct { MinTimeout time.Duration TimeoutStep time.Duration diff --git a/modules/storage/azureblob.go b/modules/storage/azureblob.go index e7297cec77..a273a7770d 100644 --- a/modules/storage/azureblob.go +++ b/modules/storage/azureblob.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io" + "net/http" "net/url" "os" "path" @@ -246,16 +247,53 @@ func (a *AzureBlobStorage) Delete(path string) error { return convertAzureBlobErr(err) } -// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes. -func (a *AzureBlobStorage) URL(path, name, _ string, reqParams url.Values) (*url.URL, error) { - blobClient := a.getBlobClient(path) +func (a *AzureBlobStorage) getSasURL(b *blob.Client, template sas.BlobSignatureValues) (string, error) { + urlParts, err := blob.ParseURL(b.URL()) + if err != nil { + return "", err + } - // TODO: OBJECT-STORAGE-CONTENT-TYPE: "browser inline rendering images/PDF" needs proper Content-Type header from storage - startTime := time.Now() - u, err := blobClient.GetSASURL(sas.BlobPermissions{ - Read: true, - }, time.Now().Add(5*time.Minute), &blob.GetSASURLOptions{ - StartTime: &startTime, + var t time.Time + if urlParts.Snapshot == "" { + t = time.Time{} + } else { + t, err = time.Parse(blob.SnapshotTimeFormat, urlParts.Snapshot) + if err != nil { + return "", err + } + } + + template.ContainerName = urlParts.ContainerName + template.BlobName = urlParts.BlobName + template.SnapshotTime = t + template.Version = sas.Version + + qps, err := template.SignWithSharedKey(a.credential) + if err != nil { + return "", err + } + + endpoint := b.URL() + "?" + qps.Encode() + + return endpoint, nil +} + +func (a *AzureBlobStorage) ServeDirectURL(storePath, name, method string, reqParams *ServeDirectOptions) (*url.URL, error) { + blobClient := a.getBlobClient(storePath) + + startTime := time.Now().UTC() + + param := prepareServeDirectOptions(reqParams, name) + + u, err := a.getSasURL(blobClient, sas.BlobSignatureValues{ + Permissions: (&sas.BlobPermissions{ + Read: method == http.MethodGet || method == http.MethodHead, + Write: method == http.MethodPut, + }).String(), + StartTime: startTime, + ExpiryTime: startTime.Add(5 * time.Minute), + ContentDisposition: param.ContentDisposition, + ContentType: param.ContentType, }) if err != nil { return nil, convertAzureBlobErr(err) diff --git a/modules/storage/azureblob_test.go b/modules/storage/azureblob_test.go index b3791b4916..b5d5d0fecc 100644 --- a/modules/storage/azureblob_test.go +++ b/modules/storage/azureblob_test.go @@ -14,12 +14,13 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAzureBlobStorageIterator(t *testing.T) { +func TestAzureBlobStorage(t *testing.T) { if os.Getenv("CI") == "" { t.Skip("azureBlobStorage not present outside of CI") return } - testStorageIterator(t, setting.AzureBlobStorageType, &setting.Storage{ + storageType := setting.AzureBlobStorageType + config := &setting.Storage{ AzureBlobConfig: setting.AzureBlobStorageConfig{ // https://learn.microsoft.com/azure/storage/common/storage-use-azurite?tabs=visual-studio-code#ip-style-url Endpoint: "http://devstoreaccount1.azurite.local:10000", @@ -28,7 +29,25 @@ func TestAzureBlobStorageIterator(t *testing.T) { AccountKey: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", Container: "test", }, - }) + } + table := []struct { + name string + test func(t *testing.T, typStr Type, cfg *setting.Storage) + }{ + { + name: "iterator", + test: testStorageIterator, + }, + { + name: "testBlobStorageURLContentTypeAndDisposition", + test: testBlobStorageURLContentTypeAndDisposition, + }, + } + for _, entry := range table { + t.Run(entry.name, func(t *testing.T) { + entry.test(t, storageType, config) + }) + } } func TestAzureBlobStoragePath(t *testing.T) { diff --git a/modules/storage/helper.go b/modules/storage/helper.go index f6c3d5eebb..30d96a33ad 100644 --- a/modules/storage/helper.go +++ b/modules/storage/helper.go @@ -30,7 +30,7 @@ func (s discardStorage) Delete(_ string) error { return fmt.Errorf("%s", s) } -func (s discardStorage) URL(_, _, _ string, _ url.Values) (*url.URL, error) { +func (s discardStorage) ServeDirectURL(_, _, _ string, _ *ServeDirectOptions) (*url.URL, error) { return nil, fmt.Errorf("%s", s) } diff --git a/modules/storage/helper_test.go b/modules/storage/helper_test.go index 3cba1e13c0..ae64c8aca4 100644 --- a/modules/storage/helper_test.go +++ b/modules/storage/helper_test.go @@ -37,7 +37,7 @@ func Test_discardStorage(t *testing.T) { assert.Error(t, err, string(tt)) } { - got, err := tt.URL("path", "name", "GET", nil) + got, err := tt.ServeDirectURL("path", "name", "GET", nil) assert.Nil(t, got) assert.Errorf(t, err, string(tt)) } diff --git a/modules/storage/local.go b/modules/storage/local.go index 8a1776f606..04e3e05f95 100644 --- a/modules/storage/local.go +++ b/modules/storage/local.go @@ -5,6 +5,7 @@ package storage import ( "context" + "errors" "fmt" "io" "net/url" @@ -27,25 +28,32 @@ type LocalStorage struct { // NewLocalStorage returns a local files func NewLocalStorage(ctx context.Context, config *setting.Storage) (ObjectStorage, error) { + // prepare storage root path if !filepath.IsAbs(config.Path) { - return nil, fmt.Errorf("LocalStorageConfig.Path should have been prepared by setting/storage.go and should be an absolute path, but not: %q", config.Path) - } - log.Info("Creating new Local Storage at %s", config.Path) - if err := os.MkdirAll(config.Path, os.ModePerm); err != nil { - return nil, err + return nil, fmt.Errorf("LocalStorage config.Path should have been prepared by setting/storage.go and should be an absolute path, but not: %q", config.Path) } + storageRoot := util.FilePathJoinAbs(config.Path) - if config.TemporaryPath == "" { - config.TemporaryPath = filepath.Join(config.Path, "tmp") + // prepare storage temporary path + storageTmp := config.TemporaryPath + if storageTmp == "" { + storageTmp = filepath.Join(storageRoot, "tmp") } - if !filepath.IsAbs(config.TemporaryPath) { - return nil, fmt.Errorf("LocalStorageConfig.TemporaryPath should be an absolute path, but not: %q", config.TemporaryPath) + if !filepath.IsAbs(storageTmp) { + return nil, fmt.Errorf("LocalStorage config.TemporaryPath should be an absolute path, but not: %q", config.TemporaryPath) + } + storageTmp = util.FilePathJoinAbs(storageTmp) + + // create the storage root if not exist + log.Info("Creating new Local Storage at %s", storageRoot) + if err := os.MkdirAll(storageRoot, os.ModePerm); err != nil { + return nil, err } return &LocalStorage{ ctx: ctx, - dir: config.Path, - tmpdir: config.TemporaryPath, + dir: storageRoot, + tmpdir: storageTmp, }, nil } @@ -108,44 +116,59 @@ func (l *LocalStorage) Stat(path string) (os.FileInfo, error) { return os.Stat(l.buildLocalPath(path)) } -// Delete delete a file -func (l *LocalStorage) Delete(path string) error { - return util.Remove(l.buildLocalPath(path)) +func (l *LocalStorage) deleteEmptyParentDirs(localFullPath string) { + for parent := filepath.Dir(localFullPath); len(parent) > len(l.dir); parent = filepath.Dir(parent) { + if err := os.Remove(parent); err != nil { + // since the target file has been deleted, parent dir error is not related to the file deletion itself. + break + } + } } -// URL gets the redirect URL to a file -func (l *LocalStorage) URL(path, name, _ string, reqParams url.Values) (*url.URL, error) { +// Delete deletes the file in storage and removes the empty parent directories (if possible) +func (l *LocalStorage) Delete(path string) error { + localFullPath := l.buildLocalPath(path) + err := util.Remove(localFullPath) + l.deleteEmptyParentDirs(localFullPath) + return err +} + +func (l *LocalStorage) ServeDirectURL(path, name, _ string, reqParams *ServeDirectOptions) (*url.URL, error) { return nil, ErrURLNotSupported } +func (l *LocalStorage) normalizeWalkError(err error) error { + if errors.Is(err, os.ErrNotExist) { + // ignore it because the file may be deleted during the walk, and we don't care about it + return nil + } + return err +} + // IterateObjects iterates across the objects in the local storage func (l *LocalStorage) IterateObjects(dirName string, fn func(path string, obj Object) error) error { dir := l.buildLocalPath(dirName) - return filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { - if err != nil { + return filepath.WalkDir(dir, func(path string, d os.DirEntry, errWalk error) error { + if err := l.ctx.Err(); err != nil { return err } - select { - case <-l.ctx.Done(): - return l.ctx.Err() - default: + if errWalk != nil { + return l.normalizeWalkError(errWalk) } - if path == l.dir { - return nil - } - if d.IsDir() { + if path == l.dir || d.IsDir() { return nil } + relPath, err := filepath.Rel(l.dir, path) if err != nil { - return err + return l.normalizeWalkError(err) } obj, err := os.Open(path) if err != nil { - return err + return l.normalizeWalkError(err) } defer obj.Close() - return fn(relPath, obj) + return fn(filepath.ToSlash(relPath), obj) }) } diff --git a/modules/storage/local_test.go b/modules/storage/local_test.go index 0592fd716b..29c13539d0 100644 --- a/modules/storage/local_test.go +++ b/modules/storage/local_test.go @@ -4,11 +4,14 @@ package storage import ( + "os" + "strings" "testing" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestBuildLocalPath(t *testing.T) { @@ -53,6 +56,49 @@ func TestBuildLocalPath(t *testing.T) { } } +func TestLocalStorageDelete(t *testing.T) { + rootDir := t.TempDir() + st, err := NewLocalStorage(t.Context(), &setting.Storage{Path: rootDir}) + require.NoError(t, err) + + assertExists := func(t *testing.T, path string, exists bool) { + _, err = os.Stat(rootDir + "/" + path) + if exists { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, os.ErrNotExist) + } + } + + _, err = st.Save("dir/sub1/1-a.txt", strings.NewReader(""), -1) + require.NoError(t, err) + _, err = st.Save("dir/sub1/1-b.txt", strings.NewReader(""), -1) + require.NoError(t, err) + _, err = st.Save("dir/sub2/2-a.txt", strings.NewReader(""), -1) + require.NoError(t, err) + + assertExists(t, "dir/sub1/1-a.txt", true) + assertExists(t, "dir/sub1/1-b.txt", true) + assertExists(t, "dir/sub2/2-a.txt", true) + + require.NoError(t, st.Delete("dir/sub1/1-a.txt")) + assertExists(t, "dir/sub1", true) + assertExists(t, "dir/sub1/1-a.txt", false) + assertExists(t, "dir/sub1/1-b.txt", true) + assertExists(t, "dir/sub2/2-a.txt", true) + + require.NoError(t, st.Delete("dir/sub1/1-b.txt")) + assertExists(t, ".", true) + assertExists(t, "dir/sub1", false) + assertExists(t, "dir/sub1/1-a.txt", false) + assertExists(t, "dir/sub1/1-b.txt", false) + assertExists(t, "dir/sub2/2-a.txt", true) + + require.NoError(t, st.Delete("dir/sub2/2-a.txt")) + assertExists(t, ".", true) + assertExists(t, "dir", false) +} + func TestLocalStorageIterator(t *testing.T) { testStorageIterator(t, setting.LocalStorageType, &setting.Storage{Path: t.TempDir()}) } diff --git a/modules/storage/minio.go b/modules/storage/minio.go index 6993ac2d92..ace78bb610 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -23,11 +23,7 @@ import ( "github.com/minio/minio-go/v7/pkg/credentials" ) -var ( - _ ObjectStorage = &MinioStorage{} - - quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") -) +var _ ObjectStorage = &MinioStorage{} type minioObject struct { *minio.Object @@ -278,37 +274,16 @@ func (m *MinioStorage) Delete(path string) error { return convertMinioErr(err) } -// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes. -func (m *MinioStorage) URL(storePath, name, method string, serveDirectReqParams url.Values) (*url.URL, error) { - // copy serveDirectReqParams - reqParams, err := url.ParseQuery(serveDirectReqParams.Encode()) - if err != nil { - return nil, err - } +func (m *MinioStorage) ServeDirectURL(storePath, name, method string, opt *ServeDirectOptions) (*url.URL, error) { + reqParams := url.Values{} - // Here we might not know the real filename, and it's quite inefficient to detect the mine type by pre-fetching the object head. - // So we just do a quick detection by extension name, at least if works for the "View Raw File" for an LFS file on the Web UI. - // Detect content type by extension name, only support the well-known safe types for inline rendering. - // TODO: OBJECT-STORAGE-CONTENT-TYPE: need a complete solution and refactor for Azure in the future - ext := path.Ext(name) - inlineExtMimeTypes := map[string]string{ - ".png": "image/png", - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".gif": "image/gif", - ".webp": "image/webp", - ".avif": "image/avif", - // ATTENTION! Don't support unsafe types like HTML/SVG due to security concerns: they can contain JS code, and maybe they need proper Content-Security-Policy - // HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context, it seems fine to render it inline - ".pdf": "application/pdf", - - // TODO: refactor with "modules/public/mime_types.go", for example: "DetectWellKnownSafeInlineMimeType" + param := prepareServeDirectOptions(opt, name) + // minio does not ignore empty params + if param.ContentType != "" { + reqParams.Set("response-content-type", param.ContentType) } - if mimeType, ok := inlineExtMimeTypes[ext]; ok { - reqParams.Set("response-content-type", mimeType) - reqParams.Set("response-content-disposition", "inline") - } else { - reqParams.Set("response-content-disposition", fmt.Sprintf(`attachment; filename="%s"`, quoteEscaper.Replace(name))) + if param.ContentDisposition != "" { + reqParams.Set("response-content-disposition", param.ContentDisposition) } expires := 5 * time.Minute @@ -323,6 +298,7 @@ func (m *MinioStorage) URL(storePath, name, method string, serveDirectReqParams // IterateObjects iterates across the objects in the miniostorage func (m *MinioStorage) IterateObjects(dirName string, fn func(path string, obj Object) error) error { opts := minio.GetObjectOptions{} + // FIXME: this loop is not right and causes resource leaking, see the comment of ListObjects for mObjInfo := range m.client.ListObjects(m.ctx, m.bucket, minio.ListObjectsOptions{ Prefix: m.buildMinioDirPrefix(dirName), Recursive: true, diff --git a/modules/storage/minio_test.go b/modules/storage/minio_test.go index 2726d765dd..5c15ee1ed6 100644 --- a/modules/storage/minio_test.go +++ b/modules/storage/minio_test.go @@ -16,12 +16,13 @@ import ( "github.com/stretchr/testify/assert" ) -func TestMinioStorageIterator(t *testing.T) { +func TestMinioStorage(t *testing.T) { if os.Getenv("CI") == "" { t.Skip("minioStorage not present outside of CI") return } - testStorageIterator(t, setting.MinioStorageType, &setting.Storage{ + storageType := setting.MinioStorageType + config := &setting.Storage{ MinioConfig: setting.MinioStorageConfig{ Endpoint: "minio:9000", AccessKeyID: "123456", @@ -29,7 +30,25 @@ func TestMinioStorageIterator(t *testing.T) { Bucket: "gitea", Location: "us-east-1", }, - }) + } + table := []struct { + name string + test func(t *testing.T, typStr Type, cfg *setting.Storage) + }{ + { + name: "iterator", + test: testStorageIterator, + }, + { + name: "testBlobStorageURLContentTypeAndDisposition", + test: testBlobStorageURLContentTypeAndDisposition, + }, + } + for _, entry := range table { + t.Run(entry.name, func(t *testing.T) { + entry.test(t, storageType, config) + }) + } } func TestMinioStoragePath(t *testing.T) { diff --git a/modules/storage/storage.go b/modules/storage/storage.go index 1868817c05..e19c421ba8 100644 --- a/modules/storage/storage.go +++ b/modules/storage/storage.go @@ -10,8 +10,11 @@ import ( "io" "net/url" "os" + "path" + "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/setting" ) @@ -56,6 +59,37 @@ type Object interface { Stat() (os.FileInfo, error) } +// ServeDirectOptions customizes HTTP headers for a generated signed URL. +type ServeDirectOptions struct { + // Overrides the automatically detected MIME type. + ContentType string +} + +// Safe defaults are applied only when not explicitly overridden by the caller. +func prepareServeDirectOptions(optsOptional *ServeDirectOptions, name string) (ret struct { + ContentType string + ContentDisposition string +}, +) { + // Here we might not know the real filename, and it's quite inefficient to detect the MIME type by pre-fetching the object head. + // So we just do a quick detection by extension name, at least it works for the "View Raw File" for an LFS file on the Web UI. + // TODO: OBJECT-STORAGE-CONTENT-TYPE: need a complete solution and refactor for Azure in the future + + if optsOptional != nil { + ret.ContentType = optsOptional.ContentType + } + name = path.Base(name) + if ret.ContentType == "" { + ext := path.Ext(name) + ret.ContentType = public.DetectWellKnownMimeType(ext) + } + // When using ServeDirect, the URL is from the object storage's web server, + // it is not the same origin as Gitea server, so it should be safe enough to use "inline" to render the content directly. + // If a browser doesn't support the content type to be displayed inline, browser will download with the filename. + ret.ContentDisposition = httplib.EncodeContentDispositionInline(name) + return ret +} + // ObjectStorage represents an object storage to handle a bucket and files type ObjectStorage interface { Open(path string) (Object, error) @@ -67,8 +101,21 @@ type ObjectStorage interface { Stat(path string) (os.FileInfo, error) Delete(path string) error - URL(path, name, method string, reqParams url.Values) (*url.URL, error) - IterateObjects(path string, iterator func(path string, obj Object) error) error + + // ServeDirectURL generates a "serve-direct" URL for the specified blob storage file, + // end user (browser) will use this URL to access the file directly from the object storage, bypassing Gitea server. + // Usually the link is time-limited (a few minutes) and contains a signature to ensure security. + // The generated URL must NOT use the same origin as Gitea server, otherwise it will cause security issues. + // * method defines which HTTP method is permitted for certain storage providers (e.g., MinIO). + // * opt allows customizing the Content-Type and Content-Disposition headers. + // TODO: need to merge "ServeDirect()" check into this function, avoid duplicate code and potential inconsistency. + ServeDirectURL(path, name, method string, opt *ServeDirectOptions) (*url.URL, error) + + // IterateObjects calls the iterator function for each object in the storage with the given path as prefix + // The "fullPath" argument in callback is the full path in this storage. + // * IterateObjects("", ...): iterate all objects in this storage + // * IterateObjects("sub-path", ...): iterate all objects with "sub-path" as prefix in this storage, the "fullPath" will be like "sub-path/xxx" + IterateObjects(basePath string, iterator func(fullPath string, obj Object) error) error } // Copy copies a file from source ObjectStorage to dest ObjectStorage @@ -131,7 +178,7 @@ var ( // Actions represents actions storage Actions ObjectStorage = uninitializedStorage - // Actions Artifacts represents actions artifacts storage + // ActionsArtifacts Artifacts represents actions artifacts storage ActionsArtifacts ObjectStorage = uninitializedStorage ) diff --git a/modules/storage/storage_test.go b/modules/storage/storage_test.go index 08f274e74b..83ee2ef793 100644 --- a/modules/storage/storage_test.go +++ b/modules/storage/storage_test.go @@ -4,12 +4,14 @@ package storage import ( + "net/http" "strings" "testing" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func testStorageIterator(t *testing.T, typStr Type, cfg *setting.Storage) { @@ -50,3 +52,53 @@ func testStorageIterator(t *testing.T, typStr Type, cfg *setting.Storage) { assert.Len(t, expected, count) } } + +type expectedServeDirectHeaders struct { + ContentType string + ContentDisposition string +} + +func testSingleBlobStorageURLContentTypeAndDisposition(t *testing.T, s ObjectStorage, path, name string, expected expectedServeDirectHeaders, reqParams *ServeDirectOptions) { + u, err := s.ServeDirectURL(path, name, http.MethodGet, reqParams) + require.NoError(t, err) + resp, err := http.Get(u.String()) + require.NoError(t, err) + defer resp.Body.Close() + if expected.ContentType != "" { + assert.Equal(t, expected.ContentType, resp.Header.Get("Content-Type")) + } + if expected.ContentDisposition != "" { + assert.Equal(t, expected.ContentDisposition, resp.Header.Get("Content-Disposition")) + } +} + +func testBlobStorageURLContentTypeAndDisposition(t *testing.T, typStr Type, cfg *setting.Storage) { + s, err := NewStorage(typStr, cfg) + assert.NoError(t, err) + + testFilename := "test.txt" + _, err = s.Save(testFilename, strings.NewReader("dummy-content"), -1) + assert.NoError(t, err) + + testSingleBlobStorageURLContentTypeAndDisposition(t, s, testFilename, "test.txt", expectedServeDirectHeaders{ + ContentType: "text/plain; charset=utf-8", + ContentDisposition: `inline; filename=test.txt`, + }, nil) + + testSingleBlobStorageURLContentTypeAndDisposition(t, s, testFilename, "test.pdf", expectedServeDirectHeaders{ + ContentType: "application/pdf", + ContentDisposition: `inline; filename=test.pdf`, + }, nil) + + testSingleBlobStorageURLContentTypeAndDisposition(t, s, testFilename, "test.wasm", expectedServeDirectHeaders{ + ContentDisposition: `inline; filename=test.wasm`, + }, nil) + + testSingleBlobStorageURLContentTypeAndDisposition(t, s, testFilename, "test.wasm", expectedServeDirectHeaders{ + ContentType: "application/wasm", + ContentDisposition: `inline; filename=test.wasm`, + }, &ServeDirectOptions{ + ContentType: "application/wasm", + }) + assert.NoError(t, s.Delete(testFilename)) +} diff --git a/modules/structs/activity.go b/modules/structs/activity.go index 9085495593..b896adfed5 100644 --- a/modules/structs/activity.go +++ b/modules/structs/activity.go @@ -12,7 +12,7 @@ type Activity struct { UserID int64 `json:"user_id"` // Receiver user // the type of action // - // enum: create_repo,rename_repo,star_repo,watch_repo,commit_repo,create_issue,create_pull_request,transfer_repo,push_tag,comment_issue,merge_pull_request,close_issue,reopen_issue,close_pull_request,reopen_pull_request,delete_tag,delete_branch,mirror_sync_push,mirror_sync_create,mirror_sync_delete,approve_pull_request,reject_pull_request,comment_pull,publish_release,pull_review_dismissed,pull_request_ready_for_review,auto_merge_pull_request + // enum: ["create_repo","rename_repo","star_repo","watch_repo","commit_repo","create_issue","create_pull_request","transfer_repo","push_tag","comment_issue","merge_pull_request","close_issue","reopen_issue","close_pull_request","reopen_pull_request","delete_tag","delete_branch","mirror_sync_push","mirror_sync_create","mirror_sync_delete","approve_pull_request","reject_pull_request","comment_pull","publish_release","pull_review_dismissed","pull_request_ready_for_review","auto_merge_pull_request"] OpType string `json:"op_type"` // The ID of the user who performed the action ActUserID int64 `json:"act_user_id"` diff --git a/modules/structs/activitypub.go b/modules/structs/activitypub.go deleted file mode 100644 index 39a6c1ac2a..0000000000 --- a/modules/structs/activitypub.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package structs - -// ActivityPub type -type ActivityPub struct { - // Context defines the JSON-LD context for ActivityPub - Context string `json:"@context"` -} diff --git a/modules/structs/hook.go b/modules/structs/hook.go index 57af38464a..99c1535155 100644 --- a/modules/structs/hook.go +++ b/modules/structs/hook.go @@ -19,6 +19,8 @@ var ErrInvalidReceiveHook = errors.New("Invalid JSON payload received over webho type Hook struct { // The unique identifier of the webhook ID int64 `json:"id"` + // Optional human-readable name for the webhook + Name string `json:"name"` // The type of the webhook (e.g., gitea, slack, discord) Type string `json:"type"` // Branch filter pattern to determine which branches trigger the webhook @@ -51,7 +53,7 @@ type CreateHookOptionConfig map[string]string // CreateHookOption options when create a hook type CreateHookOption struct { // required: true - // enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,wechatwork,packagist + // enum: ["dingtalk","discord","gitea","gogs","msteams","slack","telegram","feishu","wechatwork","packagist"] // The type of the webhook to create Type string `json:"type" binding:"Required"` // required: true @@ -66,6 +68,8 @@ type CreateHookOption struct { // default: false // Whether the webhook should be active upon creation Active bool `json:"active"` + // Optional human-readable name for the webhook + Name string `json:"name" binding:"MaxSize(255)"` } // EditHookOption options when modify one hook @@ -80,6 +84,8 @@ type EditHookOption struct { AuthorizationHeader string `json:"authorization_header"` // Whether the webhook is active and will be triggered Active *bool `json:"active"` + // Optional human-readable name + Name *string `json:"name,omitzero" binding:"MaxSize(255)"` } // Payloader payload is some part of one hook diff --git a/modules/structs/issue.go b/modules/structs/issue.go index 22116091ab..7c230bafa9 100644 --- a/modules/structs/issue.go +++ b/modules/structs/issue.go @@ -14,6 +14,8 @@ import ( ) // StateType issue state type +// +// swagger:enum StateType type StateType string const ( @@ -21,10 +23,11 @@ const ( StateOpen StateType = "open" // StateClosed pr is closed StateClosed StateType = "closed" - // StateAll is all - StateAll StateType = "all" ) +// StateAll is a query parameter filter value, not a valid object state. +const StateAll = "all" + // PullRequestMeta PR info if an issue is a PR type PullRequestMeta struct { HasMerged bool `json:"merged"` @@ -58,15 +61,11 @@ type Issue struct { Labels []*Label `json:"labels"` Milestone *Milestone `json:"milestone"` // deprecated - Assignee *User `json:"assignee"` - Assignees []*User `json:"assignees"` - // Whether the issue is open or closed - // - // type: string - // enum: open,closed - State StateType `json:"state"` - IsLocked bool `json:"is_locked"` - Comments int `json:"comments"` + Assignee *User `json:"assignee"` + Assignees []*User `json:"assignees"` + State StateType `json:"state"` + IsLocked bool `json:"is_locked"` + Comments int `json:"comments"` // swagger:strfmt date-time Created time.Time `json:"created_at"` // swagger:strfmt date-time @@ -82,6 +81,8 @@ type Issue struct { Repo *RepositoryMeta `json:"repository"` PinOrder int `json:"pin_order"` + // The version of the issue content for optimistic locking + ContentVersion int `json:"content_version"` } // CreateIssueOption options to create one issue @@ -115,6 +116,8 @@ type EditIssueOption struct { // swagger:strfmt date-time Deadline *time.Time `json:"due_date"` RemoveDeadline *bool `json:"unset_due_date"` + // The current version of the issue content to detect conflicts during editing + ContentVersion *int `json:"content_version"` } // EditDeadlineOption options for creating a deadline @@ -132,6 +135,8 @@ type IssueDeadline struct { } // IssueFormFieldType defines issue form field type, can be "markdown", "textarea", "input", "dropdown" or "checkboxes" +// +// swagger:enum IssueFormFieldType type IssueFormFieldType string const ( @@ -168,7 +173,8 @@ func (iff IssueFormField) VisibleInContent() bool { } // IssueFormFieldVisible defines issue form field visible -// swagger:model +// +// swagger:enum IssueFormFieldVisible type IssueFormFieldVisible string const ( diff --git a/modules/structs/issue_milestone.go b/modules/structs/issue_milestone.go index 226c613d47..dd8bdc6cda 100644 --- a/modules/structs/issue_milestone.go +++ b/modules/structs/issue_milestone.go @@ -40,7 +40,7 @@ type CreateMilestoneOption struct { // swagger:strfmt date-time // Deadline is the due date for the milestone Deadline *time.Time `json:"due_on"` - // enum: open,closed + // enum: ["open","closed"] // State indicates the initial state of the milestone State string `json:"state"` } @@ -52,6 +52,7 @@ type EditMilestoneOption struct { // Description provides updated details about the milestone Description *string `json:"description"` // State indicates the updated state of the milestone + // enum: ["open","closed"] State *string `json:"state"` // Deadline is the updated due date for the milestone Deadline *time.Time `json:"due_on"` diff --git a/modules/structs/notifications.go b/modules/structs/notifications.go index cee5da6624..d7aa0783dc 100644 --- a/modules/structs/notifications.go +++ b/modules/structs/notifications.go @@ -40,7 +40,7 @@ type NotificationSubject struct { // Type indicates the type of the notification subject Type NotifySubjectType `json:"type" binding:"In(Issue,Pull,Commit,Repository)"` // State indicates the current state of the notification subject - State StateType `json:"state"` + State NotifySubjectStateType `json:"state"` } // NotificationCount number of unread notifications @@ -49,7 +49,22 @@ type NotificationCount struct { New int64 `json:"new"` } +// NotifySubjectStateType represents the state of a notification subject +// swagger:enum NotifySubjectStateType +type NotifySubjectStateType string + +const ( + // NotifySubjectStateOpen is an open subject + NotifySubjectStateOpen NotifySubjectStateType = "open" + // NotifySubjectStateClosed is a closed subject + NotifySubjectStateClosed NotifySubjectStateType = "closed" + // NotifySubjectStateMerged is a merged pull request + NotifySubjectStateMerged NotifySubjectStateType = "merged" +) + // NotifySubjectType represent type of notification subject +// +// swagger:enum NotifySubjectType type NotifySubjectType string const ( diff --git a/modules/structs/org.go b/modules/structs/org.go index c3d70ebf00..723689cb53 100644 --- a/modules/structs/org.go +++ b/modules/structs/org.go @@ -60,29 +60,27 @@ type CreateOrgOption struct { // The location of the organization Location string `json:"location" binding:"MaxSize(50)"` // possible values are `public` (default), `limited` or `private` - // enum: public,limited,private + // enum: ["public","limited","private"] Visibility string `json:"visibility" binding:"In(,public,limited,private)"` // Whether repository administrators can change team access RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"` } -// TODO: make EditOrgOption fields optional after https://gitea.com/go-chi/binding/pulls/5 got merged - // EditOrgOption options for editing an organization type EditOrgOption struct { // The full display name of the organization - FullName string `json:"full_name" binding:"MaxSize(100)"` - // The email address of the organization - Email string `json:"email" binding:"MaxSize(255)"` + FullName *string `json:"full_name" binding:"MaxSize(100)"` + // The email address of the organization; use empty string to clear + Email *string `json:"email" binding:"MaxSize(255)"` // The description of the organization - Description string `json:"description" binding:"MaxSize(255)"` + Description *string `json:"description" binding:"MaxSize(255)"` // The website URL of the organization - Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` + Website *string `json:"website" binding:"ValidUrl;MaxSize(255)"` // The location of the organization - Location string `json:"location" binding:"MaxSize(50)"` + Location *string `json:"location" binding:"MaxSize(50)"` // possible values are `public`, `limited` or `private` - // enum: public,limited,private - Visibility string `json:"visibility" binding:"In(,public,limited,private)"` + // enum: ["public","limited","private"] + Visibility *string `json:"visibility" binding:"In(,public,limited,private)"` // Whether repository administrators can change team access RepoAdminChangeTeamAccess *bool `json:"repo_admin_change_team_access"` } diff --git a/modules/structs/org_team.go b/modules/structs/org_team.go index d34de5b6d2..f730a5681c 100644 --- a/modules/structs/org_team.go +++ b/modules/structs/org_team.go @@ -16,7 +16,7 @@ type Team struct { Organization *Organization `json:"organization"` // Whether the team has access to all repositories in the organization IncludesAllRepositories bool `json:"includes_all_repositories"` - // enum: none,read,write,admin,owner + // enum: ["none","read","write","admin","owner"] Permission string `json:"permission"` // example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"] // Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions. @@ -35,7 +35,7 @@ type CreateTeamOption struct { Description string `json:"description" binding:"MaxSize(255)"` // Whether the team has access to all repositories in the organization IncludesAllRepositories bool `json:"includes_all_repositories"` - // enum: read,write,admin + // enum: ["read","write","admin"] Permission string `json:"permission"` // example: ["repo.actions","repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.ext_wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"] // Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions. @@ -54,7 +54,7 @@ type EditTeamOption struct { Description *string `json:"description" binding:"MaxSize(255)"` // Whether the team has access to all repositories in the organization IncludesAllRepositories *bool `json:"includes_all_repositories"` - // enum: read,write,admin + // enum: ["read","write","admin"] Permission string `json:"permission"` // example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"] // Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions. diff --git a/modules/structs/pull.go b/modules/structs/pull.go index 3ad2f78bd3..cd2ffbe719 100644 --- a/modules/structs/pull.go +++ b/modules/structs/pull.go @@ -91,6 +91,8 @@ type PullRequest struct { // The pin order for the pull request PinOrder int `json:"pin_order"` + // The version of the pull request content for optimistic locking + ContentVersion int `json:"content_version"` } // PRBranchInfo information about a branch @@ -168,6 +170,8 @@ type EditPullRequestOption struct { RemoveDeadline *bool `json:"unset_due_date"` // Whether to allow maintainer edits AllowMaintainerEdit *bool `json:"allow_maintainer_edit"` + // The current version of the pull request content to detect conflicts during editing + ContentVersion *int `json:"content_version"` } // ChangedFile store information about files affected by the pull request diff --git a/modules/structs/pull_review.go b/modules/structs/pull_review.go index f44d2f84f5..de0677efab 100644 --- a/modules/structs/pull_review.go +++ b/modules/structs/pull_review.go @@ -8,6 +8,8 @@ import ( ) // ReviewStateType review state type +// +// swagger:enum ReviewStateType type ReviewStateType string const ( @@ -21,10 +23,11 @@ const ( ReviewStateRequestChanges ReviewStateType = "REQUEST_CHANGES" // ReviewStateRequestReview review is requested from user ReviewStateRequestReview ReviewStateType = "REQUEST_REVIEW" - // ReviewStateUnknown state of pr is unknown - ReviewStateUnknown ReviewStateType = "" ) +// ReviewStateUnknown is an internal sentinel for unknown review state, not a valid API value. +const ReviewStateUnknown = "" + // PullReview represents a pull request review type PullReview struct { ID int64 `json:"id"` diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 765546a5aa..7cd64fd7a4 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -73,6 +73,7 @@ type Repository struct { Stars int `json:"stars_count"` Forks int `json:"forks_count"` Watchers int `json:"watchers_count"` + BranchCount int `json:"branch_count"` OpenIssues int `json:"open_issues_count"` OpenPulls int `json:"open_pr_counter"` Releases int `json:"release_counter"` @@ -113,7 +114,7 @@ type Repository struct { Internal bool `json:"internal"` MirrorInterval string `json:"mirror_interval"` // ObjectFormatName of the underlying git repository - // enum: sha1,sha256 + // enum: ["sha1","sha256"] ObjectFormatName string `json:"object_format_name"` // swagger:strfmt date-time MirrorUpdated time.Time `json:"mirror_updated"` @@ -135,7 +136,7 @@ type CreateRepoOption struct { // Whether the repository is private Private bool `json:"private"` // Label-Set to use - IssueLabels string `json:"issue_labels"` + IssueLabels string `json:"issue_labels" binding:"MaxSize(255)"` // Whether the repository should be auto-initialized? AutoInit bool `json:"auto_init"` // Whether the repository is template @@ -143,16 +144,16 @@ type CreateRepoOption struct { // Gitignores to use Gitignores string `json:"gitignores"` // License to use - License string `json:"license"` + License string `json:"license" binding:"MaxSize(100)"` // Readme of the repository to create - Readme string `json:"readme"` + Readme string `json:"readme" binding:"MaxSize(255)"` // DefaultBranch of the repository (used when initializes and in template) DefaultBranch string `json:"default_branch" binding:"GitRefName;MaxSize(100)"` // TrustModel of the repository - // enum: default,collaborator,committer,collaboratorcommitter + // enum: ["default","collaborator","committer","collaboratorcommitter"] TrustModel string `json:"trust_model"` - // ObjectFormatName of the underlying git repository - // enum: sha1,sha256 + // ObjectFormatName of the underlying git repository, empty string for default (sha1) + // enum: ["sha1","sha256"] ObjectFormatName string `json:"object_format_name" binding:"MaxSize(6)"` } @@ -377,7 +378,7 @@ type MigrateRepoOptions struct { // required: true RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` - // enum: git,github,gitea,gitlab,gogs,onedev,gitbucket,codebase,codecommit + // enum: ["git","github","gitea","gitlab","gogs","onedev","gitbucket","codebase","codecommit"] Service string `json:"service"` AuthUsername string `json:"auth_username"` AuthPassword string `json:"auth_password"` diff --git a/modules/structs/repo_actions.go b/modules/structs/repo_actions.go index b491d6ccce..92ca9bccce 100644 --- a/modules/structs/repo_actions.go +++ b/modules/structs/repo_actions.go @@ -196,12 +196,27 @@ type ActionRunner struct { Name string `json:"name"` Status string `json:"status"` Busy bool `json:"busy"` + Disabled bool `json:"disabled"` Ephemeral bool `json:"ephemeral"` Labels []*ActionRunnerLabel `json:"labels"` } +// EditActionRunnerOption represents the editable fields for a runner. +// swagger:model +type EditActionRunnerOption struct { + // required: true + Disabled *bool `json:"disabled"` +} + // ActionRunnersResponse returns Runners type ActionRunnersResponse struct { Entries []*ActionRunner `json:"runners"` TotalCount int64 `json:"total_count"` } + +// RunDetails returns workflow_dispatch runid and url +type RunDetails struct { + WorkflowRunID int64 `json:"workflow_run_id"` + RunURL string `json:"run_url"` + HTMLURL string `json:"html_url"` +} diff --git a/modules/structs/repo_collaborator.go b/modules/structs/repo_collaborator.go index 9ede7f075a..6b315df403 100644 --- a/modules/structs/repo_collaborator.go +++ b/modules/structs/repo_collaborator.go @@ -5,7 +5,7 @@ package structs // AddCollaboratorOption options when adding a user as a collaborator of a repository type AddCollaboratorOption struct { - // enum: read,write,admin + // enum: ["read","write","admin"] // Permission level to grant the collaborator Permission *string `json:"permission"` } diff --git a/modules/structs/repo_file.go b/modules/structs/repo_file.go index 59665062b7..53ce5aeae2 100644 --- a/modules/structs/repo_file.go +++ b/modules/structs/repo_file.go @@ -72,7 +72,7 @@ type ChangeFileOperation struct { // indicates what to do with the file: "create" for creating a new file, "update" for updating an existing file, // "upload" for creating or updating a file, "rename" for renaming a file, and "delete" for deleting an existing file. // required: true - // enum: create,update,upload,rename,delete + // enum: ["create","update","upload","rename","delete"] Operation string `json:"operation" binding:"Required"` // path to the existing or new file // required: true diff --git a/modules/svg/svg.go b/modules/svg/svg.go index 333b5764c2..234b1f8c13 100644 --- a/modules/svg/svg.go +++ b/modules/svg/svg.go @@ -8,13 +8,32 @@ import ( "html/template" "path" "strings" + "sync" gitea_html "code.gitea.io/gitea/modules/htmlutil" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/public" ) -var svgIcons map[string]string +type svgIconItem struct { + html string + mocking bool +} + +type svgCacheKey struct { + icon string + size int + class string +} + +var ( + svgIcons map[string]svgIconItem + + svgCacheMu sync.Mutex + svgCache sync.Map + svgCacheCount int + svgCacheLimit = 10000 +) const defaultSize = 16 @@ -26,7 +45,7 @@ func Init() error { return err } - svgIcons = make(map[string]string, len(files)) + svgIcons = make(map[string]svgIconItem, len(files)) for _, file := range files { if path.Ext(file) != ".svg" { continue @@ -35,7 +54,7 @@ func Init() error { if err != nil { log.Error("Failed to read SVG file %s: %v", file, err) } else { - svgIcons[file[:len(file)-4]] = string(Normalize(bs, defaultSize)) + svgIcons[file[:len(file)-4]] = svgIconItem{html: string(Normalize(bs, defaultSize))} } } return nil @@ -43,10 +62,13 @@ func Init() error { func MockIcon(icon string) func() { if svgIcons == nil { - svgIcons = make(map[string]string) + svgIcons = make(map[string]svgIconItem) } orig, exist := svgIcons[icon] - svgIcons[icon] = fmt.Sprintf(``, icon, defaultSize, defaultSize) + svgIcons[icon] = svgIconItem{ + html: fmt.Sprintf(``, icon, defaultSize, defaultSize), + mocking: true, + } return func() { if exist { svgIcons[icon] = orig @@ -58,11 +80,28 @@ func MockIcon(icon string) func() { // RenderHTML renders icons - arguments icon name (string), size (int), class (string) func RenderHTML(icon string, others ...any) template.HTML { + result, _ := renderHTML(icon, others...) + return result +} + +func renderHTML(icon string, others ...any) (_ template.HTML, usingCache bool) { if icon == "" { - return "" + return "", false } size, class := gitea_html.ParseSizeAndClass(defaultSize, "", others...) - if svgStr, ok := svgIcons[icon]; ok { + if svgItem, ok := svgIcons[icon]; ok { + svgStr := svgItem.html + // fast path for default size and no classes + if size == defaultSize && class == "" { + return template.HTML(svgStr), false + } + + cacheKey := svgCacheKey{icon, size, class} + cachedHTML, cached := svgCache.Load(cacheKey) + if cached && !svgItem.mocking { + return cachedHTML.(template.HTML), true + } + // the code is somewhat hacky, but it just works, because the SVG contents are all normalized if size != defaultSize { svgStr = strings.Replace(svgStr, fmt.Sprintf(`width="%d"`, defaultSize), fmt.Sprintf(`width="%d"`, size), 1) @@ -71,8 +110,24 @@ func RenderHTML(icon string, others ...any) template.HTML { if class != "" { svgStr = strings.Replace(svgStr, `class="`, fmt.Sprintf(`class="%s `, class), 1) } - return template.HTML(svgStr) + result := template.HTML(svgStr) + + if !svgItem.mocking { + // no need to double-check, the rendering is fast enough and the cache is just an optimization + svgCacheMu.Lock() + if svgCacheCount >= svgCacheLimit { + svgCache.Clear() + svgCacheCount = 0 + } + svgCacheCount++ + svgCache.Store(cacheKey, result) + svgCacheMu.Unlock() + } + + return result, false } + // during test (or something wrong happens), there is no SVG loaded, so use a dummy span to tell that the icon is missing - return template.HTML(fmt.Sprintf("%s(%d/%s)", template.HTMLEscapeString(icon), size, template.HTMLEscapeString(class))) + dummy := template.HTML(fmt.Sprintf("%s(%d/%s)", template.HTMLEscapeString(icon), size, template.HTMLEscapeString(class))) + return dummy, false } diff --git a/modules/svg/svg_test.go b/modules/svg/svg_test.go new file mode 100644 index 0000000000..a42f57cec6 --- /dev/null +++ b/modules/svg/svg_test.go @@ -0,0 +1,54 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package svg + +import ( + "testing" + + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" +) + +func TestRenderHTMLCache(t *testing.T) { + const svgRealContent = "RealContent" + svgIcons = map[string]svgIconItem{ + "test": {html: `` + svgRealContent + ``}, + } + + // default params: no cache entry + _, usingCache := renderHTML("test") + assert.False(t, usingCache) + _, usingCache = renderHTML("test") + assert.False(t, usingCache) + + // non-default params: cached + _, usingCache = renderHTML("test", 24) + assert.False(t, usingCache) + _, usingCache = renderHTML("test", 24) + assert.True(t, usingCache) + + // mocked svg shouldn't be cached + revertMock := MockIcon("test") + mockedHTML, usingCache := renderHTML("test", 24) + assert.False(t, usingCache) + assert.NotContains(t, mockedHTML, svgRealContent) + revertMock() + realHTML, usingCache := renderHTML("test", 24) + assert.True(t, usingCache) + assert.Contains(t, realHTML, svgRealContent) + + t.Run("CacheWithLimit", func(t *testing.T) { + assert.NotZero(t, svgCacheCount) + const testLimit = 3 + defer test.MockVariableValue(&svgCacheLimit, testLimit)() + for i := range 10 { + _, usingCache = renderHTML("test", 100+i) + assert.False(t, usingCache) + _, usingCache = renderHTML("test", 100+i) + assert.True(t, usingCache) + assert.LessOrEqual(t, svgCacheCount, testLimit) + } + }) +} diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 11c52bd5a7..f81be1255a 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -6,15 +6,18 @@ package templates import ( "fmt" + "html" "html/template" "net/url" "strconv" "strings" + "sync" "time" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/htmlutil" "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/svg" "code.gitea.io/gitea/modules/templates/eval" @@ -22,8 +25,7 @@ import ( "code.gitea.io/gitea/services/gitdiff" ) -// NewFuncMap returns functions for injecting to templates -func NewFuncMap() template.FuncMap { +func newFuncMapWebPage() template.FuncMap { return map[string]any{ "DumpVar": dumpVar, "NIL": func() any { return nil }, @@ -37,8 +39,6 @@ func NewFuncMap() template.FuncMap { "QueryEscape": queryEscape, "QueryBuild": QueryBuild, "SanitizeHTML": SanitizeHTML, - "URLJoin": util.URLJoin, - "DotEscape": dotEscape, "PathEscape": url.PathEscape, "PathEscapeSegments": util.PathEscapeSegments, @@ -59,6 +59,7 @@ func NewFuncMap() template.FuncMap { // ----------------------------------------------------------------- // time / number / format + "ShortSha": base.ShortSha, "FileSize": base.FileSize, "CountFmt": countFmt, "Sec2Hour": util.SecToHours, @@ -69,6 +70,9 @@ func NewFuncMap() template.FuncMap { return strconv.FormatInt(time.Since(startTime).Nanoseconds()/1e6, 10) + "ms" }, + "AssetURI": public.AssetURI, + "ScriptImport": scriptImport, + // ----------------------------------------------------------------- // setting "AppName": func() string { @@ -80,25 +84,12 @@ func NewFuncMap() template.FuncMap { "AssetUrlPrefix": func() string { return setting.StaticURLPrefix + "/assets" }, - "AppUrl": func() string { - // The usage of AppUrl should be avoided as much as possible, - // because the AppURL(ROOT_URL) may not match user's visiting site and the ROOT_URL in app.ini may be incorrect. - // And it's difficult for Gitea to guess absolute URL correctly with zero configuration, - // because Gitea doesn't know whether the scheme is HTTP or HTTPS unless the reverse proxy could tell Gitea. - return setting.AppURL - }, "AppVer": func() string { return setting.AppVer }, - "AppDomain": func() string { // documented in mail-templates.md + "AppDomain": func() string { // TODO: helm registry still uses it, need to use current request host in the future return setting.Domain }, - "AssetVersion": func() string { - return setting.AssetVersion - }, - "DefaultShowFullName": func() bool { - return setting.UI.DefaultShowFullName - }, "ShowFooterTemplateLoadTime": func() bool { return setting.Other.ShowFooterTemplateLoadTime }, @@ -144,8 +135,7 @@ func NewFuncMap() template.FuncMap { "ReactionToEmoji": reactionToEmoji, // ----------------------------------------------------------------- - // misc - "ShortSha": base.ShortSha, + // misc (TODO: move them to MiscUtils to avoid bloating the main func map) "ActionContent2Commits": ActionContent2Commits, "IsMultilineCommitMessage": isMultilineCommitMessage, "CommentMustAsDiff": gitdiff.CommentMustAsDiff, @@ -179,11 +169,6 @@ func queryEscape(s string) template.URL { return template.URL(url.QueryEscape(s)) } -// dotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent auto-linkers from detecting these as urls -func dotEscape(raw string) string { - return strings.ReplaceAll(raw, ".", "\u200d.\u200d") -} - // iif is an "inline-if", similar util.Iif[T] but templates need the non-generic version, // and it could be simply used as "{{iif expr trueVal}}" (omit the falseVal). func iif(condition any, vals ...any) any { @@ -307,3 +292,30 @@ func QueryBuild(a ...any) template.URL { } return template.URL(s) } + +var globalVars = sync.OnceValue(func() (ret struct { + scriptImportRemainingPart string +}, +) { + // add onerror handler to alert users when the script fails to load: + // * for end users: there were many users reporting that "UI doesn't work", actually they made mistakes in their config + // * for developers: help them to remember to run "make watch-frontend" to build frontend assets + // the message will be directly put in the onerror JS code's string + onScriptErrorPrompt := `Please make sure the asset files can be accessed.` + if !setting.IsProd { + onScriptErrorPrompt += `\n\nFor development, run: make watch-frontend.` + } + onScriptErrorJS := fmt.Sprintf(`alert('Failed to load asset file from ' + this.src + '. %s')`, onScriptErrorPrompt) + ret.scriptImportRemainingPart = `onerror="` + html.EscapeString(onScriptErrorJS) + `">` + return ret +}) + +func scriptImport(path string, typ ...string) template.HTML { + if len(typ) > 0 { + if typ[0] == "module" { + return template.HTML(` - +{{ScriptImport "js/iife.js"}} diff --git a/templates/base/head_style.tmpl b/templates/base/head_style.tmpl index b2fc033558..4a4fb9d96f 100644 --- a/templates/base/head_style.tmpl +++ b/templates/base/head_style.tmpl @@ -1,2 +1,2 @@ - - + + diff --git a/templates/base/markup_codepreview.tmpl b/templates/base/markup_codepreview.tmpl index e16848581d..4c04b5092c 100644 --- a/templates/base/markup_codepreview.tmpl +++ b/templates/base/markup_codepreview.tmpl @@ -13,10 +13,7 @@ {{- range $idx, $line := .HighlightLines -}} - {{- if $.EscapeStatus.Escaped -}} - {{- $lineEscapeStatus := index $.LineEscapeStatus $idx -}} - {{if $lineEscapeStatus.Escaped}}{{end}} - {{- end}} + {{- ctx.RenderUtils.RenderUnicodeEscapeToggleTd $.EscapeStatus (index $.LineEscapeStatus $idx)}}
{{$line.FormattedContent}}
{{/* only div works, span generates incorrect HTML structure */}} {{- end -}} diff --git a/templates/base/modal_actions_confirm.tmpl b/templates/base/modal_actions_confirm.tmpl index ec6942b9e7..4fde7e097a 100644 --- a/templates/base/modal_actions_confirm.tmpl +++ b/templates/base/modal_actions_confirm.tmpl @@ -12,15 +12,15 @@ The ".ok.button" and ".cancel.button" selectors are also used by Fomantic Modal {{if .ModalButtonDangerText}} {{else}} - {{$textNegitive := ctx.Locale.Tr "modal.no"}} + {{$textNegative := ctx.Locale.Tr "modal.no"}} {{$textPositive := ctx.Locale.Tr "modal.yes"}} {{if eq .ModalButtonTypes "confirm"}} - {{$textNegitive = ctx.Locale.Tr "modal.cancel"}} + {{$textNegative = ctx.Locale.Tr "modal.cancel"}} {{$textPositive = ctx.Locale.Tr "modal.confirm"}} {{end}} - {{if .ModalButtonCancelText}}{{$textNegitive = .ModalButtonCancelText}}{{end}} + {{if .ModalButtonCancelText}}{{$textNegative = .ModalButtonCancelText}}{{end}} {{if .ModalButtonOkText}}{{$textPositive = .ModalButtonOkText}}{{end}} - + {{end}} diff --git a/templates/devtest/devtest-footer.tmpl b/templates/devtest/devtest-footer.tmpl index a1b3b86e5c..091a1035a9 100644 --- a/templates/devtest/devtest-footer.tmpl +++ b/templates/devtest/devtest-footer.tmpl @@ -1,3 +1 @@ -{{/* TODO: the devtest.js is isolated from index.js, so no module is shared and many index.js functions do not work in devtest.ts */}} - {{template "base/footer" ctx.RootData}} diff --git a/templates/devtest/devtest-header.tmpl b/templates/devtest/devtest-header.tmpl index 0775dccc2d..628e4388a0 100644 --- a/templates/devtest/devtest-header.tmpl +++ b/templates/devtest/devtest-header.tmpl @@ -1,3 +1,4 @@ {{template "base/head" ctx.RootData}} - + +
{{template "base/alert" .}} diff --git a/templates/devtest/gitea-ui.tmpl b/templates/devtest/gitea-ui.tmpl index c1e6590a43..1584792b6b 100644 --- a/templates/devtest/gitea-ui.tmpl +++ b/templates/devtest/gitea-ui.tmpl @@ -84,12 +84,6 @@ -
-

<origin-url>

-
-
-
-

<overflow-menu>

@@ -117,15 +111,6 @@
-
-

Absolute Dates

-
-
-
-
-
-
-

LocaleNumber

{{ctx.Locale.PrettyNumber 1}}
@@ -137,17 +122,6 @@
{{ctx.Locale.PrettyNumber 1234567}}
-
-

TimeSince

-
Now: {{DateUtils.TimeSince .TimeNow}}
-
5s past: {{DateUtils.TimeSince .TimePast5s}}
-
5s future: {{DateUtils.TimeSince .TimeFuture5s}}
-
2m past: {{DateUtils.TimeSince .TimePast2m}}
-
2m future: {{DateUtils.TimeSince .TimeFuture2m}}
-
1y past: {{DateUtils.TimeSince .TimePast1y}}
-
1y future: {{DateUtils.TimeSince .TimeFuture1y}}
-
-

SVG alignment

@@ -184,7 +158,7 @@

ComboMarkdownEditor

- {{template "shared/combomarkdowneditor" dict "MarkdownPreviewContext" "/owner/path"}} + {{template "shared/combomarkdowneditor" dict "MarkdownEditorContext" (ctx.MiscUtils.MarkdownEditorGeneral nil)}}

Tailwind CSS Demo

diff --git a/templates/devtest/relative-time.tmpl b/templates/devtest/relative-time.tmpl new file mode 100644 index 0000000000..041ce49f09 --- /dev/null +++ b/templates/devtest/relative-time.tmpl @@ -0,0 +1,66 @@ +{{template "devtest/devtest-header"}} +
+
+
+

Relative (auto)

+
now:
+
3m ago:
+
3h ago:
+
1d ago:
+
3d ago:
+
3d future:
+
40d ago (threshold):
+
+
+

tense=past

+
3m ago:
+
future clamped:
+
60d ago:
+
+
+

tense=future

+
3h future:
+
past clamped:
+
+
+

Duration

+
0s:
+
3h:
+
1d 2h:
+
short:
+
narrow:
+
+
+

Datetime (absolute)

+
default:
+
month=short:
+
month=long:
+
numeric:
+
weekday:
+
with time:
+
+
+

Threshold

+
P0Y:
+
P1D:
+
P30D:
+
+
+

Prefix

+
default:
+
prefix="":
+
prefix="at":
+
+
+

TimeSince (Go helper)

+
now: {{DateUtils.TimeSince .TimeNow}}
+
5s past: {{DateUtils.TimeSince .TimePast5s}}
+
5s future: {{DateUtils.TimeSince .TimeFuture5s}}
+
2m past: {{DateUtils.TimeSince .TimePast2m}}
+
2m future: {{DateUtils.TimeSince .TimeFuture2m}}
+
1y past: {{DateUtils.TimeSince .TimePast1y}}
+
1y future: {{DateUtils.TimeSince .TimeFuture1y}}
+
+
+
+{{template "devtest/devtest-footer"}} diff --git a/templates/devtest/repo-action-view.tmpl b/templates/devtest/repo-action-view.tmpl index 292c84c928..46f040d8a6 100644 --- a/templates/devtest/repo-action-view.tmpl +++ b/templates/devtest/repo-action-view.tmpl @@ -1,14 +1,14 @@ {{template "base/head" .}}
-
- Run:CanCancel - Run:CanApprove - Run:CanRerun + {{template "repo/actions/view_component" (dict - "RunIndex" (or .RunIndex 10) - "JobIndex" (or .JobIndex 0) - "ActionsURL" (print AppSubUrl "/devtest/actions-mock") + "RunID" (or .RunID 10) + "JobID" (or .JobID 0) + "ActionsURL" (print AppSubUrl "/devtest/repo-action-view") )}}
{{template "base/footer" .}} diff --git a/templates/devtest/severity-colors.tmpl b/templates/devtest/severity-colors.tmpl new file mode 100644 index 0000000000..9f86b864ea --- /dev/null +++ b/templates/devtest/severity-colors.tmpl @@ -0,0 +1,80 @@ +{{template "devtest/devtest-header"}} +
+

Severity Colors

+ +

Messages

+
+
Error Message
+

This is an error message using --color-error-* variables.

+
+
+
Warning Message
+

This is a warning message using --color-warning-* variables.

+
+
+
Success Message
+

This is a success message using --color-success-* variables.

+
+
+
Info Message
+

This is an info message using --color-info-* variables.

+
+ +

Form Fields

+
+
+ + +
+
+ +

Labels

+
+
Red
+
Orange
+
Yellow
+
Green
+
Blue
+
Violet
+
Purple
+
+ +

Color Swatches

+

Error

+
+
+
Text
+ error-bg +
+
+
Hover
+ error-bg-hover +
+
+
Active
+ error-bg-active +
+
+

Warning

+
+
+
Text
+ warning-bg +
+
+

Success

+
+
+
Text
+ success-bg +
+
+

Info

+
+
+
Text
+ info-bg +
+
+
+{{template "devtest/devtest-footer"}} diff --git a/templates/devtest/unicode-escape.tmpl b/templates/devtest/unicode-escape.tmpl new file mode 100644 index 0000000000..a61813f5c8 --- /dev/null +++ b/templates/devtest/unicode-escape.tmpl @@ -0,0 +1,17 @@ +{{template "devtest/devtest-header"}} +
+
+ + + {{range $idx, $line := .HighlightLines}} + + + {{ctx.RenderUtils.RenderUnicodeEscapeToggleTd $.EscapeStatus (index $.LineEscapeStatus $idx)}} + + + {{end}} + +
{{$line.FormattedContent}}
+
+
+{{template "devtest/devtest-footer"}} diff --git a/templates/install.tmpl b/templates/install.tmpl index acfafd3cc7..45f14d5c57 100644 --- a/templates/install.tmpl +++ b/templates/install.tmpl @@ -203,24 +203,6 @@ {{ctx.Locale.Tr "install.server_service_title"}} -
-
- - -
-
-
-
- - -
-
-
-
- - -
-
diff --git a/templates/org/header.tmpl b/templates/org/header.tmpl index 040164ba86..31f43449ab 100644 --- a/templates/org/header.tmpl +++ b/templates/org/header.tmpl @@ -7,7 +7,7 @@ {{if .Org.Visibility.IsLimited}}{{ctx.Locale.Tr "org.settings.visibility.limited_shortname"}}{{end}} {{if .Org.Visibility.IsPrivate}}{{ctx.Locale.Tr "org.settings.visibility.private_shortname"}}{{end}} - + {{if .EnableFeed}} {{svg "octicon-rss" 24}} diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl index ea294980e0..b7d037c9e1 100644 --- a/templates/org/home.tmpl +++ b/templates/org/home.tmpl @@ -9,8 +9,16 @@
{{.ProfileReadmeContent}}
{{end}} {{template "shared/repo/search" .}} - {{template "shared/repo/list" .}} - {{template "base/paginate" .}} + {{if not .Repos}} +
+ {{svg "octicon-repo" 48}} +

{{ctx.Locale.Tr "org.repos.empty"}}

+

{{ctx.Locale.Tr "org.repos.empty_description"}}

+
+ {{else}} + {{template "shared/repo/list" .}} + {{template "base/paginate" .}} + {{end}}
{{if .ShowMemberAndTeamTab}} @@ -49,7 +57,7 @@ {{if .NumMembers}}

{{ctx.Locale.Tr "org.members"}} - {{.NumMembers}} {{svg "octicon-chevron-right"}} + {{.NumMembers}} {{svg "octicon-chevron-right"}}

{{$isMember := .IsOrganizationMember}} @@ -63,7 +71,7 @@ {{if .IsOrganizationMember}}
{{range .Teams}} diff --git a/templates/org/member/members.tmpl b/templates/org/member/members.tmpl index 9b9f060770..1cb50f785b 100644 --- a/templates/org/member/members.tmpl +++ b/templates/org/member/members.tmpl @@ -4,6 +4,13 @@
{{template "base/alert" .}} + {{if .IsOrganizationOwner}} +
+
{{ctx.Locale.Tr "org.teams.manage_team_member_prompt"}}
+ {{ctx.Locale.Tr "org.teams.manage_team_member"}} +
+
+ {{end}}
{{range .Members}} {{$isPublic := index $.MembersIsPublicMember .ID}} @@ -15,27 +22,27 @@
{{template "shared/user/name" .}} {{if not $isPublic}} - {{ctx.Locale.Tr "org.members.private"}} + {{ctx.Locale.Tr "org.members.private"}} {{end}}
- {{if not $.PublicOnly}} -
+
+ {{if not $.PublicOnly}} +
{{ctx.Locale.Tr "org.members.member_role"}} {{if index $.MembersIsUserOrgOwner .ID}}{{svg "octicon-shield-lock"}} {{ctx.Locale.Tr "org.members.owner"}}{{else}}{{ctx.Locale.Tr "org.members.member"}}{{end}}
+ {{end}} {{if $.IsOrganizationOwner}} -
- {{ctx.Locale.Tr "admin.users.2fa"}} - - {{if index $.MembersTwoFaStatus .ID}} - {{svg "octicon-check"}} - {{else}} - {{svg "octicon-x"}} - {{end}} - +
+ {{ctx.Locale.Tr "admin.users.2fa"}}: + {{if index $.MembersTwoFaStatus .ID}} + {{svg "octicon-check"}} + {{else}} + {{svg "octicon-x"}} + {{end}}
{{end}} - {{end}} +
{{if or (eq $.SignedUser.ID .ID) $.IsOrganizationOwner}} @@ -46,45 +53,23 @@ {{end}} {{end}} {{if eq $.SignedUser.ID .ID}} -
- -
+ {{else if $.IsOrganizationOwner}} -
- -
+ {{end}}
{{end}}
- {{template "base/paginate" .}}
- - - {{template "base/footer" .}} diff --git a/templates/org/settings/actions_general.tmpl b/templates/org/settings/actions_general.tmpl new file mode 100644 index 0000000000..ebf9482f61 --- /dev/null +++ b/templates/org/settings/actions_general.tmpl @@ -0,0 +1,5 @@ +{{template "org/settings/layout_head" (dict "ctxData" .)}} +
+ {{template "shared/actions/owner_general_settings" .}} +
+{{template "org/settings/layout_footer" .}} diff --git a/templates/org/settings/labels.tmpl b/templates/org/settings/labels.tmpl index 21d7c0ef3c..283b2199cb 100644 --- a/templates/org/settings/labels.tmpl +++ b/templates/org/settings/labels.tmpl @@ -1,6 +1,6 @@ {{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings labels")}}
-
+
{{ctx.Locale.Tr "org.settings.labels_desc"}}
diff --git a/templates/org/settings/navbar.tmpl b/templates/org/settings/navbar.tmpl index 58475de7e7..4c06b2cb1b 100644 --- a/templates/org/settings/navbar.tmpl +++ b/templates/org/settings/navbar.tmpl @@ -26,9 +26,12 @@ {{end}} {{if .EnableActions}} -
+
{{ctx.Locale.Tr "actions.actions"}}