diff --git a/.air.toml b/.air.toml index de97bd8b29..d506c19426 100644 --- a/.air.toml +++ b/.air.toml @@ -2,9 +2,10 @@ root = "." tmp_dir = ".air" [build] +pre_cmd = ["killall -9 gitea 2>/dev/null || true"] # kill off potential zombie processes from previous runs cmd = "make --no-print-directory backend" bin = "gitea" -delay = 1000 +delay = 2000 include_ext = ["go", "tmpl"] include_file = ["main.go"] include_dir = ["cmd", "models", "modules", "options", "routers", "services"] @@ -15,8 +16,11 @@ exclude_dir = [ "modules/avatar/testdata", "modules/git/tests", "modules/migration/file_format_testdata", + "modules/markup/tests/repo/repo1_filepreview", "routers/private/tests", "services/gitdiff/testdata", + "services/migrations/testdata", + "services/webhook/sourcehut/testdata", ] exclude_regex = ["_test.go$", "_gen.go$"] stop_on_error = true diff --git a/.deadcode-out b/.deadcode-out index 5e6f81780f..62458dd6b6 100644 --- a/.deadcode-out +++ b/.deadcode-out @@ -22,7 +22,6 @@ package "code.gitea.io/gitea/models/actions" func (ScheduleList).GetRepoIDs func (ScheduleList).LoadTriggerUser func (ScheduleList).LoadRepos - func GetVariableByID package "code.gitea.io/gitea/models/asymkey" func (ErrGPGKeyAccessDenied).Error diff --git a/.dockerignore b/.dockerignore index 4c14a94620..a1611a1ca5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -77,7 +77,6 @@ cpu.out /public/assets/css /public/assets/fonts /public/assets/img/avatar -/public/assets/img/webpack /vendor /web_src/fomantic/node_modules /web_src/fomantic/build/* @@ -95,6 +94,9 @@ cpu.out /.air /.go-licenses +# Files and folders that were previously generated +/public/assets/img/webpack + # Snapcraft snap/.snapcraft/ parts/ diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 99ce2e97d6..91019cde84 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -3,6 +3,7 @@ reportUnusedDisableDirectives: true ignorePatterns: - /web_src/js/vendor + - /web_src/fomantic parserOptions: sourceType: module @@ -309,7 +310,7 @@ rules: jquery/no-merge: [2] jquery/no-param: [2] jquery/no-parent: [0] - jquery/no-parents: [0] + jquery/no-parents: [2] jquery/no-parse-html: [2] jquery/no-prop: [2] jquery/no-proxy: [2] @@ -318,8 +319,8 @@ rules: jquery/no-show: [2] jquery/no-size: [2] jquery/no-sizzle: [0] - jquery/no-slide: [0] - jquery/no-submit: [0] + jquery/no-slide: [2] + jquery/no-submit: [2] jquery/no-text: [0] jquery/no-toggle: [2] jquery/no-trigger: [0] @@ -457,7 +458,7 @@ rules: no-jquery/no-other-utils: [2] no-jquery/no-param: [2] no-jquery/no-parent: [0] - no-jquery/no-parents: [0] + no-jquery/no-parents: [2] no-jquery/no-parse-html-literal: [0] no-jquery/no-parse-html: [2] no-jquery/no-parse-json: [2] @@ -536,7 +537,7 @@ rules: no-underscore-dangle: [0] no-unexpected-multiline: [2] no-unmodified-loop-condition: [2] - no-unneeded-ternary: [0] + no-unneeded-ternary: [2] no-unreachable-loop: [2] no-unreachable: [2] no-unsafe-finally: [2] @@ -715,12 +716,14 @@ rules: unicorn/import-style: [0] unicorn/new-for-builtins: [2] unicorn/no-abusive-eslint-disable: [0] + unicorn/no-anonymous-default-export: [0] unicorn/no-array-callback-reference: [0] unicorn/no-array-for-each: [2] unicorn/no-array-method-this-argument: [2] unicorn/no-array-push-push: [2] unicorn/no-array-reduce: [2] unicorn/no-await-expression-member: [0] + unicorn/no-await-in-promise-methods: [2] unicorn/no-console-spaces: [0] unicorn/no-document-cookie: [2] unicorn/no-empty-file: [2] @@ -737,6 +740,7 @@ rules: unicorn/no-null: [0] unicorn/no-object-as-default-parameter: [0] unicorn/no-process-exit: [0] + unicorn/no-single-promise-in-promise-methods: [2] unicorn/no-static-only-class: [2] unicorn/no-thenable: [2] unicorn/no-this-assignment: [2] diff --git a/.forgejo/workflows/backport.yml b/.forgejo/workflows/backport.yml index a51633ed9b..6181dcf352 100644 --- a/.forgejo/workflows/backport.yml +++ b/.forgejo/workflows/backport.yml @@ -45,39 +45,13 @@ jobs: cat <<'EOF' ${{ toJSON(github) }} EOF - - name: Fetch labels - id: fetch-labels - shell: bash - run: | - set -x - echo "Labels retrieved below" - export DEBIAN_FRONTEND=noninteractive - apt-get update -qq - apt-get -q install -qq -y jq - filtered_labels=$(echo "$LABELS" | jq -c 'map(select(.name | startswith("backport/v")))') - echo "FILTERED_LABELS=${filtered_labels}" >> $GITHUB_ENV - env: - LABELS: ${{ toJSON(github.event.pull_request.labels) }} - - name: Extract targets - id: extract-targets - shell: bash - run: | - set -x - targets="$(echo $FILTERED_LABELS | jq -c '[.[] | .name | sub("backport/"; "")]')" - echo "targets=$(echo $targets)" >> $GITHUB_OUTPUT - - - name: Printing info - shell: bash - run: | - echo "targets: ${{ steps.extract-targets.outputs.targets }}" - echo "target-branch: ${{ fromJSON(steps.extract-targets.outputs.targets)[0] }}" - echo "pull-request: ${{ github.event.pull_request.url }}" - - - uses: https://code.forgejo.org/actions/git-backporting@v4.6.0 + - uses: https://code.forgejo.org/actions/git-backporting@v4.8.0 with: - target-branch: ${{ fromJSON(steps.extract-targets.outputs.targets)[0] }}/forgejo + target-branch-pattern: "^backport/(?(v.*))$" strategy: ort strategy-option: find-renames - no-squash: true + cherry-pick-options: -x auth: ${{ secrets.BACKPORT_TOKEN }} pull-request: ${{ github.event.pull_request.url }} + auto-no-squash: true + enable-err-notification: true diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index 1a5cc15188..6ce7141004 100644 --- a/.forgejo/workflows/renovate.yml +++ b/.forgejo/workflows/renovate.yml @@ -1,3 +1,8 @@ +# +# Runs every 2 hours, but Renovate is limited to create new PR before 4am. +# See renovate.json for more settings. +# Automerge is enabled for Renovate PR's but need to be approved before. +# name: renovate on: @@ -5,7 +10,7 @@ on: branches: - 'renovate/**' # self-test updates schedule: - - cron: '*/30 * * * *' + - cron: '0 0/2 * * *' env: RENOVATE_DRY_RUN: ${{ (github.event_name != 'schedule' && github.ref_name != github.event.repository.default_branch) && 'full' || '' }} @@ -17,10 +22,11 @@ jobs: runs-on: docker container: - image: ghcr.io/visualon/renovate:37.272.0 + image: ghcr.io/visualon/renovate:37.330.1 steps: - - uses: https://code.forgejo.org/actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + - name: Load renovate repo cache + uses: https://code.forgejo.org/actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: | .tmp/cache/renovate/repository @@ -28,7 +34,8 @@ jobs: restore-keys: | repo-cache- - - run: renovate + - name: Run renovate + run: renovate env: GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_COM_TOKEN }} LOG_LEVEL: debug @@ -45,7 +52,7 @@ jobs: GIT_COMMITTER_EMAIL: 'forgejo-renovate-action@forgejo.org' - name: Save renovate repo cache - if: always() && env.RENOVATE_DRY_RUN == 'true' + if: always() && env.RENOVATE_DRY_RUN != 'full' uses: https://code.forgejo.org/actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: | diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml index 125cb798dc..cd955c01af 100644 --- a/.forgejo/workflows/testing.yml +++ b/.forgejo/workflows/testing.yml @@ -46,7 +46,7 @@ jobs: image: 'docker.io/node:20-bookworm' services: minio: - image: bitnami/minio:2024.2.26 + image: bitnami/minio:2024.3.30 options: >- --hostname gitea.minio env: @@ -136,10 +136,12 @@ jobs: image: 'docker.io/node:20-bookworm' services: minio: - image: bitnami/minio:2024.2.26 + image: bitnami/minio:2024.3.30 env: MINIO_ROOT_USER: 123456 MINIO_ROOT_PASSWORD: 12345678 + ldap: + image: docker.io/gitea/test-openldap:latest pgsql: image: 'docker.io/postgres:15' env: @@ -176,6 +178,7 @@ jobs: TAGS: bindata RACE_ENABLED: true USE_REPO_TEST_DIR: 1 + TEST_LDAP: 1 test-sqlite: if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} runs-on: docker diff --git a/.gitattributes b/.gitattributes index 51131c7d83..4e748c071a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ * text=auto eol=lf *.tmpl linguist-language=go-html-template +*.pb.go linguist-generated /assets/*.json linguist-generated /public/assets/img/svg/*.svg linguist-generated /templates/swagger/v1_json.tmpl linguist-generated diff --git a/.gitea/issue_template/bug-report.yaml b/.gitea/issue_template/bug-report.yaml index 6edbca886f..6fab61fcdc 100644 --- a/.gitea/issue_template/bug-report.yaml +++ b/.gitea/issue_template/bug-report.yaml @@ -87,4 +87,3 @@ body: - SQLite - PostgreSQL - MySQL - - MSSQL diff --git a/.gitignore b/.gitignore index b883e079d1..ebbed981e1 100644 --- a/.gitignore +++ b/.gitignore @@ -83,7 +83,6 @@ cpu.out /public/assets/css /public/assets/fonts /public/assets/licenses.txt -/public/assets/img/webpack /vendor /web_src/fomantic/node_modules /web_src/fomantic/build/* @@ -102,6 +101,9 @@ cpu.out /.go-licenses /.cur-deadcode-out +# Files and folders that were previously generated +/public/assets/img/webpack + # Snapcraft /gitea_a*.txt snap/.snapcraft/ diff --git a/.golangci.yml b/.golangci.yml index 6d52f99401..6d835909fc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,13 +1,14 @@ linters: + enable-all: false + disable-all: true + fast: false enable: - bidichk - # - deadcode # deprecated - https://github.com/golangci/golangci-lint/issues/1841 - depguard - dupl - errcheck - forbidigo - gocritic - # - gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time. - gofmt - gofumpt - gosimple @@ -17,16 +18,11 @@ linters: - nolintlint - revive - staticcheck - # - structcheck # deprecated - https://github.com/golangci/golangci-lint/issues/1841 - stylecheck - typecheck - unconvert - unused - # - varcheck # deprecated - https://github.com/golangci/golangci-lint/issues/1841 - wastedassign - enable-all: false - disable-all: true - fast: false run: timeout: 10m @@ -35,6 +31,9 @@ run: - public - web_src +output: + sort-results: true + linters-settings: stylecheck: checks: ["all", "-ST1005", "-ST1003"] @@ -51,27 +50,37 @@ linters-settings: errorCode: 1 warningCode: 1 rules: + - name: atomic + - name: bare-return - name: blank-imports + - name: constant-logical-expr - name: context-as-argument - name: context-keys-type - name: dot-imports + - name: duplicated-imports + - name: empty-lines + - name: error-naming - name: error-return - name: error-strings - - name: error-naming + - name: errorf - name: exported + - name: identical-branches - name: if-return - name: increment-decrement - - name: var-naming - - name: var-declaration + - name: indent-error-flow + - name: modifies-value-receiver - name: package-comments - name: range - name: receiver-naming + - name: redefines-builtin-id + - name: string-of-int + - name: superfluous-else - name: time-naming + - name: unconditional-recursion - name: unexported-return - - name: indent-error-flow - - name: errorf - - name: duplicated-imports - - name: modifies-value-receiver + - name: unreachable-code + - name: var-declaration + - name: var-naming gofumpt: extra-rules: true depguard: @@ -96,8 +105,12 @@ linters-settings: issues: max-issues-per-linter: 0 max-same-issues: 0 + exclude-dirs: [node_modules, public, web_src] + exclude-case-sensitive: true exclude-rules: - # Exclude some linters from running on tests files. + - path: models/db/sql_postgres_with_schema.go + linters: + - nolintlint - path: _test\.go linters: - gocyclo @@ -115,19 +128,19 @@ issues: - path: cmd linters: - forbidigo - - linters: + - text: "webhook" + linters: - dupl - text: "webhook" - - linters: + - text: "`ID' should not be capitalized" + linters: - gocritic - text: "`ID' should not be capitalized" - - linters: + - text: "swagger" + linters: - unused - deadcode - text: "swagger" - - linters: + - text: "argument x is overwritten before first use" + linters: - staticcheck - text: "argument x is overwritten before first use" - text: "commentFormatting: put a space between `//` and comment text" linters: - gocritic diff --git a/.ignore b/.ignore index 5c945ab981..5b96dabd38 100644 --- a/.ignore +++ b/.ignore @@ -4,6 +4,8 @@ /modules/options/bindata.go /modules/public/bindata.go /modules/templates/bindata.go -/vendor +/options/gitignore +/options/license /public/assets +/vendor node_modules diff --git a/.stylelintrc.yaml b/.stylelintrc.yaml deleted file mode 100644 index 60cce7dbf7..0000000000 --- a/.stylelintrc.yaml +++ /dev/null @@ -1,223 +0,0 @@ -plugins: - - stylelint-declaration-strict-value - - stylelint-declaration-block-no-ignored-properties - - "@stylistic/stylelint-plugin" - -ignoreFiles: - - "**/*.go" - -overrides: - - files: ["**/chroma/*", "**/codemirror/*", "**/standalone/*", "**/console.css", "font_i18n.css"] - rules: - scale-unlimited/declaration-strict-value: null - - files: ["**/chroma/*", "**/codemirror/*"] - rules: - block-no-empty: null - - files: ["**/*.vue"] - customSyntax: postcss-html - -rules: - "@stylistic/at-rule-name-case": null - "@stylistic/at-rule-name-newline-after": null - "@stylistic/at-rule-name-space-after": null - "@stylistic/at-rule-semicolon-newline-after": null - "@stylistic/at-rule-semicolon-space-before": null - "@stylistic/block-closing-brace-empty-line-before": null - "@stylistic/block-closing-brace-newline-after": null - "@stylistic/block-closing-brace-newline-before": null - "@stylistic/block-closing-brace-space-after": null - "@stylistic/block-closing-brace-space-before": null - "@stylistic/block-opening-brace-newline-after": null - "@stylistic/block-opening-brace-newline-before": null - "@stylistic/block-opening-brace-space-after": null - "@stylistic/block-opening-brace-space-before": always - "@stylistic/color-hex-case": lower - "@stylistic/declaration-bang-space-after": never - "@stylistic/declaration-bang-space-before": null - "@stylistic/declaration-block-semicolon-newline-after": null - "@stylistic/declaration-block-semicolon-newline-before": null - "@stylistic/declaration-block-semicolon-space-after": null - "@stylistic/declaration-block-semicolon-space-before": never - "@stylistic/declaration-block-trailing-semicolon": null - "@stylistic/declaration-colon-newline-after": null - "@stylistic/declaration-colon-space-after": null - "@stylistic/declaration-colon-space-before": never - "@stylistic/function-comma-newline-after": null - "@stylistic/function-comma-newline-before": null - "@stylistic/function-comma-space-after": null - "@stylistic/function-comma-space-before": null - "@stylistic/function-max-empty-lines": 0 - "@stylistic/function-parentheses-newline-inside": never-multi-line - "@stylistic/function-parentheses-space-inside": null - "@stylistic/function-whitespace-after": null - "@stylistic/indentation": 2 - "@stylistic/linebreaks": null - "@stylistic/max-empty-lines": 1 - "@stylistic/max-line-length": null - "@stylistic/media-feature-colon-space-after": null - "@stylistic/media-feature-colon-space-before": never - "@stylistic/media-feature-name-case": null - "@stylistic/media-feature-parentheses-space-inside": null - "@stylistic/media-feature-range-operator-space-after": always - "@stylistic/media-feature-range-operator-space-before": always - "@stylistic/media-query-list-comma-newline-after": null - "@stylistic/media-query-list-comma-newline-before": null - "@stylistic/media-query-list-comma-space-after": null - "@stylistic/media-query-list-comma-space-before": null - "@stylistic/named-grid-areas-alignment": null - "@stylistic/no-empty-first-line": null - "@stylistic/no-eol-whitespace": true - "@stylistic/no-extra-semicolons": true - "@stylistic/no-missing-end-of-source-newline": null - "@stylistic/number-leading-zero": null - "@stylistic/number-no-trailing-zeros": null - "@stylistic/property-case": lower - "@stylistic/selector-attribute-brackets-space-inside": null - "@stylistic/selector-attribute-operator-space-after": null - "@stylistic/selector-attribute-operator-space-before": null - "@stylistic/selector-combinator-space-after": null - "@stylistic/selector-combinator-space-before": null - "@stylistic/selector-descendant-combinator-no-non-space": null - "@stylistic/selector-list-comma-newline-after": null - "@stylistic/selector-list-comma-newline-before": null - "@stylistic/selector-list-comma-space-after": always-single-line - "@stylistic/selector-list-comma-space-before": never-single-line - "@stylistic/selector-max-empty-lines": 0 - "@stylistic/selector-pseudo-class-case": lower - "@stylistic/selector-pseudo-class-parentheses-space-inside": never - "@stylistic/selector-pseudo-element-case": lower - "@stylistic/string-quotes": double - "@stylistic/unicode-bom": null - "@stylistic/unit-case": lower - "@stylistic/value-list-comma-newline-after": null - "@stylistic/value-list-comma-newline-before": null - "@stylistic/value-list-comma-space-after": null - "@stylistic/value-list-comma-space-before": null - "@stylistic/value-list-max-empty-lines": 0 - alpha-value-notation: null - annotation-no-unknown: true - at-rule-allowed-list: null - at-rule-disallowed-list: null - at-rule-empty-line-before: null - at-rule-no-unknown: [true, {ignoreAtRules: [tailwind]}] - at-rule-no-vendor-prefix: true - at-rule-property-required-list: null - block-no-empty: true - color-function-notation: null - color-hex-alpha: null - color-hex-length: null - color-named: null - color-no-hex: null - color-no-invalid-hex: true - comment-empty-line-before: null - comment-no-empty: true - comment-pattern: null - comment-whitespace-inside: null - comment-word-disallowed-list: null - custom-media-pattern: null - custom-property-empty-line-before: null - custom-property-no-missing-var-function: true - custom-property-pattern: null - declaration-block-no-duplicate-custom-properties: true - declaration-block-no-duplicate-properties: [true, {ignore: [consecutive-duplicates-with-different-values]}] - declaration-block-no-redundant-longhand-properties: null - declaration-block-no-shorthand-property-overrides: null - declaration-block-single-line-max-declarations: null - declaration-empty-line-before: null - declaration-no-important: null - declaration-property-max-values: null - declaration-property-unit-allowed-list: null - declaration-property-unit-disallowed-list: {line-height: [em]} - declaration-property-value-allowed-list: null - declaration-property-value-disallowed-list: null - declaration-property-value-no-unknown: true - font-family-name-quotes: always-where-recommended - font-family-no-duplicate-names: true - font-family-no-missing-generic-family-keyword: true - font-weight-notation: null - function-allowed-list: null - function-calc-no-unspaced-operator: true - function-disallowed-list: null - function-linear-gradient-no-nonstandard-direction: true - function-name-case: lower - function-no-unknown: true - function-url-no-scheme-relative: null - function-url-quotes: always - function-url-scheme-allowed-list: null - function-url-scheme-disallowed-list: null - hue-degree-notation: null - import-notation: string - keyframe-block-no-duplicate-selectors: true - keyframe-declaration-no-important: true - keyframe-selector-notation: null - keyframes-name-pattern: null - length-zero-no-unit: [true, ignore: [custom-properties], ignoreFunctions: [var]] - max-nesting-depth: null - media-feature-name-allowed-list: null - media-feature-name-disallowed-list: null - media-feature-name-no-unknown: true - media-feature-name-no-vendor-prefix: true - media-feature-name-unit-allowed-list: null - media-feature-name-value-allowed-list: null - media-feature-name-value-no-unknown: true - media-feature-range-notation: null - media-query-no-invalid: true - named-grid-areas-no-invalid: true - no-descending-specificity: null - no-duplicate-at-import-rules: true - no-duplicate-selectors: true - no-empty-source: true - no-invalid-double-slash-comments: true - no-invalid-position-at-import-rule: [true, ignoreAtRules: [tailwind]] - no-irregular-whitespace: true - no-unknown-animations: null - no-unknown-custom-properties: null - number-max-precision: null - plugin/declaration-block-no-ignored-properties: true - property-allowed-list: null - property-disallowed-list: null - property-no-unknown: true - property-no-vendor-prefix: null - rule-empty-line-before: null - rule-selector-property-disallowed-list: null - scale-unlimited/declaration-strict-value: [[/color$/, font-weight], {ignoreValues: /^(inherit|transparent|unset|initial|currentcolor|none)$/, ignoreFunctions: false, disableFix: true, expandShorthand: true}] - selector-anb-no-unmatchable: true - selector-attribute-name-disallowed-list: null - selector-attribute-operator-allowed-list: null - selector-attribute-operator-disallowed-list: null - selector-attribute-quotes: always - selector-class-pattern: null - selector-combinator-allowed-list: null - selector-combinator-disallowed-list: null - selector-disallowed-list: null - selector-id-pattern: null - selector-max-attribute: null - selector-max-class: null - selector-max-combinators: null - selector-max-compound-selectors: null - selector-max-id: null - selector-max-pseudo-class: null - selector-max-specificity: null - selector-max-type: null - selector-max-universal: null - selector-nested-pattern: null - selector-no-qualifying-type: null - selector-no-vendor-prefix: true - selector-not-notation: null - selector-pseudo-class-allowed-list: null - selector-pseudo-class-disallowed-list: null - selector-pseudo-class-no-unknown: true - selector-pseudo-element-allowed-list: null - selector-pseudo-element-colon-notation: double - selector-pseudo-element-disallowed-list: null - selector-pseudo-element-no-unknown: true - selector-type-case: lower - selector-type-no-unknown: [true, {ignore: [custom-elements]}] - shorthand-property-no-redundant-values: true - string-no-newline: true - time-min-milliseconds: null - unit-allowed-list: null - unit-disallowed-list: null - unit-no-unknown: true - value-keyword-case: null - value-no-vendor-prefix: [true, {ignoreValues: [box, inline-box]}] diff --git a/CODEOWNERS b/CODEOWNERS index 10e9d52b35..e30d2c42b4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -16,6 +16,8 @@ web_src/.* @caesar @crystal @gusted # HTML templates used by the backend. templates/.* @caesar @crystal @gusted +## the issue sidebar was touched by fnetx +templates/repo/issue/view_content/sidebar.* @fnetx # Files related to Go development. @@ -30,3 +32,9 @@ models/.* @dachary @earl-warren @gusted # of how Forgejo comes together. It's tedious to write good integration testing # for code that lives in here. routers/.* @dachary @earl-warren @gusted + +# Let new strings be checked by the translation team. +options/locale/locale_en-US.ini @0ko + +# Personal interest +.*/webhook.* @oliverpool diff --git a/Dockerfile.rootless b/Dockerfile.rootless index 3f4cba955a..6d1503f034 100644 --- a/Dockerfile.rootless +++ b/Dockerfile.rootless @@ -100,8 +100,11 @@ ENV GITEA_CUSTOM /var/lib/gitea/custom ENV GITEA_TEMP /tmp/gitea ENV TMPDIR /tmp/gitea -#TODO add to docs the ability to define the ini to load (useful to test and revert a config) -ENV GITEA_APP_INI /etc/gitea/app.ini +# Legacy config file for backwards compatibility +# TODO: remove on next major version release +ENV GITEA_APP_INI_LEGACY /etc/gitea/app.ini + +ENV GITEA_APP_INI ${GITEA_CUSTOM}/conf/app.ini ENV HOME "/var/lib/gitea/git" VOLUME ["/var/lib/gitea", "/etc/gitea"] WORKDIR /var/lib/gitea diff --git a/MAINTAINERS b/MAINTAINERS deleted file mode 100644 index 72171f80ed..0000000000 --- a/MAINTAINERS +++ /dev/null @@ -1,61 +0,0 @@ -Alexey Makhov (@makhov) -Bo-Yi Wu (@appleboy) -Ethan Koenig (@ethantkoenig) -Kees de Vries (@Bwko) -Kim Carlbäcker (@bkcsoft) -LefsFlare (@LefsFlarey) -Lunny Xiao (@lunny) -Rachid Zarouali (@xinity) -Rémy Boulanouar (@DblK) -Sandro Santilli (@strk) -Thibault Meyer (@0xbaadf00d) -Thomas Boerger (@tboerger) -Patrick G (@geek1011) -Antoine Girard (@sapk) -Lauris Bukšis-Haberkorns (@lafriks) -Jonas Östanbäck (@cez81) -David Schneiderbauer (@daviian) -Peter Žeby (@morlinest) -Matti Ranta (@techknowlogick) -Jonas Franz (@jonasfranz) -Alexey Terentyev (@axifive) -Lanre Adelowo (@adelowo) -Konrad Langenberg (@kolaente) -He-Long Zhang (@BetaCat0) -Andrew Thornton (@zeripath) -John Olheiser (@jolheiser) -Richard Mahn (@richmahn) -Mrsdizzie (@mrsdizzie) -silverwind (@silverwind) -Gary Kim (@gary-kim) -Guillermo Prandi (@guillep2k) -Mura Li (@typeless) -6543 <6543@obermui.de> (@6543) -jaqra (@jaqra) -David Svantesson (@davidsvantesson) -a1012112796 <1012112796@qq.com> (@a1012112796) -Karl Heinz Marbaise (@khmarbaise) -Norwin Roosen (@noerw) -Kyle Dumont (@kdumontnu) -Patrick Schratz (@pat-s) -Janis Estelmann (@KN4CK3R) -Steven Kriegler (@justusbunsi) -Jimmy Praet (@jpraet) -Leon Hofmeister (@delvh) -Wim (@42wim) -Jason Song (@wolfogre) -Yarden Shoham (@yardenshoham) -Yu Tian (@Zettat123) -Eddie Yang <576951401@qq.com> (@yp05327) -Dong Ge (@sillyguodong) -Xinyi Gong (@HesterG) -wxiaoguang (@wxiaoguang) -Gary Moon (@garymoon) -Philip Peterson (@philip-peterson) -Denys Konovalov (@denyskon) -Punit Inani (@puni9869) -CaiCandong <1290147055@qq.com> (@caicandong) -Rui Chen (@chenrui333) -Nanguan Lin (@lng2020) -kerwin612 (@kerwin612) -Gary Wang (@BLumia) diff --git a/Makefile b/Makefile index 86b59950af..6d4e3e0703 100644 --- a/Makefile +++ b/Makefile @@ -26,18 +26,18 @@ DIFF ?= diff --unified XGO_VERSION := go-1.21.x -AIR_PACKAGE ?= github.com/cosmtrek/air@v1.49.0 -EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v2/cmd/editorconfig-checker@2.8.0 -GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0 -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.56.1 -GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 -MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1 -SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.6-0.20240201115257-bcc7c78b7786 -XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest -GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 -GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.0.3 -ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.6.26 -DEADCODE_PACKAGE ?= golang.org/x/tools/internal/cmd/deadcode@v0.14.0 +AIR_PACKAGE ?= github.com/cosmtrek/air@v1.49.0 # renovate: datasource=go +EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v2/cmd/editorconfig-checker@2.8.0 # renovate: datasource=go +GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0 # renovate: datasource=go +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2 # renovate: datasource=go +GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 # renovate: datasource=go +MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1 # renovate: datasource=go +SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.6-0.20240201115257-bcc7c78b7786 # renovate: datasource=go +XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest # renovate: datasource=go +GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasource=go +GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go +ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.6.27 # renovate: datasource=go +DEADCODE_PACKAGE ?= golang.org/x/tools/internal/cmd/deadcode@v0.14.0 # renovate: datasource=go DOCKER_IMAGE ?= gitea/gitea DOCKER_TAG ?= latest @@ -129,7 +129,7 @@ FOMANTIC_WORK_DIR := web_src/fomantic WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f) WEBPACK_CONFIGS := webpack.config.js tailwind.config.js WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css -WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/img/webpack +WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST)) @@ -154,9 +154,9 @@ TAR_EXCLUDES := .git data indexers queues log node_modules $(EXECUTABLE) $(FOMAN GO_DIRS := build cmd models modules routers services tests WEB_DIRS := web_src/js web_src/css -ESLINT_FILES := web_src/js tools *.config.js tests/e2e +ESLINT_FILES := web_src/js tools *.js tests/e2e STYLELINT_FILES := web_src/css web_src/js/components/*.vue -SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github +SPELLCHECK_FILES := $(GO_DIRS) $(WEB_DIRS) docs/content templates options/locale/locale_en-US.ini .github $(wildcard *.go *.js *.md *.yml *.yaml *.toml) GO_SOURCES := $(wildcard *.go) GO_SOURCES += $(shell find $(GO_DIRS) -type f -name "*.go" ! -path modules/options/bindata.go ! -path modules/public/bindata.go ! -path modules/templates/bindata.go) @@ -195,10 +195,6 @@ TEST_PGSQL_DBNAME ?= testgitea TEST_PGSQL_USERNAME ?= postgres TEST_PGSQL_PASSWORD ?= postgres TEST_PGSQL_SCHEMA ?= gtestschema -TEST_MSSQL_HOST ?= mssql:1433 -TEST_MSSQL_DBNAME ?= gitea -TEST_MSSQL_USERNAME ?= sa -TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1 .PHONY: all all: build @@ -248,7 +244,7 @@ help: @echo " - show-version-major show major release number only" @echo " - test-frontend test frontend files" @echo " - test-backend test backend files" - @echo " - test-e2e[\#TestSpecificName] test end to end using playwright" + @echo " - test-e2e-sqlite[\#name.test.e2e] test end to end using playwright and sqlite" @echo " - update update js and py dependencies" @echo " - update-js update js dependencies" @echo " - update-py update py dependencies" @@ -308,14 +304,14 @@ clean: e2e*.test \ tests/integration/gitea-integration-* \ tests/integration/indexers-* \ - tests/mysql.ini tests/pgsql.ini tests/mssql.ini man/ \ + tests/mysql.ini tests/pgsql.ini man/ \ tests/e2e/gitea-e2e-*/ \ tests/e2e/indexers-*/ \ tests/e2e/reports/ tests/e2e/test-artifacts/ tests/e2e/test-snapshots/ .PHONY: fmt fmt: - GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}' + @GOFUMPT_PACKAGE=$(GOFUMPT_PACKAGE) $(GO) run build/code-batch-process.go gitea-fmt -w '{file-list}' $(eval TEMPLATES := $(shell find templates -type f -name '*.tmpl')) @# strip whitespace after '{{' or '(' and before '}}' or ')' unless there is only @# whitespace before it @@ -338,8 +334,8 @@ ifneq "$(TAGS)" "$(shell cat $(TAGS_EVIDENCE) 2>/dev/null)" TAGS_PREREQ := $(TAGS_EVIDENCE) endif -OAPI_CODEGEN_PACKAGE ?= github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.12.4 -KIN_OPENAPI_CODEGEN_PACKAGE ?= github.com/getkin/kin-openapi/cmd/validate@v0.114.0 +OAPI_CODEGEN_PACKAGE ?= github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.12.4 # renovate: datasource=go +KIN_OPENAPI_CODEGEN_PACKAGE ?= github.com/getkin/kin-openapi/cmd/validate@v0.114.0 # renovate: datasource=go FORGEJO_API_SERVER = routers/api/forgejo/v1/generated.go .PHONY: generate-forgejo-api @@ -611,27 +607,6 @@ test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql .PHONY: test-pgsql-migration test-pgsql-migration: migrations.pgsql.test migrations.individual.pgsql.test -generate-ini-mssql: - sed -e 's|{{TEST_MSSQL_HOST}}|${TEST_MSSQL_HOST}|g' \ - -e 's|{{TEST_MSSQL_DBNAME}}|${TEST_MSSQL_DBNAME}|g' \ - -e 's|{{TEST_MSSQL_USERNAME}}|${TEST_MSSQL_USERNAME}|g' \ - -e 's|{{TEST_MSSQL_PASSWORD}}|${TEST_MSSQL_PASSWORD}|g' \ - -e 's|{{REPO_TEST_DIR}}|${REPO_TEST_DIR}|g' \ - -e 's|{{TEST_LOGGER}}|$(or $(TEST_LOGGER),test$(COMMA)file)|g' \ - -e 's|{{TEST_TYPE}}|$(or $(TEST_TYPE),integration)|g' \ - tests/mssql.ini.tmpl > tests/mssql.ini - -.PHONY: test-mssql -test-mssql: integrations.mssql.test generate-ini-mssql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test - -.PHONY: test-mssql\#% -test-mssql\#%: integrations.mssql.test generate-ini-mssql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test -test.run $(subst .,/,$*) - -.PHONY: test-mssql-migration -test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test - .PHONY: playwright playwright: deps-frontend npx playwright install $(PLAYWRIGHT_FLAGS) @@ -652,6 +627,10 @@ test-e2e-sqlite: playwright e2e.sqlite.test generate-ini-sqlite test-e2e-sqlite\#%: playwright e2e.sqlite.test generate-ini-sqlite GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini ./e2e.sqlite.test -test.run TestE2e/$* +.PHONY: test-e2e-sqlite-firefox\#% +test-e2e-sqlite-firefox\#%: playwright e2e.sqlite.test generate-ini-sqlite + GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/sqlite.ini PLAYWRIGHT_PROJECT=firefox ./e2e.sqlite.test -test.run TestE2e/$* + .PHONY: test-e2e-mysql test-e2e-mysql: playwright e2e.mysql.test generate-ini-mysql GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./e2e.mysql.test -test.run TestE2e @@ -668,14 +647,6 @@ test-e2e-pgsql: playwright e2e.pgsql.test generate-ini-pgsql test-e2e-pgsql\#%: playwright e2e.pgsql.test generate-ini-pgsql GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./e2e.pgsql.test -test.run TestE2e/$* -.PHONY: test-e2e-mssql -test-e2e-mssql: playwright e2e.mssql.test generate-ini-mssql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test -test.run TestE2e - -.PHONY: test-e2e-mssql\#% -test-e2e-mssql\#%: playwright e2e.mssql.test generate-ini-mssql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./e2e.mssql.test -test.run TestE2e/$* - .PHONY: test-e2e-debugserver test-e2e-debugserver: e2e.sqlite.test generate-ini-sqlite sed -i s/3003/3000/g tests/sqlite.ini @@ -689,10 +660,6 @@ bench-sqlite: integrations.sqlite.test generate-ini-sqlite bench-mysql: integrations.mysql.test generate-ini-mysql GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mysql.ini ./integrations.mysql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . -.PHONY: bench-mssql -bench-mssql: integrations.mssql.test generate-ini-mssql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./integrations.mssql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . - .PHONY: bench-pgsql bench-pgsql: integrations.pgsql.test generate-ini-pgsql GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./integrations.pgsql.test -test.cpuprofile=cpu.out -test.run DontRunTests -test.bench . @@ -711,9 +678,6 @@ integrations.mysql.test: git-check $(GO_SOURCES) integrations.pgsql.test: git-check $(GO_SOURCES) $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.pgsql.test -integrations.mssql.test: git-check $(GO_SOURCES) - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.mssql.test - integrations.sqlite.test: git-check $(GO_SOURCES) $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration -o integrations.sqlite.test -tags '$(TEST_TAGS)' @@ -733,11 +697,6 @@ migrations.pgsql.test: $(GO_SOURCES) generate-ini-pgsql $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.pgsql.test GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini ./migrations.pgsql.test -.PHONY: migrations.mssql.test -migrations.mssql.test: $(GO_SOURCES) generate-ini-mssql - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.mssql.test - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini ./migrations.mssql.test - .PHONY: migrations.sqlite.test migrations.sqlite.test: $(GO_SOURCES) generate-ini-sqlite $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/integration/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)' @@ -763,17 +722,6 @@ migrations.individual.pgsql.test: $(GO_SOURCES) migrations.individual.pgsql.test\#%: $(GO_SOURCES) generate-ini-pgsql GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/pgsql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$* - -.PHONY: migrations.individual.mssql.test -migrations.individual.mssql.test: $(GO_SOURCES) generate-ini-mssql - for pkg in $(MIGRATION_PACKAGES); do \ - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' -test.failfast $$pkg || exit 1; \ - done - -.PHONY: migrations.individual.mssql.test\#% -migrations.individual.mssql.test\#%: $(GO_SOURCES) generate-ini-mssql - GITEA_ROOT="$(CURDIR)" GITEA_CONF=tests/mssql.ini $(GO) test $(GOTESTFLAGS) -tags '$(TEST_TAGS)' code.gitea.io/gitea/models/migrations/$* - .PHONY: migrations.individual.sqlite.test migrations.individual.sqlite.test: $(GO_SOURCES) generate-ini-sqlite for pkg in $(MIGRATION_PACKAGES); do \ @@ -790,9 +738,6 @@ e2e.mysql.test: $(GO_SOURCES) e2e.pgsql.test: $(GO_SOURCES) $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.pgsql.test -e2e.mssql.test: $(GO_SOURCES) - $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.mssql.test - e2e.sqlite.test: $(GO_SOURCES) $(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/tests/e2e -o e2e.sqlite.test -tags '$(TEST_TAGS)' @@ -968,8 +913,9 @@ webpack: $(WEBPACK_DEST) $(WEBPACK_DEST): $(WEBPACK_SOURCES) $(WEBPACK_CONFIGS) package-lock.json @$(MAKE) -s node-check node_modules - rm -rf $(WEBPACK_DEST_ENTRIES) - npx webpack + @rm -rf $(WEBPACK_DEST_ENTRIES) + @echo "Running webpack..." + @BROWSERSLIST_IGNORE_OLD_DATA=true npx webpack @touch $(WEBPACK_DEST) .PHONY: svg diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 927d56852b..6d583f0eff 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -4,6 +4,10 @@ A minor or major Forgejo release is published every [three months](https://forge A [patch or minor release](https://semver.org/spec/v2.0.0.html) (e.g. upgrading from v7.0.0 to v7.0.1 or v7.1.0) does not require manual intervention. But [major releases](https://semver.org/spec/v2.0.0.html#spec-item-8) where the first version number changes (e.g. upgrading from v1.21 to v7.0) contain breaking changes and the release notes explain how to deal with them. +## Upcoming releases (not available yet) + +- [8.0.0](/release-notes/8.0.0/) + ## 7.0.2 This is a bug fix release. See the documentation for more information on the [upgrade procedure](https://forgejo.org/docs/v7.0/admin/upgrade/). diff --git a/assets/go-licenses.json b/assets/go-licenses.json index d1062d747b..62971df47f 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -294,11 +294,6 @@ "path": "github.com/davecgh/go-spew/spew/LICENSE", "licenseText": "ISC License\n\nCopyright (c) 2012-2016 Dave Collins \u003cdave@davec.name\u003e\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n" }, - { - "name": "github.com/denisenkom/go-mssqldb", - "path": "github.com/denisenkom/go-mssqldb/LICENSE.txt", - "licenseText": "Copyright (c) 2012 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/dgryski/go-rendezvous", "path": "github.com/dgryski/go-rendezvous/LICENSE", @@ -504,16 +499,6 @@ "path": "github.com/golang-jwt/jwt/v5/LICENSE", "licenseText": "Copyright (c) 2012 Dave Grijalva\nCopyright (c) 2021 golang-jwt maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n" }, - { - "name": "github.com/golang-sql/civil", - "path": "github.com/golang-sql/civil/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/golang-sql/sqlexp", - "path": "github.com/golang-sql/sqlexp/LICENSE", - "licenseText": "Copyright (c) 2017 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/golang/geo", "path": "github.com/golang/geo/LICENSE", @@ -764,11 +749,6 @@ "path": "github.com/minio/minio-go/v7/LICENSE", "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, - { - "name": "github.com/minio/sha256-simd", - "path": "github.com/minio/sha256-simd/LICENSE", - "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" - }, { "name": "github.com/mitchellh/mapstructure", "path": "github.com/mitchellh/mapstructure/LICENSE", @@ -954,11 +934,6 @@ "path": "github.com/syndtr/goleveldb/leveldb/LICENSE", "licenseText": "Copyright 2012 Suryandaru Triandana \u003csyndtr@gmail.com\u003e\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\nnotice, this list of conditions and the following disclaimer in the\ndocumentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, - { - "name": "github.com/tstranex/u2f", - "path": "github.com/tstranex/u2f/LICENSE", - "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 The Go FIDO U2F Library 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/ulikunitz/xz", "path": "github.com/ulikunitz/xz/LICENSE", diff --git a/build/backport-locales.go b/build/backport-locales.go index d112dd72bd..3df83ea6d9 100644 --- a/build/backport-locales.go +++ b/build/backport-locales.go @@ -18,8 +18,8 @@ import ( func main() { if len(os.Args) != 2 { - println("usage: backport-locales ") - println("eg: backport-locales release/v1.19") + fmt.Println("usage: backport-locales ") + fmt.Println("eg: backport-locales release/v1.19") os.Exit(1) } diff --git a/build/code-batch-process.go b/build/code-batch-process.go index b3ee399420..cc2ab68026 100644 --- a/build/code-batch-process.go +++ b/build/code-batch-process.go @@ -69,6 +69,7 @@ func newFileCollector(fileFilter string, batchSize int) (*fileCollector, error) co.includePatterns = append(co.includePatterns, regexp.MustCompile(`.*\.go$`)) co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`.*\bbindata\.go$`)) + co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`\.pb\.go$`)) co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/gitea-repositories-meta`)) co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`tests/integration/migration-test`)) co.excludePatterns = append(co.excludePatterns, regexp.MustCompile(`modules/git/tests`)) @@ -203,17 +204,6 @@ Example: `, "file-batch-exec") } -func getGoVersion() string { - goModFile, err := os.ReadFile("go.mod") - if err != nil { - log.Fatalf(`Faild to read "go.mod": %v`, err) - os.Exit(1) - } - goModVersionRegex := regexp.MustCompile(`go \d+\.\d+`) - goModVersionLine := goModVersionRegex.Find(goModFile) - return string(goModVersionLine[3:]) -} - func newFileCollectorFromMainOptions(mainOptions map[string]string) (fc *fileCollector, err error) { fileFilter := mainOptions["file-filter"] if fileFilter == "" { @@ -278,7 +268,8 @@ func main() { log.Print("the -d option is not supported by gitea-fmt") } cmdErrors = append(cmdErrors, giteaFormatGoImports(files, containsString(subArgs, "-w"))) - cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra", "-lang", getGoVersion()}, substArgs...))) + cmdErrors = append(cmdErrors, passThroughCmd("gofmt", append([]string{"-w", "-r", "interface{} -> any"}, substArgs...))) + cmdErrors = append(cmdErrors, passThroughCmd("go", append([]string{"run", os.Getenv("GOFUMPT_PACKAGE"), "-extra"}, substArgs...))) default: log.Fatalf("unknown cmd: %s %v", subCmd, subArgs) } diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go index ec92e342d4..4777a92908 100644 --- a/cmd/admin_auth.go +++ b/cmd/admin_auth.go @@ -4,6 +4,7 @@ package cmd import ( + "errors" "fmt" "os" "text/tabwriter" @@ -91,7 +92,7 @@ func runListAuth(c *cli.Context) error { func runDeleteAuth(c *cli.Context) error { if !c.IsSet("id") { - return fmt.Errorf("--id flag is missing") + return errors.New("--id flag is missing") } ctx, cancel := installSignals() diff --git a/cmd/admin_auth_oauth.go b/cmd/admin_auth_oauth.go index c151c0af27..8e6239ac33 100644 --- a/cmd/admin_auth_oauth.go +++ b/cmd/admin_auth_oauth.go @@ -4,6 +4,7 @@ package cmd import ( + "errors" "fmt" "net/url" @@ -193,7 +194,7 @@ func runAddOauth(c *cli.Context) error { func runUpdateOauth(c *cli.Context) error { if !c.IsSet("id") { - return fmt.Errorf("--id flag is missing") + return errors.New("--id flag is missing") } ctx, cancel := installSignals() diff --git a/cmd/admin_auth_stmp.go b/cmd/admin_auth_stmp.go index 58a6e2ac22..d724746905 100644 --- a/cmd/admin_auth_stmp.go +++ b/cmd/admin_auth_stmp.go @@ -5,7 +5,6 @@ package cmd import ( "errors" - "fmt" "strings" auth_model "code.gitea.io/gitea/models/auth" @@ -166,7 +165,7 @@ func runAddSMTP(c *cli.Context) error { func runUpdateSMTP(c *cli.Context) error { if !c.IsSet("id") { - return fmt.Errorf("--id flag is missing") + return errors.New("--id flag is missing") } ctx, cancel := installSignals() diff --git a/cmd/admin_user_delete.go b/cmd/admin_user_delete.go index 1cbc6f7527..520557554a 100644 --- a/cmd/admin_user_delete.go +++ b/cmd/admin_user_delete.go @@ -4,6 +4,7 @@ package cmd import ( + "errors" "fmt" "strings" @@ -42,7 +43,7 @@ var microcmdUserDelete = &cli.Command{ func runDeleteUser(c *cli.Context) error { if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") { - return fmt.Errorf("You must provide the id, username or email of a user to delete") + return errors.New("You must provide the id, username or email of a user to delete") } ctx, cancel := installSignals() diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index 6e78939680..6c2c10494e 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -4,6 +4,7 @@ package cmd import ( + "errors" "fmt" auth_model "code.gitea.io/gitea/models/auth" @@ -42,7 +43,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{ func runGenerateAccessToken(c *cli.Context) error { if !c.IsSet("username") { - return fmt.Errorf("You must provide a username to generate a token for") + return errors.New("You must provide a username to generate a token for") } ctx, cancel := installSignals() @@ -68,7 +69,7 @@ func runGenerateAccessToken(c *cli.Context) error { return err } if exist { - return fmt.Errorf("access token name has been used already") + return errors.New("access token name has been used already") } // make sure the scopes are valid diff --git a/cmd/doctor_convert.go b/cmd/doctor_convert.go index 48c835ad0e..190b2fc2ef 100644 --- a/cmd/doctor_convert.go +++ b/cmd/doctor_convert.go @@ -17,7 +17,7 @@ import ( 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", + Description: "A command to convert an existing MySQL database from utf8 to utf8mb4", Action: runDoctorConvert, } @@ -35,21 +35,14 @@ func runDoctorConvert(ctx *cli.Context) error { log.Info("Log path: %s", setting.Log.RootPath) log.Info("Configuration file: %s", setting.CustomConf) - switch { - case setting.Database.Type.IsMySQL(): + if setting.Database.Type.IsMySQL() { if err := db.ConvertDatabaseTable(); err != nil { log.Fatal("Failed to convert database & table: %v", err) return err } fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4") - case setting.Database.Type.IsMSSQL(): - if err := db.ConvertVarcharToNVarchar(); err != nil { - log.Fatal("Failed to convert database from varchar to nvarchar: %v", err) - return err - } - fmt.Println("Converted successfully, please confirm your database's all columns character is NVARCHAR now") - default: - fmt.Println("This command can only be used with a MySQL or MSSQL database") + } else { + fmt.Println("This command can only be used with a MySQL database") } return nil diff --git a/cmd/dump.go b/cmd/dump.go index 3ea92aa112..0a18adb27d 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -128,7 +128,7 @@ It can be used for backup and capture Forgejo server image to send to maintainer &cli.StringFlag{ Name: "database", Aliases: []string{"d"}, - Usage: "Specify the database SQL syntax: sqlite3, mysql, mssql, postgres", + Usage: "Specify the database SQL syntax: sqlite3, mysql, postgres", }, &cli.BoolFlag{ Name: "skip-repository", diff --git a/cmd/embedded.go b/cmd/embedded.go index 71d483d11c..9f03f7be7c 100644 --- a/cmd/embedded.go +++ b/cmd/embedded.go @@ -157,9 +157,9 @@ func runViewDo(c *cli.Context) error { } if len(matchedAssetFiles) == 0 { - return fmt.Errorf("no files matched the given pattern") + return errors.New("no files matched the given pattern") } else if len(matchedAssetFiles) > 1 { - return fmt.Errorf("too many files matched the given pattern, try to be more specific") + return errors.New("too many files matched the given pattern, try to be more specific") } data, err := matchedAssetFiles[0].fs.ReadFile(matchedAssetFiles[0].name) @@ -180,7 +180,7 @@ func runExtractDo(c *cli.Context) error { } if c.NArg() == 0 { - return fmt.Errorf("a list of pattern of files to extract is mandatory (e.g. '**' for all)") + return errors.New("a list of pattern of files to extract is mandatory (e.g. '**' for all)") } destdir := "." diff --git a/cmd/hook.go b/cmd/hook.go index 04df7ce18c..ff3059f9df 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -482,7 +482,7 @@ func hookPrintResults(results []private.HookPostReceiveBranchResult) { fmt.Fprintf(os.Stderr, " %s\n", res.URL) } fmt.Fprintln(os.Stderr, "") - os.Stderr.Sync() + _ = os.Stderr.Sync() } } @@ -782,7 +782,7 @@ func writeFlushPktLine(ctx context.Context, out io.Writer) error { return nil } -// Write an Pkt-Line based on `data` to `out` according to the specifcation. +// Write an Pkt-Line based on `data` to `out` according to the specification. // https://git-scm.com/docs/protocol-common func writeDataPktLine(ctx context.Context, out io.Writer, data []byte) error { // Implementations SHOULD NOT send an empty pkt-line ("0004"). diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go index 2c701f2672..6049b00d5e 100644 --- a/cmd/manager_logging.go +++ b/cmd/manager_logging.go @@ -4,6 +4,7 @@ package cmd import ( + "errors" "fmt" "os" @@ -249,7 +250,7 @@ func runAddFileLogger(c *cli.Context) error { if c.IsSet("filename") { vals["filename"] = c.String("filename") } else { - return fmt.Errorf("filename must be set when creating a file logger") + return errors.New("filename must be set when creating a file logger") } if c.IsSet("rotate") { vals["rotate"] = c.Bool("rotate") diff --git a/cmd/serv.go b/cmd/serv.go index d5c54f91b8..0e3006b36b 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -136,7 +136,7 @@ func runServ(c *cli.Context) error { setup(ctx, c.Bool("debug")) if setting.SSH.Disabled { - println("Forgejo: SSH has been disabled") + fmt.Println("Forgejo: SSH has been disabled") return nil } @@ -164,13 +164,13 @@ func runServ(c *cli.Context) error { } switch key.Type { case asymkey_model.KeyTypeDeploy: - println("Hi there! You've successfully authenticated with the deploy key named " + key.Name + ", but Forgejo does not provide shell access.") + fmt.Println("Hi there! You've successfully authenticated with the deploy key named " + key.Name + ", but Forgejo does not provide shell access.") case asymkey_model.KeyTypePrincipal: - println("Hi there! You've successfully authenticated with the principal " + key.Content + ", but Forgejo does not provide shell access.") + fmt.Println("Hi there! You've successfully authenticated with the principal " + key.Content + ", but Forgejo does not provide shell access.") default: - println("Hi there, " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Forgejo does not provide shell access.") + fmt.Println("Hi there, " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Forgejo does not provide shell access.") } - println("If this is unexpected, please log in with password and setup Forgejo under another user.") + fmt.Println("If this is unexpected, please log in with password and setup Forgejo under another user.") return nil } else if c.Bool("debug") { log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND")) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 72dcaddb60..4eee2cd1ff 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -407,8 +407,8 @@ USER = root ;; Database connection max life time, default is 0 or 3s mysql (See #6804 & #7071 for reasoning) ;CONN_MAX_LIFETIME = 3s ;; -;; Database maximum number of open connections, default is 0 meaning no maximum -;MAX_OPEN_CONNS = 0 +;; Database maximum number of open connections, default is 100 which is the lowest default from Postgres (MariaDB + MySQL default to 151). Ensure you only increase the value if you configured your database server accordingly. +;MAX_OPEN_CONNS = 100 ;; ;; Whether execute database models migrations automatically ;AUTO_MIGRATION = true @@ -1503,6 +1503,11 @@ LEVEL = Info ;; - manage_ssh_keys: a user cannot configure ssh keys ;; - manage_gpg_keys: a user cannot configure gpg keys ;USER_DISABLED_FEATURES = +;; Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior. +;; - deletion: a user cannot delete their own account +;; - manage_ssh_keys: a user cannot configure ssh keys +;; - manage_gpg_keys: a user cannot configure gpg keys +;;EXTERNAL_USER_DISABLE_FEATURES = ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -2326,6 +2331,8 @@ LEVEL = Info ;SHOW_FOOTER_VERSION = true ;; Show template execution time in the footer ;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true +;; Show the "powered by" text in the footer +;SHOW_FOOTER_POWERED_BY = true ;; Generate sitemap. Defaults to `true`. ;ENABLE_SITEMAP = true ;; Enable/Disable RSS/Atom feed diff --git a/docker/rootless/usr/local/bin/docker-entrypoint.sh b/docker/rootless/usr/local/bin/docker-entrypoint.sh index ca509214bf..e5fa41cc78 100755 --- a/docker/rootless/usr/local/bin/docker-entrypoint.sh +++ b/docker/rootless/usr/local/bin/docker-entrypoint.sh @@ -13,5 +13,10 @@ fi if [ $# -gt 0 ]; then exec "$@" else + # TODO: remove on next major version release + # Honour legacy config file if existing + if [ -f ${GITEA_APP_INI_LEGACY} ]; then + GITEA_APP_INI=${GITEA_APP_INI_LEGACY} + fi exec /usr/local/bin/gitea -c ${GITEA_APP_INI} web fi diff --git a/docker/rootless/usr/local/bin/docker-setup.sh b/docker/rootless/usr/local/bin/docker-setup.sh index b480685863..09bbeabc63 100755 --- a/docker/rootless/usr/local/bin/docker-setup.sh +++ b/docker/rootless/usr/local/bin/docker-setup.sh @@ -11,6 +11,18 @@ mkdir -p ${GITEA_CUSTOM} && chmod 0700 ${GITEA_CUSTOM} mkdir -p ${GITEA_TEMP} && chmod 0700 ${GITEA_TEMP} if [ ! -w ${GITEA_TEMP} ]; then echo "${GITEA_TEMP} is not writable"; exit 1; fi +# TODO: remove on next major version release +# Honour legacy config file if existing, but inform the user +if [ -f ${GITEA_APP_INI_LEGACY} ] && [ ${GITEA_APP_INI} != ${GITEA_APP_INI_LEGACY} ]; then + GITEA_APP_INI_DEFAULT=/var/lib/gitea/custom/conf/app.ini + echo -e \ + "\033[33mWARNING\033[0m: detected configuration file in deprecated default path ${GITEA_APP_INI_LEGACY}." \ + "The new default is ${GITEA_APP_INI_DEFAULT}. To remove this warning, choose one of the options:\n" \ + "* Move ${GITEA_APP_INI_LEGACY} to ${GITEA_APP_INI_DEFAULT} (or to \$GITEA_APP_INI if you want to override this variable)\n" \ + "* Explicitly override GITEA_APP_INI=${GITEA_APP_INI_LEGACY} in the container environment" + GITEA_APP_INI=${GITEA_APP_INI_LEGACY} +fi + #Prepare config file if [ ! -f ${GITEA_APP_INI} ]; then diff --git a/go.mod b/go.mod index 5819522902..9dc8ae3f5b 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( code.gitea.io/gitea-vet v0.2.3 code.gitea.io/sdk/gitea v0.17.1 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 - connectrpc.com/connect v1.15.0 + connectrpc.com/connect v1.16.1 gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669 gitea.com/go-chi/cache v0.2.0 gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 @@ -15,6 +15,7 @@ require ( gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121 github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 + github.com/ProtonMail/go-crypto v1.0.0 github.com/PuerkitoBio/goquery v1.8.1 github.com/alecthomas/chroma/v2 v2.13.0 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb @@ -22,13 +23,12 @@ require ( github.com/buildkite/terminal-to-html/v3 v3.10.1 github.com/caddyserver/certmagic v0.20.0 github.com/chi-middleware/proxy v1.1.1 - github.com/denisenkom/go-mssqldb v0.12.3 github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 github.com/djherbis/buffer v1.2.0 github.com/djherbis/nio/v3 v3.0.1 github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 github.com/dustin/go-humanize v1.0.1 - github.com/editorconfig/editorconfig-core-go/v2 v2.6.1 + github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 github.com/emersion/go-imap v1.2.1 github.com/emirpasic/gods v1.18.1 github.com/felixge/fgprof v0.9.4 @@ -65,7 +65,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 - github.com/klauspost/compress v1.17.7 + github.com/klauspost/compress v1.17.8 github.com/klauspost/cpuid/v2 v2.2.6 github.com/lib/pq v1.10.9 github.com/markbates/goth v1.78.0 @@ -74,7 +74,7 @@ require ( github.com/meilisearch/meilisearch-go v0.26.1 github.com/mholt/archiver/v3 v3.5.1 github.com/microcosm-cc/bluemonday v1.0.26 - github.com/minio/minio-go/v7 v7.0.69 + github.com/minio/minio-go/v7 v7.0.70 github.com/msteinert/pam v1.2.0 github.com/nektos/act v0.2.52 github.com/niklasfasching/go-org v1.7.0 @@ -90,14 +90,13 @@ require ( github.com/sassoftware/go-rpmutils v0.2.1-0.20240124161140-277b154961dd github.com/sergi/go-diff v1.3.1 github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.0 - github.com/tstranex/u2f v1.0.0 github.com/ulikunitz/xz v0.5.11 - github.com/urfave/cli/v2 v2.27.1 + github.com/urfave/cli/v2 v2.27.2 github.com/xanzy/go-gitlab v0.96.0 github.com/yohcop/openid-go v1.0.1 - github.com/yuin/goldmark v1.6.0 + github.com/yuin/goldmark v1.7.0 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-meta v1.1.0 golang.org/x/crypto v0.21.0 @@ -131,7 +130,6 @@ require ( github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/RoaringBitmap/roaring v1.7.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect @@ -163,7 +161,7 @@ require ( github.com/couchbase/go-couchbase v0.1.1 // indirect github.com/couchbase/gomemcached v0.3.0 // indirect github.com/couchbase/goutils v0.1.2 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidmz/go-pageant v1.0.2 // indirect @@ -193,8 +191,6 @@ require ( github.com/go-webauthn/x v0.1.6 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect - github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -226,7 +222,6 @@ require ( github.com/mholt/acmez v1.2.0 // indirect github.com/miekg/dns v1.1.58 // indirect github.com/minio/md5-simd v1.1.2 // indirect - github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -247,8 +242,8 @@ require ( github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.46.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/rhysd/actionlint v1.6.26 // indirect - github.com/rivo/uniseg v0.4.4 // indirect + github.com/rhysd/actionlint v1.6.27 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rs/xid v1.5.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -274,9 +269,9 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect + github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect github.com/zeebo/blake3 v0.2.3 // indirect - go.etcd.io/bbolt v1.3.8 // indirect + go.etcd.io/bbolt v1.3.9 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect go.opentelemetry.io/otel v1.22.0 // indirect go.opentelemetry.io/otel/trace v1.22.0 // indirect @@ -284,7 +279,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect - golang.org/x/mod v0.14.0 // indirect + golang.org/x/mod v0.16.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/appengine v1.6.8 // indirect @@ -298,7 +293,7 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 -replace github.com/nektos/act => gitea.com/gitea/act v0.259.1 +replace github.com/nektos/act => gitea.com/gitea/act v0.261.1 replace github.com/gorilla/feeds => github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5 diff --git a/go.sum b/go.sum index b029072e7d..a42bce3812 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,8 @@ code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8= code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM= 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.15.0 h1:lFdeCbZrVVDydAqwr4xGV2y+ULn+0Z73s5JBj2LikWo= -connectrpc.com/connect v1.15.0/go.mod h1:bQmjpDY8xItMnttnurVgOkHUBMRT9cpsNi2O4AjKhmA= +connectrpc.com/connect v1.16.1 h1:rOdrK/RTI/7TVnn3JsVxt3n028MlTRwmK5Q4heSpjis= +connectrpc.com/connect v1.16.1/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -52,8 +52,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= -gitea.com/gitea/act v0.259.1 h1:8GG1o/xtUHl3qjn5f0h/2FXrT5ubBn05TJOM5ry+FBw= -gitea.com/gitea/act v0.259.1/go.mod h1:UxZWRYqQG2Yj4+4OqfGWW5a3HELwejyWFQyU7F1jUD8= +gitea.com/gitea/act v0.261.1 h1:iACWLc/k8wct9fCF2WdYKqn2Hxx6NjW9zbOP79HF4H4= +gitea.com/gitea/act v0.261.1/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok= gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669 h1:RUBX+MK/TsDxpHmymaOaydfigEbbzqUnG1OTZU/HAeo= gitea.com/go-chi/binding v0.0.0-20230415142243-04b515c6d669/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc= gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w= @@ -70,9 +70,6 @@ github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121 h1:r3qt8PCHnfjOv9PN3H github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121/go.mod h1:Ock8XgA7pvULhIaHGAk/cDnRfNrF9Jey81nPcc403iU= github.com/6543/go-version v1.3.1 h1:HvOp+Telns7HWJ2Xo/05YXQSB2bE0WmVgbHqwMPZT4U= github.com/6543/go-version v1.3.1/go.mod h1:oqFAHCwtLVUTLdhQmVZWYvaHXTdsbB4SY85at64SQEo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -206,8 +203,8 @@ github.com/couchbase/gomemcached v0.3.0 h1:XkMDdP6w7rtvLijDE0/RhcccX+XvAk5cboyBv github.com/couchbase/gomemcached v0.3.0/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo= github.com/couchbase/goutils v0.1.2 h1:gWr8B6XNWPIhfalHNog3qQKfGiYyh4K4VhO3P2o9BCs= github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE= -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= @@ -236,14 +233,13 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/editorconfig/editorconfig-core-go/v2 v2.6.1 h1:iPCqofzMO41WVbcS/B5Ym7AwHQg9cyQ7Ie/R2XU5L3A= -github.com/editorconfig/editorconfig-core-go/v2 v2.6.1/go.mod h1:VY4oyqUnpULFB3SCRpl24GFDIN1PmfiQIvN/G4ScSNg= +github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 h1:dKG8sc7n321deIVRcQtwlMNoBEra7j0qQ8RwxO8RN0w= +github.com/editorconfig/editorconfig-core-go/v2 v2.6.2/go.mod h1:7dvD3GCm7eBw53xZ/lsiq72LqobdMg3ITbMBxnmJmqY= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= @@ -376,7 +372,6 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= 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= @@ -556,8 +551,8 @@ github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -623,10 +618,8 @@ github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= 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.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0= -github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ= -github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= -github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/minio/minio-go/v7 v7.0.70 h1:1u9NtMgfK1U42kUxcsl5v0yj6TEOPR497OAQxpJnn2g= +github.com/minio/minio-go/v7 v7.0.70/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -640,7 +633,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM= @@ -691,7 +683,6 @@ github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= 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= @@ -715,11 +706,11 @@ github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwy github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rhysd/actionlint v1.6.26 h1:zi7jPZf3Ks14gCXYAAL47uBziyFlX7+Xwilqhexct9g= -github.com/rhysd/actionlint v1.6.26/go.mod h1:TIj1DlCgtYLOv5CH9wCK+WJTOr1qAdnFzkGi0IgSCO4= +github.com/rhysd/actionlint v1.6.27 h1:xxwe8YmveBcC8lydW6GoHMGmB6H/MTqUU60F2p10wjw= +github.com/rhysd/actionlint v1.6.27/go.mod h1:m2nFUjAnOrxCMXuOMz9evYBRCLUsMnKY2IJl/N5umbk= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -792,8 +783,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= @@ -801,8 +793,6 @@ github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpP github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= -github.com/tstranex/u2f v1.0.0 h1:HhJkSzDDlVSVIVt7pDJwCHQj67k7A5EeBgPmeD+pVsQ= -github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= @@ -810,8 +800,8 @@ github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= 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/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= -github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= +github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= @@ -833,8 +823,8 @@ github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgk github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= -github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI= -github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= +github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5 h1:3seWKGVhGoc66Ht5QlhQsr4xT2caDnFegsnh2NqvENU= github.com/yardenshoham/feeds v0.0.0-20240110072658-f3d0c21c0bd5/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js= @@ -846,8 +836,8 @@ github.com/yuin/goldmark v1.1.32/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.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= -github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA= +github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= 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= @@ -858,8 +848,8 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= -go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= @@ -887,7 +877,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U 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-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -936,8 +925,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -969,7 +958,6 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1264,7 +1252,6 @@ 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-20210107192922-496545a6307b/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= diff --git a/models/actions/run.go b/models/actions/run.go index 493bd0173c..397455e41d 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -98,13 +98,10 @@ func (run *ActionRun) LoadAttributes(ctx context.Context) error { return nil } - if run.Repo == nil { - repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID) - if err != nil { - return err - } - run.Repo = repo + if err := run.LoadRepo(ctx); err != nil { + return err } + if err := run.Repo.LoadAttributes(ctx); err != nil { return err } @@ -120,6 +117,19 @@ func (run *ActionRun) LoadAttributes(ctx context.Context) error { return nil } +func (run *ActionRun) LoadRepo(ctx context.Context) error { + if run == nil || run.Repo != nil { + return nil + } + + repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID) + if err != nil { + return err + } + run.Repo = repo + return nil +} + func (run *ActionRun) Duration() time.Duration { return calculateDuration(run.Started, run.Stopped, run.Status) + run.PreviousDuration } diff --git a/models/actions/run_job_list.go b/models/actions/run_job_list.go index 6ea6cb9d3b..6c5d3b3252 100644 --- a/models/actions/run_job_list.go +++ b/models/actions/run_job_list.go @@ -16,14 +16,9 @@ import ( type ActionJobList []*ActionRunJob func (jobs ActionJobList) GetRunIDs() []int64 { - ids := make(container.Set[int64], len(jobs)) - for _, j := range jobs { - if j.RunID == 0 { - continue - } - ids.Add(j.RunID) - } - return ids.Values() + return container.FilterSlice(jobs, func(j *ActionRunJob) (int64, bool) { + return j.RunID, j.RunID != 0 + }) } func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error { diff --git a/models/actions/run_list.go b/models/actions/run_list.go index 388bfc4f86..4046c7d369 100644 --- a/models/actions/run_list.go +++ b/models/actions/run_list.go @@ -19,19 +19,15 @@ type RunList []*ActionRun // GetUserIDs returns a slice of user's id func (runs RunList) GetUserIDs() []int64 { - ids := make(container.Set[int64], len(runs)) - for _, run := range runs { - ids.Add(run.TriggerUserID) - } - return ids.Values() + return container.FilterSlice(runs, func(run *ActionRun) (int64, bool) { + return run.TriggerUserID, true + }) } func (runs RunList) GetRepoIDs() []int64 { - ids := make(container.Set[int64], len(runs)) - for _, run := range runs { - ids.Add(run.RepoID) - } - return ids.Values() + return container.FilterSlice(runs, func(run *ActionRun) (int64, bool) { + return run.RepoID, true + }) } func (runs RunList) LoadTriggerUser(ctx context.Context) error { diff --git a/models/actions/runner.go b/models/actions/runner.go index 67f003387b..9192925d5a 100644 --- a/models/actions/runner.go +++ b/models/actions/runner.go @@ -270,7 +270,7 @@ func CountRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { // Only affect action runners were a owner ID is set, as actions runners // could also be created on a repository. return db.GetEngine(ctx).Table("action_runner"). - Join("LEFT", "user", "`action_runner`.owner_id = `user`.id"). + Join("LEFT", "`user`", "`action_runner`.owner_id = `user`.id"). Where("`action_runner`.owner_id != ?", 0). And(builder.IsNull{"`user`.id"}). Count(new(ActionRunner)) @@ -279,7 +279,7 @@ func CountRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { subQuery := builder.Select("`action_runner`.id"). From("`action_runner`"). - Join("LEFT", "user", "`action_runner`.owner_id = `user`.id"). + Join("LEFT", "`user`", "`action_runner`.owner_id = `user`.id"). Where(builder.Neq{"`action_runner`.owner_id": 0}). And(builder.IsNull{"`user`.id"}) b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`") @@ -289,3 +289,25 @@ func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { } return res.RowsAffected() } + +func CountRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) { + return db.GetEngine(ctx).Table("action_runner"). + Join("LEFT", "`repository`", "`action_runner`.repo_id = `repository`.id"). + Where("`action_runner`.repo_id != ?", 0). + And(builder.IsNull{"`repository`.id"}). + Count(new(ActionRunner)) +} + +func FixRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) { + subQuery := builder.Select("`action_runner`.id"). + From("`action_runner`"). + Join("LEFT", "`repository`", "`action_runner`.repo_id = `repository`.id"). + Where(builder.Neq{"`action_runner`.repo_id": 0}). + And(builder.IsNull{"`repository`.id"}) + b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`") + res, err := db.GetEngine(ctx).Exec(b) + if err != nil { + return 0, err + } + return res.RowsAffected() +} diff --git a/models/actions/runner_list.go b/models/actions/runner_list.go index 87f0886b47..3ef8ebb254 100644 --- a/models/actions/runner_list.go +++ b/models/actions/runner_list.go @@ -16,14 +16,9 @@ type RunnerList []*ActionRunner // GetUserIDs returns a slice of user's id func (runners RunnerList) GetUserIDs() []int64 { - ids := make(container.Set[int64], len(runners)) - for _, runner := range runners { - if runner.OwnerID == 0 { - continue - } - ids.Add(runner.OwnerID) - } - return ids.Values() + return container.FilterSlice(runners, func(runner *ActionRunner) (int64, bool) { + return runner.OwnerID, runner.OwnerID != 0 + }) } func (runners RunnerList) LoadOwners(ctx context.Context) error { @@ -41,16 +36,9 @@ func (runners RunnerList) LoadOwners(ctx context.Context) error { } func (runners RunnerList) getRepoIDs() []int64 { - repoIDs := make(container.Set[int64], len(runners)) - for _, runner := range runners { - if runner.RepoID == 0 { - continue - } - if _, ok := repoIDs[runner.RepoID]; !ok { - repoIDs[runner.RepoID] = struct{}{} - } - } - return repoIDs.Values() + return container.FilterSlice(runners, func(runner *ActionRunner) (int64, bool) { + return runner.RepoID, runner.RepoID > 0 + }) } func (runners RunnerList) LoadRepos(ctx context.Context) error { diff --git a/models/actions/schedule_list.go b/models/actions/schedule_list.go index ecd2c1121c..5361b94801 100644 --- a/models/actions/schedule_list.go +++ b/models/actions/schedule_list.go @@ -18,19 +18,15 @@ type ScheduleList []*ActionSchedule // GetUserIDs returns a slice of user's id func (schedules ScheduleList) GetUserIDs() []int64 { - ids := make(container.Set[int64], len(schedules)) - for _, schedule := range schedules { - ids.Add(schedule.TriggerUserID) - } - return ids.Values() + return container.FilterSlice(schedules, func(schedule *ActionSchedule) (int64, bool) { + return schedule.TriggerUserID, true + }) } func (schedules ScheduleList) GetRepoIDs() []int64 { - ids := make(container.Set[int64], len(schedules)) - for _, schedule := range schedules { - ids.Add(schedule.RepoID) - } - return ids.Values() + return container.FilterSlice(schedules, func(schedule *ActionSchedule) (int64, bool) { + return schedule.RepoID, true + }) } func (schedules ScheduleList) LoadTriggerUser(ctx context.Context) error { diff --git a/models/actions/schedule_spec_list.go b/models/actions/schedule_spec_list.go index e9ae268a6e..f7dac72f8b 100644 --- a/models/actions/schedule_spec_list.go +++ b/models/actions/schedule_spec_list.go @@ -16,11 +16,9 @@ import ( type SpecList []*ActionScheduleSpec func (specs SpecList) GetScheduleIDs() []int64 { - ids := make(container.Set[int64], len(specs)) - for _, spec := range specs { - ids.Add(spec.ScheduleID) - } - return ids.Values() + return container.FilterSlice(specs, func(spec *ActionScheduleSpec) (int64, bool) { + return spec.ScheduleID, true + }) } func (specs SpecList) LoadSchedules(ctx context.Context) error { @@ -46,11 +44,9 @@ func (specs SpecList) LoadSchedules(ctx context.Context) error { } func (specs SpecList) GetRepoIDs() []int64 { - ids := make(container.Set[int64], len(specs)) - for _, spec := range specs { - ids.Add(spec.RepoID) - } - return ids.Values() + return container.FilterSlice(specs, func(spec *ActionScheduleSpec) (int64, bool) { + return spec.RepoID, true + }) } func (specs SpecList) LoadRepos(ctx context.Context) error { diff --git a/models/actions/task_list.go b/models/actions/task_list.go index b07d00b8db..5e17f91441 100644 --- a/models/actions/task_list.go +++ b/models/actions/task_list.go @@ -16,14 +16,9 @@ import ( type TaskList []*ActionTask func (tasks TaskList) GetJobIDs() []int64 { - ids := make(container.Set[int64], len(tasks)) - for _, t := range tasks { - if t.JobID == 0 { - continue - } - ids.Add(t.JobID) - } - return ids.Values() + return container.FilterSlice(tasks, func(t *ActionTask) (int64, bool) { + return t.JobID, t.JobID != 0 + }) } func (tasks TaskList) LoadJobs(ctx context.Context) error { diff --git a/models/actions/variable.go b/models/actions/variable.go index 14ded60fac..8aff844659 100644 --- a/models/actions/variable.go +++ b/models/actions/variable.go @@ -6,13 +6,11 @@ package actions import ( "context" "errors" - "fmt" "strings" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" - "code.gitea.io/gitea/modules/util" "xorm.io/builder" ) @@ -55,24 +53,24 @@ type FindVariablesOpts struct { db.ListOptions OwnerID int64 RepoID int64 + Name string } func (opts FindVariablesOpts) ToConds() builder.Cond { cond := builder.NewCond() + // Since we now support instance-level variables, + // there is no need to check for null values for `owner_id` and `repo_id` cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) + + if opts.Name != "" { + cond = cond.And(builder.Eq{"name": strings.ToUpper(opts.Name)}) + } return cond } -func GetVariableByID(ctx context.Context, variableID int64) (*ActionVariable, error) { - var variable ActionVariable - has, err := db.GetEngine(ctx).Where("id=?", variableID).Get(&variable) - if err != nil { - return nil, err - } else if !has { - return nil, fmt.Errorf("variable with id %d: %w", variableID, util.ErrNotExist) - } - return &variable, nil +func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariable, error) { + return db.Find[ActionVariable](ctx, opts) } func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) { @@ -84,9 +82,21 @@ func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) return count != 0, err } +func DeleteVariable(ctx context.Context, id int64) error { + if _, err := db.DeleteByID[ActionVariable](ctx, id); err != nil { + return err + } + return nil +} + func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) { variables := map[string]string{} + if err := run.LoadRepo(ctx); err != nil { + log.Error("LoadRepo: %v", err) + return nil, err + } + // Global globalVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{}) if err != nil { diff --git a/models/activities/action_list.go b/models/activities/action_list.go index fdf0f35d4f..aafb7f8a26 100644 --- a/models/activities/action_list.go +++ b/models/activities/action_list.go @@ -22,11 +22,9 @@ import ( type ActionList []*Action func (actions ActionList) getUserIDs() []int64 { - userIDs := make(container.Set[int64], len(actions)) - for _, action := range actions { - userIDs.Add(action.ActUserID) - } - return userIDs.Values() + return container.FilterSlice(actions, func(action *Action) (int64, bool) { + return action.ActUserID, true + }) } func (actions ActionList) LoadActUsers(ctx context.Context) (map[int64]*user_model.User, error) { @@ -50,11 +48,9 @@ func (actions ActionList) LoadActUsers(ctx context.Context) (map[int64]*user_mod } func (actions ActionList) getRepoIDs() []int64 { - repoIDs := make(container.Set[int64], len(actions)) - for _, action := range actions { - repoIDs.Add(action.RepoID) - } - return repoIDs.Values() + return container.FilterSlice(actions, func(action *Action) (int64, bool) { + return action.RepoID, true + }) } func (actions ActionList) LoadRepositories(ctx context.Context) error { @@ -80,18 +76,19 @@ func (actions ActionList) loadRepoOwner(ctx context.Context, userMap map[int64]* userMap = make(map[int64]*user_model.User) } - userSet := make(container.Set[int64], len(actions)) - for _, action := range actions { + missingUserIDs := container.FilterSlice(actions, func(action *Action) (int64, bool) { if action.Repo == nil { - continue - } - if _, ok := userMap[action.Repo.OwnerID]; !ok { - userSet.Add(action.Repo.OwnerID) + return 0, false } + _, alreadyLoaded := userMap[action.Repo.OwnerID] + return action.Repo.OwnerID, !alreadyLoaded + }) + if len(missingUserIDs) == 0 { + return nil } if err := db.GetEngine(ctx). - In("id", userSet.Values()). + In("id", missingUserIDs). Find(&userMap); err != nil { return fmt.Errorf("find user: %w", err) } @@ -135,6 +132,9 @@ func (actions ActionList) LoadComments(ctx context.Context) error { commentIDs = append(commentIDs, action.CommentID) } } + if len(commentIDs) == 0 { + return nil + } commentsMap := make(map[int64]*issues_model.Comment, len(commentIDs)) if err := db.GetEngine(ctx).In("id", commentIDs).Find(&commentsMap); err != nil { diff --git a/models/activities/notification_list.go b/models/activities/notification_list.go index a6140d6f73..32d2a5c051 100644 --- a/models/activities/notification_list.go +++ b/models/activities/notification_list.go @@ -196,15 +196,11 @@ func (nl NotificationList) LoadAttributes(ctx context.Context) error { return nil } +// getPendingRepoIDs returns all the repositoty ids which haven't been loaded func (nl NotificationList) getPendingRepoIDs() []int64 { - ids := make(container.Set[int64], len(nl)) - for _, notification := range nl { - if notification.Repository != nil { - continue - } - ids.Add(notification.RepoID) - } - return ids.Values() + return container.FilterSlice(nl, func(n *Notification) (int64, bool) { + return n.RepoID, n.Repository == nil + }) } // LoadRepos loads repositories from database diff --git a/models/activities/user_heatmap.go b/models/activities/user_heatmap.go index 78fcd76d43..080075d793 100644 --- a/models/activities/user_heatmap.go +++ b/models/activities/user_heatmap.go @@ -39,12 +39,8 @@ func getUserHeatmapData(ctx context.Context, user *user_model.User, team *organi // Group by 15 minute intervals which will allow the client to accurately shift the timestamp to their timezone. // The interval is based on the fact that there are timezones such as UTC +5:30 and UTC +12:45. groupBy := "created_unix / 900 * 900" - groupByName := "timestamp" // We need this extra case because mssql doesn't allow grouping by alias - switch { - case setting.Database.Type.IsMySQL(): + if setting.Database.Type.IsMySQL() { groupBy = "created_unix DIV 900 * 900" - case setting.Database.Type.IsMSSQL(): - groupByName = groupBy } cond, err := activityQueryCondition(ctx, GetFeedsOptions{ @@ -67,7 +63,7 @@ func getUserHeatmapData(ctx context.Context, user *user_model.User, team *organi Table("action"). Where(cond). And("created_unix > ?", timeutil.TimeStampNow()-31536000). - GroupBy(groupByName). + GroupBy("timestamp"). OrderBy("timestamp"). Find(&hdata) } diff --git a/models/asymkey/gpg_key_object_verification.go b/models/asymkey/gpg_key_object_verification.go index 67764ffc58..e5c31a74a7 100644 --- a/models/asymkey/gpg_key_object_verification.go +++ b/models/asymkey/gpg_key_object_verification.go @@ -94,7 +94,6 @@ func ParseObjectWithSignature(ctx context.Context, c *GitObject) *ObjectVerifica Reason: "gpg.error.no_committer_account", } } - } } diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index 83d60e3abe..125d64b36f 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -8,6 +8,7 @@ import ( "crypto/sha256" "encoding/base32" "encoding/base64" + "errors" "fmt" "net" "net/url" @@ -144,6 +145,11 @@ func (app *OAuth2Application) TableName() string { // ContainsRedirectURI checks if redirectURI is allowed for app func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool { + // OAuth2 requires the redirect URI to be an exact match, no dynamic parts are allowed. + // https://stackoverflow.com/questions/55524480/should-dynamic-query-parameters-be-present-in-the-redirection-uri-for-an-oauth2 + // https://www.rfc-editor.org/rfc/rfc6819#section-5.2.3.3 + // https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-12#section-3.1 contains := func(s string) bool { s = strings.TrimSuffix(strings.ToLower(s), "/") for _, u := range app.RedirectURIs { @@ -296,7 +302,7 @@ func UpdateOAuth2Application(ctx context.Context, opts UpdateOAuth2ApplicationOp return nil, err } if app.UID != opts.UserID { - return nil, fmt.Errorf("UID mismatch") + return nil, errors.New("UID mismatch") } builtinApps := BuiltinApplications() if _, builtin := builtinApps[app.ClientID]; builtin { diff --git a/models/auth/source.go b/models/auth/source.go index 1a3a1b20a6..d03d4975dc 100644 --- a/models/auth/source.go +++ b/models/auth/source.go @@ -33,6 +33,7 @@ const ( DLDAP // 5 OAuth2 // 6 SSPI // 7 + Remote // 8 ) // String returns the string name of the LoginType @@ -53,6 +54,7 @@ var Names = map[Type]string{ PAM: "PAM", OAuth2: "OAuth2", SSPI: "SPNEGO with SSPI", + Remote: "Remote", } // Config represents login config as far as the db is concerned @@ -181,6 +183,10 @@ func (source *Source) IsSSPI() bool { return source.Type == SSPI } +func (source *Source) IsRemote() bool { + return source.Type == Remote +} + // HasTLS returns true of this source supports TLS. func (source *Source) HasTLS() bool { hasTLSer, ok := source.Cfg.(HasTLSer) diff --git a/models/db/collation.go b/models/db/collation.go index 149e74a85c..39d28fa2ff 100644 --- a/models/db/collation.go +++ b/models/db/collation.go @@ -41,20 +41,6 @@ func findAvailableCollationsMySQL(x *xorm.Engine) (ret container.Set[string], er return ret, nil } -func findAvailableCollationsMSSQL(x *xorm.Engine) (ret container.Set[string], err error) { - var res []struct { - Name string - } - if err = x.SQL("SELECT * FROM sys.fn_helpcollations() WHERE name LIKE '%[_]CS[_]AS%'").Find(&res); err != nil { - return nil, err - } - ret = make(container.Set[string], len(res)) - for _, r := range res { - ret.Add(r.Name) - } - return ret, nil -} - func CheckCollations(x *xorm.Engine) (*CheckCollationsResult, error) { dbTables, err := x.DBMetas() if err != nil { @@ -84,18 +70,6 @@ func CheckCollations(x *xorm.Engine) (*CheckCollationsResult, error) { // At the moment, it's safe to ignore the database difference, just trim the prefix and compare. It could be fixed easily if there is any problem in the future. return a == b || strings.TrimPrefix(a, "utf8mb4_") == strings.TrimPrefix(b, "utf8mb4_") } - } else if x.Dialect().URI().DBType == schemas.MSSQL { - if _, err = x.SQL("SELECT DATABASEPROPERTYEX(DB_NAME(), 'Collation')").Get(&res.DatabaseCollation); err != nil { - return nil, err - } - res.IsCollationCaseSensitive = func(s string) bool { - return strings.HasSuffix(s, "_CS_AS") - } - candidateCollations = []string{"Latin1_General_CS_AS"} - res.AvailableCollation, err = findAvailableCollationsMSSQL(x) - if err != nil { - return nil, err - } } else { return nil, nil } @@ -146,10 +120,6 @@ func alterDatabaseCollation(x *xorm.Engine, collation string) error { if x.Dialect().URI().DBType == schemas.MYSQL { _, err := x.Exec("ALTER DATABASE CHARACTER SET utf8mb4 COLLATE " + collation) return err - } else if x.Dialect().URI().DBType == schemas.MSSQL { - // TODO: MSSQL has many limitations on changing database collation, it could fail in many cases. - _, err := x.Exec("ALTER DATABASE CURRENT COLLATE " + collation) - return err } return errors.New("unsupported database type") } @@ -165,12 +135,11 @@ func preprocessDatabaseCollation(x *xorm.Engine) { } // try to alter database collation to expected if the database is empty, it might fail in some cases (and it isn't necessary to succeed) - // at the moment, there is no "altering" solution for MSSQL, site admin should manually change the database collation + // at the moment. if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) && r.ExistingTableNumber == 0 { if err = alterDatabaseCollation(x, r.ExpectedCollation); err != nil { log.Error("Failed to change database collation to %q: %v", r.ExpectedCollation, err) } else { - _, _ = x.Exec("SELECT 1") // after "altering", MSSQL's session becomes invalid, so make a simple query to "refresh" the session if r, err = CheckCollations(x); err != nil { log.Error("Failed to check database collation again after altering: %v", err) // impossible case return diff --git a/models/db/common.go b/models/db/common.go index ea628bf2a0..f3fd3e72ae 100644 --- a/models/db/common.go +++ b/models/db/common.go @@ -47,8 +47,6 @@ func BuilderDialect() string { return builder.SQLITE case setting.Database.Type.IsPostgreSQL(): return builder.POSTGRES - case setting.Database.Type.IsMSSQL(): - return builder.MSSQL default: return "" } diff --git a/models/db/convert.go b/models/db/convert.go index 8c124471ab..b8b15382e7 100644 --- a/models/db/convert.go +++ b/models/db/convert.go @@ -47,33 +47,6 @@ func ConvertDatabaseTable() error { return nil } -// ConvertVarcharToNVarchar converts database and tables from varchar to nvarchar if it's mssql -func ConvertVarcharToNVarchar() error { - if x.Dialect().URI().DBType != schemas.MSSQL { - return nil - } - - sess := x.NewSession() - defer sess.Close() - res, err := sess.QuerySliceString(`SELECT 'ALTER TABLE ' + OBJECT_NAME(SC.object_id) + ' MODIFY SC.name NVARCHAR(' + CONVERT(VARCHAR(5),SC.max_length) + ')' -FROM SYS.columns SC -JOIN SYS.types ST -ON SC.system_type_id = ST.system_type_id -AND SC.user_type_id = ST.user_type_id -WHERE ST.name ='varchar'`) - if err != nil { - return err - } - for _, row := range res { - if len(row) == 1 { - if _, err = sess.Exec(row[0]); err != nil { - return err - } - } - } - return err -} - // Cell2Int64 converts a xorm.Cell type to int64, // and handles possible irregular cases. func Cell2Int64(val xorm.Cell) int64 { diff --git a/models/db/engine.go b/models/db/engine.go index b3a4171e3f..b7146e87f2 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -22,9 +22,8 @@ import ( "xorm.io/xorm/names" "xorm.io/xorm/schemas" - _ "github.com/denisenkom/go-mssqldb" // Needed for the MSSQL driver - _ "github.com/go-sql-driver/mysql" // Needed for the MySQL driver - _ "github.com/lib/pq" // Needed for the Postgresql driver + _ "github.com/go-sql-driver/mysql" // Needed for the MySQL driver + _ "github.com/lib/pq" // Needed for the Postgresql driver ) var ( @@ -114,10 +113,8 @@ func newXORMEngine() (*xorm.Engine, error) { if err != nil { return nil, err } - if setting.Database.Type == "mysql" { + if setting.Database.Type.IsMySQL() { engine.Dialect().SetParams(map[string]string{"rowFormat": "DYNAMIC"}) - } else if setting.Database.Type == "mssql" { - engine.Dialect().SetParams(map[string]string{"DEFAULT_VARCHAR": "nvarchar"}) } engine.SetSchema(setting.Database.Schema) return engine, nil @@ -239,7 +236,6 @@ func NamesToBean(names ...string) ([]any, error) { // Need to map provided names to beans... beanMap := make(map[string]any) for _, bean := range tables { - beanMap[strings.ToLower(reflect.Indirect(reflect.ValueOf(bean)).Type().Name())] = bean beanMap[strings.ToLower(x.TableName(bean))] = bean beanMap[strings.ToLower(x.TableName(bean, true))] = bean diff --git a/models/db/index.go b/models/db/index.go index 29254b1f07..259ddd6ade 100644 --- a/models/db/index.go +++ b/models/db/index.go @@ -89,33 +89,6 @@ func mysqlGetNextResourceIndex(ctx context.Context, tableName string, groupID in return idx, nil } -func mssqlGetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) { - if _, err := GetEngine(ctx).Exec(fmt.Sprintf(` -MERGE INTO %s WITH (HOLDLOCK) AS target -USING (SELECT %d AS group_id) AS source -(group_id) -ON target.group_id = source.group_id -WHEN MATCHED - THEN UPDATE - SET max_index = max_index + 1 -WHEN NOT MATCHED - THEN INSERT (group_id, max_index) - VALUES (%d, 1); -`, tableName, groupID, groupID)); err != nil { - return 0, err - } - - var idx int64 - _, err := GetEngine(ctx).SQL(fmt.Sprintf("SELECT max_index FROM %s WHERE group_id = ?", tableName), groupID).Get(&idx) - if err != nil { - return 0, err - } - if idx == 0 { - return 0, errors.New("cannot get the correct index") - } - return idx, nil -} - // GetNextResourceIndex generates a resource index, it must run in the same transaction where the resource is created func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64) (int64, error) { switch { @@ -123,8 +96,6 @@ func GetNextResourceIndex(ctx context.Context, tableName string, groupID int64) return postgresGetNextResourceIndex(ctx, tableName, groupID) case setting.Database.Type.IsMySQL(): return mysqlGetNextResourceIndex(ctx, tableName, groupID) - case setting.Database.Type.IsMSSQL(): - return mssqlGetNextResourceIndex(ctx, tableName, groupID) } e := GetEngine(ctx) diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go index 6dedde4d6c..3b6da74149 100644 --- a/models/forgejo_migrations/migrate.go +++ b/models/forgejo_migrations/migrate.go @@ -59,8 +59,13 @@ var migrations = []*Migration{ // v9 -> v10 NewMigration("Add pronouns to user", forgejo_v1_22.AddPronounsToUser), // v11 -> v12 - // it is a v7.0 migration backport see https://codeberg.org/forgejo/forgejo/pulls/3165#issuecomment-1755941 NewMigration("Add the `created` column to the `issue` table", forgejo_v1_22.AddCreatedToIssue), + // v12 -> v13 + NewMigration("Add repo_archive_download_count table", forgejo_v1_22.AddRepoArchiveDownloadCount), + // v13 -> v14 + NewMigration("Add `hide_archive_links` column to `release` table", AddHideArchiveLinksToRelease), + // v14 -> v15 + NewMigration("Remove Gitea-specific columns from the repository and badge tables", RemoveGiteaSpecificColumnsFromRepositoryAndBadge), } // GetCurrentDBVersion returns the current Forgejo database version. diff --git a/models/forgejo_migrations/v13.go b/models/forgejo_migrations/v13.go new file mode 100644 index 0000000000..614f68249d --- /dev/null +++ b/models/forgejo_migrations/v13.go @@ -0,0 +1,15 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package forgejo_migrations //nolint:revive + +import "xorm.io/xorm" + +func AddHideArchiveLinksToRelease(x *xorm.Engine) error { + type Release struct { + ID int64 `xorm:"pk autoincr"` + HideArchiveLinks bool `xorm:"NOT NULL DEFAULT false"` + } + + return x.Sync(&Release{}) +} diff --git a/models/forgejo_migrations/v14.go b/models/forgejo_migrations/v14.go new file mode 100644 index 0000000000..f6dd35ecf0 --- /dev/null +++ b/models/forgejo_migrations/v14.go @@ -0,0 +1,43 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package forgejo_migrations //nolint:revive + +import ( + "code.gitea.io/gitea/models/migrations/base" + + "xorm.io/xorm" +) + +func RemoveGiteaSpecificColumnsFromRepositoryAndBadge(x *xorm.Engine) error { + // Make sure the columns exist before dropping them + type Repository struct { + ID int64 + DefaultWikiBranch string + } + if err := x.Sync(&Repository{}); err != nil { + return err + } + + type Badge struct { + ID int64 `xorm:"pk autoincr"` + Slug string + } + err := x.Sync(new(Badge)) + if err != nil { + return err + } + + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + if err := base.DropTableColumns(sess, "repository", "default_wiki_branch"); err != nil { + return err + } + if err := base.DropTableColumns(sess, "badge", "slug"); err != nil { + return err + } + return sess.Commit() +} diff --git a/models/forgejo_migrations/v1_22/v12.go b/models/forgejo_migrations/v1_22/v12.go new file mode 100644 index 0000000000..6822524705 --- /dev/null +++ b/models/forgejo_migrations/v1_22/v12.go @@ -0,0 +1,18 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import "xorm.io/xorm" + +func AddRepoArchiveDownloadCount(x *xorm.Engine) error { + type RepoArchiveDownloadCount struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"index unique(s)"` + ReleaseID int64 `xorm:"index unique(s)"` + Type int `xorm:"unique(s)"` + Count int64 + } + + return x.Sync(&RepoArchiveDownloadCount{}) +} diff --git a/models/git/branch.go b/models/git/branch.go index c905a2d368..7e1c96d769 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -103,7 +103,7 @@ func (err ErrBranchesEqual) Unwrap() error { type Branch struct { ID int64 RepoID int64 `xorm:"UNIQUE(s)"` - Name string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mssql, mysql, by default), it's case-insensitive at the moment + Name string `xorm:"UNIQUE(s) NOT NULL"` // git's ref-name is case-sensitive internally, however, in some databases (mysql, by default), it's case-insensitive at the moment CommitID string CommitMessage string `xorm:"TEXT"` // it only stores the message summary (the first line) PusherID int64 diff --git a/models/git/branch_list.go b/models/git/branch_list.go index c6fc8ad4b1..493611f217 100644 --- a/models/git/branch_list.go +++ b/models/git/branch_list.go @@ -17,15 +17,12 @@ import ( type BranchList []*Branch func (branches BranchList) LoadDeletedBy(ctx context.Context) error { - ids := container.Set[int64]{} - for _, branch := range branches { - if !branch.IsDeleted { - continue - } - ids.Add(branch.DeletedByID) - } + ids := container.FilterSlice(branches, func(branch *Branch) (int64, bool) { + return branch.DeletedByID, branch.IsDeleted + }) + usersMap := make(map[int64]*user_model.User, len(ids)) - if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil { + if err := db.GetEngine(ctx).In("id", ids).Find(&usersMap); err != nil { return err } for _, branch := range branches { @@ -41,14 +38,13 @@ func (branches BranchList) LoadDeletedBy(ctx context.Context) error { } func (branches BranchList) LoadPusher(ctx context.Context) error { - ids := container.Set[int64]{} - for _, branch := range branches { - if branch.PusherID > 0 { // pusher_id maybe zero because some branches are sync by backend with no pusher - ids.Add(branch.PusherID) - } - } + ids := container.FilterSlice(branches, func(branch *Branch) (int64, bool) { + // pusher_id maybe zero because some branches are sync by backend with no pusher + return branch.PusherID, branch.PusherID > 0 + }) + usersMap := make(map[int64]*user_model.User, len(ids)) - if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil { + if err := db.GetEngine(ctx).In("id", ids).Find(&usersMap); err != nil { return err } for _, branch := range branches { diff --git a/models/git/commit_status.go b/models/git/commit_status.go index 45cf6c3c98..3f2172e50a 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -84,34 +84,6 @@ func mysqlGetCommitStatusIndex(ctx context.Context, repoID int64, sha string) (i return idx, nil } -func mssqlGetCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) { - if _, err := db.GetEngine(ctx).Exec(` -MERGE INTO commit_status_index WITH (HOLDLOCK) AS target -USING (SELECT ? AS repo_id, ? AS sha) AS source -(repo_id, sha) -ON target.repo_id = source.repo_id AND target.sha = source.sha -WHEN MATCHED - THEN UPDATE - SET max_index = max_index + 1 -WHEN NOT MATCHED - THEN INSERT (repo_id, sha, max_index) - VALUES (?, ?, 1); -`, repoID, sha, repoID, sha); err != nil { - return 0, err - } - - var idx int64 - _, err := db.GetEngine(ctx).SQL("SELECT max_index FROM `commit_status_index` WHERE repo_id = ? AND sha = ?", - repoID, sha).Get(&idx) - if err != nil { - return 0, err - } - if idx == 0 { - return 0, errors.New("cannot get the correct index") - } - return idx, nil -} - // GetNextCommitStatusIndex retried 3 times to generate a resource index func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (int64, error) { _, err := git.NewIDFromString(sha) @@ -124,8 +96,6 @@ func GetNextCommitStatusIndex(ctx context.Context, repoID int64, sha string) (in return postgresGetCommitStatusIndex(ctx, repoID, sha) case setting.Database.Type.IsMySQL(): return mysqlGetCommitStatusIndex(ctx, repoID, sha) - case setting.Database.Type.IsMSSQL(): - return mssqlGetCommitStatusIndex(ctx, repoID, sha) } e := db.GetEngine(ctx) @@ -287,30 +257,27 @@ func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOp } // GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs -func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHAs map[int64]string, listOptions db.ListOptions) (map[int64][]*CommitStatus, error) { +func GetLatestCommitStatusForPairs(ctx context.Context, repoSHAs []RepoSHA) (map[int64][]*CommitStatus, error) { type result struct { Index int64 RepoID int64 + SHA string } - results := make([]result, 0, len(repoIDsToLatestCommitSHAs)) + results := make([]result, 0, len(repoSHAs)) getBase := func() *xorm.Session { return db.GetEngine(ctx).Table(&CommitStatus{}) } // Create a disjunction of conditions for each repoID and SHA pair - conds := make([]builder.Cond, 0, len(repoIDsToLatestCommitSHAs)) - for repoID, sha := range repoIDsToLatestCommitSHAs { - conds = append(conds, builder.Eq{"repo_id": repoID, "sha": sha}) + conds := make([]builder.Cond, 0, len(repoSHAs)) + for _, repoSHA := range repoSHAs { + conds = append(conds, builder.Eq{"repo_id": repoSHA.RepoID, "sha": repoSHA.SHA}) } sess := getBase().Where(builder.Or(conds...)). - Select("max( `index` ) as `index`, repo_id"). - GroupBy("context_hash, repo_id").OrderBy("max( `index` ) desc") - - if !listOptions.IsListAll() { - sess = db.SetSessionPagination(sess, &listOptions) - } + Select("max( `index` ) as `index`, repo_id, sha"). + GroupBy("context_hash, repo_id, sha").OrderBy("max( `index` ) desc") err := sess.Find(&results) if err != nil { @@ -327,7 +294,7 @@ func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHA cond := builder.Eq{ "`index`": result.Index, "repo_id": result.RepoID, - "sha": repoIDsToLatestCommitSHAs[result.RepoID], + "sha": result.SHA, } conds = append(conds, cond) } @@ -395,16 +362,36 @@ func GetLatestCommitStatusForRepoCommitIDs(ctx context.Context, repoID int64, co // FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) { - start := timeutil.TimeStampNow().AddDuration(-before) + type result struct { + Index int64 + SHA string + } + getBase := func() *xorm.Session { + return db.GetEngine(ctx).Table(&CommitStatus{}).Where("repo_id = ?", repoID) + } - var contexts []string - if err := db.GetEngine(ctx).Table("commit_status"). - Where("repo_id = ?", repoID).And("updated_unix >= ?", start). - Cols("context").Distinct().Find(&contexts); err != nil { + start := timeutil.TimeStampNow().AddDuration(-before) + results := make([]result, 0, 10) + + sess := getBase().And("updated_unix >= ?", start). + Select("max( `index` ) as `index`, sha"). + GroupBy("context_hash, sha").OrderBy("max( `index` ) desc") + + err := sess.Find(&results) + if err != nil { return nil, err } - return contexts, nil + contexts := make([]string, 0, len(results)) + if len(results) == 0 { + return contexts, nil + } + + conds := make([]builder.Cond, 0, len(results)) + for _, result := range results { + conds = append(conds, builder.Eq{"`index`": result.Index, "sha": result.SHA}) + } + return contexts, getBase().And(builder.Or(conds...)).Select("context").Find(&contexts) } // NewCommitStatusOptions holds options for creating a CommitStatus diff --git a/models/git/commit_status_summary.go b/models/git/commit_status_summary.go new file mode 100644 index 0000000000..7603e7aa65 --- /dev/null +++ b/models/git/commit_status_summary.go @@ -0,0 +1,88 @@ +// Copyright 2024 Gitea. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "context" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + + "xorm.io/builder" +) + +// CommitStatusSummary holds the latest commit Status of a single Commit +type CommitStatusSummary struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX UNIQUE(repo_id_sha)"` + SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_id_sha)"` + State api.CommitStatusState `xorm:"VARCHAR(7) NOT NULL"` + TargetURL string `xorm:"TEXT"` +} + +func init() { + db.RegisterModel(new(CommitStatusSummary)) +} + +type RepoSHA struct { + RepoID int64 + SHA string +} + +func GetLatestCommitStatusForRepoAndSHAs(ctx context.Context, repoSHAs []RepoSHA) ([]*CommitStatus, error) { + cond := builder.NewCond() + for _, rs := range repoSHAs { + cond = cond.Or(builder.Eq{"repo_id": rs.RepoID, "sha": rs.SHA}) + } + + var summaries []CommitStatusSummary + if err := db.GetEngine(ctx).Where(cond).Find(&summaries); err != nil { + return nil, err + } + + commitStatuses := make([]*CommitStatus, 0, len(repoSHAs)) + for _, summary := range summaries { + commitStatuses = append(commitStatuses, &CommitStatus{ + RepoID: summary.RepoID, + SHA: summary.SHA, + State: summary.State, + TargetURL: summary.TargetURL, + }) + } + return commitStatuses, nil +} + +func UpdateCommitStatusSummary(ctx context.Context, repoID int64, sha string) error { + commitStatuses, _, err := GetLatestCommitStatus(ctx, repoID, sha, db.ListOptionsAll) + if err != nil { + return err + } + state := CalcCommitStatus(commitStatuses) + // mysql will return 0 when update a record which state hasn't been changed which behaviour is different from other database, + // so we need to use insert in on duplicate + if setting.Database.Type.IsMySQL() { + _, err := db.GetEngine(ctx).Exec("INSERT INTO commit_status_summary (repo_id,sha,state,target_url) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE state=?", + repoID, sha, state.State, state.TargetURL, state.State) + return err + } + + if cnt, err := db.GetEngine(ctx).Where("repo_id=? AND sha=?", repoID, sha). + Cols("state, target_url"). + Update(&CommitStatusSummary{ + State: state.State, + TargetURL: state.TargetURL, + }); err != nil { + return err + } else if cnt == 0 { + _, err = db.GetEngine(ctx).Insert(&CommitStatusSummary{ + RepoID: repoID, + SHA: sha, + State: state.State, + TargetURL: state.TargetURL, + }) + return err + } + return nil +} diff --git a/models/git/commit_status_test.go b/models/git/commit_status_test.go index 2ada8b3724..94c8d3776c 100644 --- a/models/git/commit_status_test.go +++ b/models/git/commit_status_test.go @@ -5,15 +5,11 @@ package git_test import ( "testing" - "time" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -187,55 +183,3 @@ func Test_CalcCommitStatus(t *testing.T) { assert.Equal(t, kase.expected, git_model.CalcCommitStatus(kase.statuses)) } } - -func TestFindRepoRecentCommitStatusContexts(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo2) - assert.NoError(t, err) - defer gitRepo.Close() - - commit, err := gitRepo.GetBranchCommit(repo2.DefaultBranch) - assert.NoError(t, err) - - defer func() { - _, err := db.DeleteByBean(db.DefaultContext, &git_model.CommitStatus{ - RepoID: repo2.ID, - CreatorID: user2.ID, - SHA: commit.ID.String(), - }) - assert.NoError(t, err) - }() - - err = git_model.NewCommitStatus(db.DefaultContext, git_model.NewCommitStatusOptions{ - Repo: repo2, - Creator: user2, - SHA: commit.ID, - CommitStatus: &git_model.CommitStatus{ - State: structs.CommitStatusFailure, - TargetURL: "https://example.com/tests/", - Context: "compliance/lint-backend", - }, - }) - assert.NoError(t, err) - - err = git_model.NewCommitStatus(db.DefaultContext, git_model.NewCommitStatusOptions{ - Repo: repo2, - Creator: user2, - SHA: commit.ID, - CommitStatus: &git_model.CommitStatus{ - State: structs.CommitStatusSuccess, - TargetURL: "https://example.com/tests/", - Context: "compliance/lint-backend", - }, - }) - assert.NoError(t, err) - - contexts, err := git_model.FindRepoRecentCommitStatusContexts(db.DefaultContext, repo2.ID, time.Hour) - assert.NoError(t, err) - if assert.Len(t, contexts, 1) { - assert.Equal(t, "compliance/lint-backend", contexts[0]) - } -} diff --git a/models/git/lfs_lock.go b/models/git/lfs_lock.go index 261c73032a..2f65833fe3 100644 --- a/models/git/lfs_lock.go +++ b/models/git/lfs_lock.go @@ -5,7 +5,7 @@ package git import ( "context" - "fmt" + "errors" "strings" "time" @@ -148,7 +148,7 @@ func DeleteLFSLockByID(ctx context.Context, id int64, repo *repo_model.Repositor } if !force && u.ID != lock.OwnerID { - return nil, fmt.Errorf("user doesn't own lock and force flag is not set") + return nil, errors.New("user doesn't own lock and force flag is not set") } if _, err := db.GetEngine(dbCtx).ID(id).Delete(new(LFSLock)); err != nil { diff --git a/models/issues/comment.go b/models/issues/comment.go index 984fb9c9fc..e4b5ed12cd 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -673,7 +673,8 @@ func (c *Comment) LoadTime(ctx context.Context) error { return err } -func (c *Comment) loadReactions(ctx context.Context, repo *repo_model.Repository) (err error) { +// LoadReactions loads comment reactions +func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository) (err error) { if c.Reactions != nil { return nil } @@ -691,11 +692,6 @@ func (c *Comment) loadReactions(ctx context.Context, repo *repo_model.Repository return nil } -// LoadReactions loads comment reactions -func (c *Comment) LoadReactions(ctx context.Context, repo *repo_model.Repository) error { - return c.loadReactions(ctx, repo) -} - func (c *Comment) loadReview(ctx context.Context) (err error) { if c.ReviewID == 0 { return nil @@ -1293,10 +1289,9 @@ func InsertIssueComments(ctx context.Context, comments []*Comment) error { return nil } - issueIDs := make(container.Set[int64]) - for _, comment := range comments { - issueIDs.Add(comment.IssueID) - } + issueIDs := container.FilterSlice(comments, func(comment *Comment) (int64, bool) { + return comment.IssueID, true + }) ctx, committer, err := db.TxContext(ctx) if err != nil { @@ -1319,7 +1314,7 @@ func InsertIssueComments(ctx context.Context, comments []*Comment) error { } } - for issueID := range issueIDs { + for _, issueID := range issueIDs { if _, err := db.Exec(ctx, "UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ? AND `type`=?) WHERE id = ?", issueID, CommentTypeComment, issueID); err != nil { return err diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go index 735c9f0893..2f6f57e0da 100644 --- a/models/issues/comment_code.go +++ b/models/issues/comment_code.go @@ -90,7 +90,7 @@ func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, doer *user_mod return pathToLineToComment, nil } -func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, doer *user_model.User, review *Review, showOutdatedComments bool) ([]*Comment, error) { +func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, doer *user_model.User, review *Review, showOutdatedComments bool) (CommentList, error) { var comments CommentList if review == nil { review = &Review{ID: 0} @@ -169,7 +169,7 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu } // FetchCodeConversation fetches the code conversation of a given comment (same review, treePath and line number) -func FetchCodeConversation(ctx context.Context, comment *Comment, doer *user_model.User) ([]*Comment, error) { +func FetchCodeConversation(ctx context.Context, comment *Comment, doer *user_model.User) (CommentList, error) { opts := FindCommentsOptions{ Type: CommentTypeCode, IssueID: comment.IssueID, diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go index 30a437ea50..370b5396e0 100644 --- a/models/issues/comment_list.go +++ b/models/issues/comment_list.go @@ -17,11 +17,9 @@ import ( type CommentList []*Comment func (comments CommentList) getPosterIDs() []int64 { - posterIDs := make(container.Set[int64], len(comments)) - for _, comment := range comments { - posterIDs.Add(comment.PosterID) - } - return posterIDs.Values() + return container.FilterSlice(comments, func(c *Comment) (int64, bool) { + return c.PosterID, c.PosterID > 0 + }) } // LoadPosters loads posters @@ -41,20 +39,10 @@ func (comments CommentList) LoadPosters(ctx context.Context) error { return nil } -func (comments CommentList) getCommentIDs() []int64 { - ids := make([]int64, 0, len(comments)) - for _, comment := range comments { - ids = append(ids, comment.ID) - } - return ids -} - func (comments CommentList) getLabelIDs() []int64 { - ids := make(container.Set[int64], len(comments)) - for _, comment := range comments { - ids.Add(comment.LabelID) - } - return ids.Values() + return container.FilterSlice(comments, func(comment *Comment) (int64, bool) { + return comment.LabelID, comment.LabelID > 0 + }) } func (comments CommentList) loadLabels(ctx context.Context) error { @@ -98,11 +86,9 @@ func (comments CommentList) loadLabels(ctx context.Context) error { } func (comments CommentList) getMilestoneIDs() []int64 { - ids := make(container.Set[int64], len(comments)) - for _, comment := range comments { - ids.Add(comment.MilestoneID) - } - return ids.Values() + return container.FilterSlice(comments, func(comment *Comment) (int64, bool) { + return comment.MilestoneID, comment.MilestoneID > 0 + }) } func (comments CommentList) loadMilestones(ctx context.Context) error { @@ -139,11 +125,9 @@ func (comments CommentList) loadMilestones(ctx context.Context) error { } func (comments CommentList) getOldMilestoneIDs() []int64 { - ids := make(container.Set[int64], len(comments)) - for _, comment := range comments { - ids.Add(comment.OldMilestoneID) - } - return ids.Values() + return container.FilterSlice(comments, func(comment *Comment) (int64, bool) { + return comment.OldMilestoneID, comment.OldMilestoneID > 0 + }) } func (comments CommentList) loadOldMilestones(ctx context.Context) error { @@ -180,11 +164,9 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error { } func (comments CommentList) getAssigneeIDs() []int64 { - ids := make(container.Set[int64], len(comments)) - for _, comment := range comments { - ids.Add(comment.AssigneeID) - } - return ids.Values() + return container.FilterSlice(comments, func(comment *Comment) (int64, bool) { + return comment.AssigneeID, comment.AssigneeID > 0 + }) } func (comments CommentList) loadAssignees(ctx context.Context) error { @@ -235,14 +217,9 @@ func (comments CommentList) loadAssignees(ctx context.Context) error { // getIssueIDs returns all the issue ids on this comment list which issue hasn't been loaded func (comments CommentList) getIssueIDs() []int64 { - ids := make(container.Set[int64], len(comments)) - for _, comment := range comments { - if comment.Issue != nil { - continue - } - ids.Add(comment.IssueID) - } - return ids.Values() + return container.FilterSlice(comments, func(comment *Comment) (int64, bool) { + return comment.IssueID, comment.Issue == nil + }) } // Issues returns all the issues of comments @@ -309,14 +286,12 @@ func (comments CommentList) LoadIssues(ctx context.Context) error { } func (comments CommentList) getDependentIssueIDs() []int64 { - ids := make(container.Set[int64], len(comments)) - for _, comment := range comments { + return container.FilterSlice(comments, func(comment *Comment) (int64, bool) { if comment.DependentIssue != nil { - continue + return 0, false } - ids.Add(comment.DependentIssueID) - } - return ids.Values() + return comment.DependentIssueID, comment.DependentIssueID > 0 + }) } func (comments CommentList) loadDependentIssues(ctx context.Context) error { @@ -369,6 +344,35 @@ func (comments CommentList) loadDependentIssues(ctx context.Context) error { return nil } +// getAttachmentCommentIDs only return the comment ids which possibly has attachments +func (comments CommentList) getAttachmentCommentIDs() []int64 { + return container.FilterSlice(comments, func(comment *Comment) (int64, bool) { + return comment.ID, comment.Type.HasAttachmentSupport() + }) +} + +// LoadAttachmentsByIssue loads attachments by issue id +func (comments CommentList) LoadAttachmentsByIssue(ctx context.Context) error { + if len(comments) == 0 { + return nil + } + + attachments := make([]*repo_model.Attachment, 0, len(comments)/2) + if err := db.GetEngine(ctx).Where("issue_id=? AND comment_id>0", comments[0].IssueID).Find(&attachments); err != nil { + return err + } + + commentAttachmentsMap := make(map[int64][]*repo_model.Attachment, len(comments)) + for _, attach := range attachments { + commentAttachmentsMap[attach.CommentID] = append(commentAttachmentsMap[attach.CommentID], attach) + } + + for _, comment := range comments { + comment.Attachments = commentAttachmentsMap[comment.ID] + } + return nil +} + // LoadAttachments loads attachments func (comments CommentList) LoadAttachments(ctx context.Context) (err error) { if len(comments) == 0 { @@ -376,16 +380,15 @@ func (comments CommentList) LoadAttachments(ctx context.Context) (err error) { } attachments := make(map[int64][]*repo_model.Attachment, len(comments)) - commentsIDs := comments.getCommentIDs() + commentsIDs := comments.getAttachmentCommentIDs() left := len(commentsIDs) for left > 0 { limit := db.DefaultMaxInSize if left < limit { limit = left } - rows, err := db.GetEngine(ctx).Table("attachment"). - Join("INNER", "comment", "comment.id = attachment.comment_id"). - In("comment.id", commentsIDs[:limit]). + rows, err := db.GetEngine(ctx). + In("comment_id", commentsIDs[:limit]). Rows(new(repo_model.Attachment)) if err != nil { return err @@ -413,11 +416,9 @@ func (comments CommentList) LoadAttachments(ctx context.Context) (err error) { } func (comments CommentList) getReviewIDs() []int64 { - ids := make(container.Set[int64], len(comments)) - for _, comment := range comments { - ids.Add(comment.ReviewID) - } - return ids.Values() + return container.FilterSlice(comments, func(comment *Comment) (int64, bool) { + return comment.ReviewID, comment.ReviewID > 0 + }) } func (comments CommentList) loadReviews(ctx context.Context) error { diff --git a/models/issues/issue_list.go b/models/issues/issue_list.go index 9ccb93bf42..2235f3d3a8 100644 --- a/models/issues/issue_list.go +++ b/models/issues/issue_list.go @@ -74,11 +74,9 @@ func (issues IssueList) LoadRepositories(ctx context.Context) (repo_model.Reposi } func (issues IssueList) getPosterIDs() []int64 { - posterIDs := make(container.Set[int64], len(issues)) - for _, issue := range issues { - posterIDs.Add(issue.PosterID) - } - return posterIDs.Values() + return container.FilterSlice(issues, func(issue *Issue) (int64, bool) { + return issue.PosterID, true + }) } func (issues IssueList) loadPosters(ctx context.Context) error { @@ -193,11 +191,9 @@ func (issues IssueList) loadLabels(ctx context.Context) error { } func (issues IssueList) getMilestoneIDs() []int64 { - ids := make(container.Set[int64], len(issues)) - for _, issue := range issues { - ids.Add(issue.MilestoneID) - } - return ids.Values() + return container.FilterSlice(issues, func(issue *Issue) (int64, bool) { + return issue.MilestoneID, true + }) } func (issues IssueList) loadMilestones(ctx context.Context) error { @@ -391,9 +387,8 @@ func (issues IssueList) LoadAttachments(ctx context.Context) (err error) { if left < limit { limit = left } - rows, err := db.GetEngine(ctx).Table("attachment"). - Join("INNER", "issue", "issue.id = attachment.issue_id"). - In("issue.id", issuesIDs[:limit]). + rows, err := db.GetEngine(ctx). + In("issue_id", issuesIDs[:limit]). Rows(new(repo_model.Attachment)) if err != nil { return err @@ -612,3 +607,23 @@ func (issues IssueList) GetApprovalCounts(ctx context.Context) (map[int64][]*Rev return approvalCountMap, nil } + +func (issues IssueList) LoadIsRead(ctx context.Context, userID int64) error { + issueIDs := issues.getIssueIDs() + issueUsers := make([]*IssueUser, 0, len(issueIDs)) + if err := db.GetEngine(ctx).Where("uid =?", userID). + In("issue_id", issueIDs). + Find(&issueUsers); err != nil { + return err + } + + for _, issueUser := range issueUsers { + for _, issue := range issues { + if issue.ID == issueUser.IssueID { + issue.IsRead = issueUser.IsRead + } + } + } + + return nil +} diff --git a/models/issues/issue_list_test.go b/models/issues/issue_list_test.go index 9069e1012d..10ba38a64b 100644 --- a/models/issues/issue_list_test.go +++ b/models/issues/issue_list_test.go @@ -72,4 +72,9 @@ func TestIssueList_LoadAttributes(t *testing.T) { assert.Nil(t, issue.Project) } } + + assert.NoError(t, issueList.LoadIsRead(db.DefaultContext, 1)) + for _, issue := range issueList { + assert.Equal(t, issue.ID == 1, issue.IsRead, "unexpected is_read value for issue[%d]", issue.ID) + } } diff --git a/models/issues/reaction.go b/models/issues/reaction.go index d5448636fe..eb7faefc79 100644 --- a/models/issues/reaction.go +++ b/models/issues/reaction.go @@ -305,14 +305,12 @@ func (list ReactionList) GroupByType() map[string]ReactionList { } func (list ReactionList) getUserIDs() []int64 { - userIDs := make(container.Set[int64], len(list)) - for _, reaction := range list { + return container.FilterSlice(list, func(reaction *Reaction) (int64, bool) { if reaction.OriginalAuthor != "" { - continue + return 0, false } - userIDs.Add(reaction.UserID) - } - return userIDs.Values() + return reaction.UserID, true + }) } func valuesUser(m map[int64]*user_model.User) []*user_model.User { diff --git a/models/issues/review.go b/models/issues/review.go index 92764db4d1..3c6934b060 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -345,11 +345,9 @@ func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error return nil, err } } - } else if opts.ReviewerTeam != nil { review.Type = ReviewTypeRequest review.ReviewerTeamID = opts.ReviewerTeam.ID - } else { return nil, fmt.Errorf("provide either reviewer or reviewer team") } diff --git a/models/issues/review_list.go b/models/issues/review_list.go index ec6cb07988..7b8c3d319c 100644 --- a/models/issues/review_list.go +++ b/models/issues/review_list.go @@ -38,12 +38,11 @@ func (reviews ReviewList) LoadReviewers(ctx context.Context) error { } func (reviews ReviewList) LoadIssues(ctx context.Context) error { - issueIDs := container.Set[int64]{} - for i := 0; i < len(reviews); i++ { - issueIDs.Add(reviews[i].IssueID) - } + issueIDs := container.FilterSlice(reviews, func(review *Review) (int64, bool) { + return review.IssueID, true + }) - issues, err := GetIssuesByIDs(ctx, issueIDs.Values()) + issues, err := GetIssuesByIDs(ctx, issueIDs) if err != nil { return err } diff --git a/models/migrations/base/db.go b/models/migrations/base/db.go index 51351cc7d3..e584793385 100644 --- a/models/migrations/base/db.go +++ b/models/migrations/base/db.go @@ -89,13 +89,6 @@ func RecreateTable(sess *xorm.Session, bean any) error { hasID = hasID || (column.IsPrimaryKey && column.IsAutoIncrement) } - if hasID && setting.Database.Type.IsMSSQL() { - if _, err := sess.Exec(fmt.Sprintf("SET IDENTITY_INSERT `%s` ON", tempTableName)); err != nil { - log.Error("Unable to set identity insert for table %s. Error: %v", tempTableName, err) - return err - } - } - sqlStringBuilder := &strings.Builder{} _, _ = sqlStringBuilder.WriteString("INSERT INTO `") _, _ = sqlStringBuilder.WriteString(tempTableName) @@ -143,13 +136,6 @@ func RecreateTable(sess *xorm.Session, bean any) error { return err } - if hasID && setting.Database.Type.IsMSSQL() { - if _, err := sess.Exec(fmt.Sprintf("SET IDENTITY_INSERT `%s` OFF", tempTableName)); err != nil { - log.Error("Unable to switch off identity insert for table %s. Error: %v", tempTableName, err) - return err - } - } - switch { case setting.Database.Type.IsSQLite3(): // SQLite will drop all the constraints on the old table @@ -228,7 +214,6 @@ func RecreateTable(sess *xorm.Session, bean any) error { return err } sequenceMap[sequence] = sequenceData - } // CASCADE causes postgres to drop all the constraints on the old table @@ -293,20 +278,6 @@ func RecreateTable(sess *xorm.Session, bean any) error { return err } } - - } - - case setting.Database.Type.IsMSSQL(): - // MSSQL will drop all the constraints on the old table - if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { - log.Error("Unable to drop old table %s. Error: %v", tableName, err) - return err - } - - // MSSQL sp_rename will move all the constraints from the temporary table to the new table - if _, err := sess.Exec(fmt.Sprintf("sp_rename `%s`,`%s`", tempTableName, tableName)); err != nil { - log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) - return err } default: @@ -444,40 +415,6 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin 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) } - case setting.Database.Type.IsMSSQL(): - cols := "" - for _, col := range columnNames { - if cols != "" { - cols += ", " - } - cols += "`" + strings.ToLower(col) + "`" - } - sql := fmt.Sprintf("SELECT Name FROM sys.default_constraints WHERE parent_object_id = OBJECT_ID('%[1]s') AND parent_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) - } - 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) - } - } - 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) - } - 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) - } - } - - 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) - } default: log.Fatal("Unrecognized DB") } @@ -489,21 +426,6 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin func ModifyColumn(x *xorm.Engine, tableName string, col *schemas.Column) error { var indexes map[string]*schemas.Index var err error - // MSSQL have to remove index at first, otherwise alter column will fail - // ref. https://sqlzealots.com/2018/05/09/error-message-the-index-is-dependent-on-column-alter-table-alter-column-failed-because-one-or-more-objects-access-this-column/ - if x.Dialect().URI().DBType == schemas.MSSQL { - indexes, err = x.Dialect().GetIndexes(x.DB(), context.Background(), tableName) - if err != nil { - return err - } - - for _, index := range indexes { - _, err = x.Exec(x.Dialect().DropIndexSQL(tableName, index)) - if err != nil { - return err - } - } - } defer func() { for _, index := range indexes { @@ -612,21 +534,6 @@ func deleteDB() error { } return nil } - case setting.Database.Type.IsMSSQL(): - host, port := setting.ParseMSSQLHostPort(setting.Database.Host) - db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", - host, port, "master", setting.Database.User, setting.Database.Passwd)) - if err != nil { - return err - } - defer db.Close() - - if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS [%s]", setting.Database.Name)); err != nil { - return err - } - if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE [%s]", setting.Database.Name)); err != nil { - return err - } } return nil diff --git a/models/migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml b/models/migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml new file mode 100644 index 0000000000..6feaeb39f0 --- /dev/null +++ b/models/migrations/fixtures/Test_AddUniqueIndexForProjectIssue/project_issue.yml @@ -0,0 +1,9 @@ +- + id: 1 + project_id: 1 + issue_id: 1 + +- + id: 2 + project_id: 1 + issue_id: 1 diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index df8ffc3308..5d80f9fd10 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -574,6 +574,21 @@ var migrations = []Migration{ NewMigration("Ensure every project has exactly one default column - No Op", noopMigration), // v293 -> v294 NewMigration("Ensure every project has exactly one default column", v1_22.CheckProjectColumnsConsistency), + + // Gitea 1.22.0-rc0 ends at 294 + + // v294 -> v295 + NewMigration("Add unique index for project issue table", v1_22.AddUniqueIndexForProjectIssue), + // v295 -> v296 + NewMigration("Add commit status summary table", v1_22.AddCommitStatusSummary), + // v296 -> v297 + NewMigration("Add missing field of commit status summary table", v1_22.AddCommitStatusSummary2), + // v297 -> v298 + NewMigration("Add everyone_access_mode for repo_unit", noopMigration), + // v298 -> v299 + NewMigration("Drop wrongly created table o_auth2_application", v1_22.DropWronglyCreatedTable), + + // Gitea 1.22.0-rc1 ends at 299 } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_11/v110.go b/models/migrations/v1_11/v110.go index 81afa1331d..fce9be847e 100644 --- a/models/migrations/v1_11/v110.go +++ b/models/migrations/v1_11/v110.go @@ -16,9 +16,6 @@ func ChangeReviewContentToText(x *xorm.Engine) error { case schemas.ORACLE: _, err := x.Exec("ALTER TABLE review MODIFY content TEXT") return err - case schemas.MSSQL: - _, err := x.Exec("ALTER TABLE review ALTER COLUMN content TEXT") - return err case schemas.POSTGRES: _, err := x.Exec("ALTER TABLE review ALTER COLUMN content TYPE TEXT") return err diff --git a/models/migrations/v1_11/v111.go b/models/migrations/v1_11/v111.go index c18d7adae1..cc3dc0d545 100644 --- a/models/migrations/v1_11/v111.go +++ b/models/migrations/v1_11/v111.go @@ -263,7 +263,6 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error { for _, u := range units { var found bool for _, team := range teams { - var teamU []*TeamUnit var unitEnabled bool err = sess.Where("team_id = ?", team.ID).Find(&teamU) @@ -332,7 +331,6 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error { } if !protectedBranch.EnableApprovalsWhitelist { - perm, err := getUserRepoPermission(sess, baseRepo, reviewer) if err != nil { return false, err diff --git a/models/migrations/v1_12/v139.go b/models/migrations/v1_12/v139.go index 279aa7df87..5b6576951d 100644 --- a/models/migrations/v1_12/v139.go +++ b/models/migrations/v1_12/v139.go @@ -12,12 +12,9 @@ import ( func PrependRefsHeadsToIssueRefs(x *xorm.Engine) error { var query string - switch { - case setting.Database.Type.IsMSSQL(): - query = "UPDATE `issue` SET `ref` = 'refs/heads/' + `ref` WHERE `ref` IS NOT NULL AND `ref` <> '' AND `ref` NOT LIKE 'refs/%'" - case setting.Database.Type.IsMySQL(): + if setting.Database.Type.IsMySQL() { query = "UPDATE `issue` SET `ref` = CONCAT('refs/heads/', `ref`) WHERE `ref` IS NOT NULL AND `ref` <> '' AND `ref` NOT LIKE 'refs/%';" - default: + } else { query = "UPDATE `issue` SET `ref` = 'refs/heads/' || `ref` WHERE `ref` IS NOT NULL AND `ref` <> '' AND `ref` NOT LIKE 'refs/%'" } diff --git a/models/migrations/v1_13/v145.go b/models/migrations/v1_13/v145.go index 8acb29bf33..5b38f1cd80 100644 --- a/models/migrations/v1_13/v145.go +++ b/models/migrations/v1_13/v145.go @@ -45,32 +45,6 @@ func IncreaseLanguageField(x *xorm.Engine) error { if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE language_stat MODIFY COLUMN language %s", sqlType)); err != nil { return err } - case setting.Database.Type.IsMSSQL(): - // Yet again MSSQL just has to be awkward. - // Here we have to drop the constraints first and then rebuild them - constraints := make([]string, 0) - if err := sess.SQL(`SELECT i.name AS Name - FROM sys.indexes i INNER JOIN sys.index_columns ic - ON i.index_id = ic.index_id AND i.object_id = ic.object_id - INNER JOIN sys.tables AS t - ON t.object_id = i.object_id - INNER JOIN sys.columns c - ON t.object_id = c.object_id AND ic.column_id = c.column_id - WHERE t.name = 'language_stat' AND c.name = 'language'`).Find(&constraints); err != nil { - return fmt.Errorf("Find constraints: %w", err) - } - for _, constraint := range constraints { - if _, err := sess.Exec(fmt.Sprintf("DROP INDEX [%s] ON `language_stat`", constraint)); err != nil { - return fmt.Errorf("Drop table `language_stat` constraint `%s`: %w", constraint, err) - } - } - if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE language_stat ALTER COLUMN language %s", sqlType)); err != nil { - return err - } - // Finally restore the constraint - if err := sess.CreateUniques(new(LanguageStat)); err != nil { - return err - } case setting.Database.Type.IsPostgreSQL(): if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE language_stat ALTER COLUMN language TYPE %s", sqlType)); err != nil { return err diff --git a/models/migrations/v1_13/v151.go b/models/migrations/v1_13/v151.go index 25af1d03ec..ea4a8eae31 100644 --- a/models/migrations/v1_13/v151.go +++ b/models/migrations/v1_13/v151.go @@ -23,36 +23,6 @@ func SetDefaultPasswordToArgon2(x *xorm.Engine) error { case setting.Database.Type.IsPostgreSQL(): _, err := x.Exec("ALTER TABLE `user` ALTER COLUMN passwd_hash_algo SET DEFAULT 'argon2';") return err - case setting.Database.Type.IsMSSQL(): - // need to find the constraint and drop it, then recreate it. - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - res, err := sess.QueryString("SELECT [name] FROM sys.default_constraints WHERE parent_object_id=OBJECT_ID(?) AND COL_NAME(parent_object_id, parent_column_id)=?;", "user", "passwd_hash_algo") - if err != nil { - return err - } - if len(res) > 0 { - constraintName := res[0]["name"] - log.Error("Results of select constraint: %s", constraintName) - _, err := sess.Exec("ALTER TABLE [user] DROP CONSTRAINT " + constraintName) - if err != nil { - return err - } - _, err = sess.Exec("ALTER TABLE [user] ADD CONSTRAINT " + constraintName + " DEFAULT 'argon2' FOR passwd_hash_algo") - if err != nil { - return err - } - } else { - _, err := sess.Exec("ALTER TABLE [user] ADD DEFAULT('argon2') FOR passwd_hash_algo") - if err != nil { - return err - } - } - return sess.Commit() - case setting.Database.Type.IsSQLite3(): // drop through default: diff --git a/models/migrations/v1_14/v158.go b/models/migrations/v1_14/v158.go index 1094d8abf7..2d688b1706 100644 --- a/models/migrations/v1_14/v158.go +++ b/models/migrations/v1_14/v158.go @@ -62,13 +62,6 @@ func UpdateCodeCommentReplies(x *xorm.Engine) error { return err } - if setting.Database.Type.IsMSSQL() { - if _, err := sess.Exec(sqlSelect + " INTO #temp_comments" + sqlTail); err != nil { - log.Error("unable to create temporary table") - return err - } - } - comments := make([]*Comment, 0, batchSize) switch { @@ -78,9 +71,6 @@ func UpdateCodeCommentReplies(x *xorm.Engine) error { fallthrough case setting.Database.Type.IsSQLite3(): sqlCmd = sqlSelect + sqlTail + " LIMIT " + strconv.Itoa(batchSize) + " OFFSET " + strconv.Itoa(start) - case setting.Database.Type.IsMSSQL(): - sqlCmd = "SELECT TOP " + strconv.Itoa(batchSize) + " * FROM #temp_comments WHERE " + - "(id NOT IN ( SELECT TOP " + strconv.Itoa(start) + " id FROM #temp_comments ORDER BY id )) ORDER BY id" default: return fmt.Errorf("Unsupported database type") } diff --git a/models/migrations/v1_14/v165.go b/models/migrations/v1_14/v165.go index 926350cdf7..5b1a779294 100644 --- a/models/migrations/v1_14/v165.go +++ b/models/migrations/v1_14/v165.go @@ -32,13 +32,7 @@ func ConvertHookTaskTypeToVarcharAndTrim(x *xorm.Engine) error { return err } - var hookTaskTrimSQL string - if dbType == schemas.MSSQL { - hookTaskTrimSQL = "UPDATE hook_task SET typ = RTRIM(LTRIM(typ))" - } else { - hookTaskTrimSQL = "UPDATE hook_task SET typ = TRIM(typ)" - } - if _, err := x.Exec(hookTaskTrimSQL); err != nil { + if _, err := x.Exec("UPDATE hook_task SET typ = TRIM(typ)"); err != nil { return err } @@ -58,12 +52,6 @@ func ConvertHookTaskTypeToVarcharAndTrim(x *xorm.Engine) error { return err } - var webhookTrimSQL string - if dbType == schemas.MSSQL { - webhookTrimSQL = "UPDATE webhook SET type = RTRIM(LTRIM(type))" - } else { - webhookTrimSQL = "UPDATE webhook SET type = TRIM(type)" - } - _, err := x.Exec(webhookTrimSQL) + _, err := x.Exec("UPDATE webhook SET type = TRIM(type)") return err } diff --git a/models/migrations/v1_15/v184.go b/models/migrations/v1_15/v184.go index 4b3dd1467a..871c9db18a 100644 --- a/models/migrations/v1_15/v184.go +++ b/models/migrations/v1_15/v184.go @@ -53,16 +53,11 @@ func RenameTaskErrorsToMessage(x *xorm.Engine) error { } } - switch { - case setting.Database.Type.IsMySQL(): + if setting.Database.Type.IsMySQL() { if _, err := sess.Exec("ALTER TABLE `task` CHANGE errors message text"); err != nil { return err } - case setting.Database.Type.IsMSSQL(): - if _, err := sess.Exec("sp_rename 'task.errors', 'message', 'COLUMN'"); err != nil { - return err - } - default: + } else { if _, err := sess.Exec("ALTER TABLE `task` RENAME COLUMN errors TO message"); err != nil { return err } diff --git a/models/migrations/v1_16/v210.go b/models/migrations/v1_16/v210.go index 533bb4bf80..db45b11aed 100644 --- a/models/migrations/v1_16/v210.go +++ b/models/migrations/v1_16/v210.go @@ -4,18 +4,43 @@ package v1_16 //nolint import ( + "crypto/ecdh" "encoding/base32" + "errors" "fmt" "strings" - "code.gitea.io/gitea/models/migrations/base" "code.gitea.io/gitea/modules/timeutil" - "github.com/tstranex/u2f" "xorm.io/xorm" "xorm.io/xorm/schemas" ) +func parseU2FRegistration(raw []byte) (pubKey *ecdh.PublicKey, keyHandle []byte, err error) { + if len(raw) < 69 { + return nil, nil, errors.New("data is too short") + } + if raw[0] != 0x05 { + return nil, nil, errors.New("invalid reserved byte") + } + raw = raw[1:] + + pubKey, err = ecdh.P256().NewPublicKey(raw[:65]) + if err != nil { + return nil, nil, err + } + raw = raw[65:] + + khLen := int(raw[0]) + if len(raw) < khLen { + return nil, nil, errors.New("invalid key handle") + } + raw = raw[1:] + keyHandle = raw[:khLen] + + return pubKey, keyHandle, nil +} + // v208 migration was completely broken func RemigrateU2FCredentials(x *xorm.Engine) error { // Create webauthnCredential table @@ -43,31 +68,6 @@ func RemigrateU2FCredentials(x *xorm.Engine) error { if err != nil { return err } - case schemas.ORACLE: - _, err := x.Exec("ALTER TABLE webauthn_credential MODIFY credential_id VARCHAR(410)") - if err != nil { - return err - } - case schemas.MSSQL: - // This column has an index on it. I could write all of the code to attempt to change the index OR - // I could just use recreate table. - sess := x.NewSession() - if err := sess.Begin(); err != nil { - _ = sess.Close() - return err - } - - if err := base.RecreateTable(sess, new(webauthnCredential)); err != nil { - _ = sess.Close() - return err - } - if err := sess.Commit(); err != nil { - _ = sess.Close() - return err - } - if err := sess.Close(); err != nil { - return err - } case schemas.POSTGRES: _, err := x.Exec("ALTER TABLE webauthn_credential ALTER COLUMN credential_id TYPE VARCHAR(410)") if err != nil { @@ -111,18 +111,8 @@ func RemigrateU2FCredentials(x *xorm.Engine) error { if err := sess.Begin(); err != nil { return fmt.Errorf("unable to allow start session. Error: %w", err) } - if x.Dialect().URI().DBType == schemas.MSSQL { - if _, err := sess.Exec("SET IDENTITY_INSERT `webauthn_credential` ON"); err != nil { - return fmt.Errorf("unable to allow identity insert on webauthn_credential. Error: %w", err) - } - } for _, reg := range regs { - parsed := new(u2f.Registration) - err = parsed.UnmarshalBinary(reg.Raw) - if err != nil { - continue - } - pubKey, err := parsed.PubKey.ECDH() + pubKey, keyHandle, err := parseU2FRegistration(reg.Raw) if err != nil { continue } @@ -131,7 +121,7 @@ func RemigrateU2FCredentials(x *xorm.Engine) error { Name: reg.Name, LowerName: strings.ToLower(reg.Name), UserID: reg.UserID, - CredentialID: base32.HexEncoding.EncodeToString(parsed.KeyHandle), + CredentialID: base32.HexEncoding.EncodeToString(keyHandle), PublicKey: pubKey.Bytes(), AttestationType: "fido-u2f", AAGUID: []byte{}, diff --git a/models/migrations/v1_16/v210_test.go b/models/migrations/v1_16/v210_test.go index d43fb03106..b39646545a 100644 --- a/models/migrations/v1_16/v210_test.go +++ b/models/migrations/v1_16/v210_test.go @@ -4,6 +4,7 @@ package v1_16 //nolint import ( + "encoding/hex" "testing" "code.gitea.io/gitea/models/migrations/base" @@ -13,6 +14,19 @@ import ( "xorm.io/xorm/schemas" ) +func TestParseU2FRegistration(t *testing.T) { + // test vectors from https://github.com/tstranex/u2f/blob/d21a03e0b1d9fc1df59ff54e7a513655c1748b0c/register_test.go#L15 + + const testRegRespHex = "0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c253082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" + + regResp, err := hex.DecodeString(testRegRespHex) + assert.NoError(t, err) + pubKey, keyHandle, err := parseU2FRegistration(regResp) + assert.NoError(t, err) + assert.Equal(t, "04b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9", hex.EncodeToString(pubKey.Bytes())) + assert.Equal(t, "2a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c25", hex.EncodeToString(keyHandle)) +} + func Test_RemigrateU2FCredentials(t *testing.T) { // Create webauthnCredential table type WebauthnCredential struct { diff --git a/models/migrations/v1_17/v220.go b/models/migrations/v1_17/v220.go index 904ddc5192..d4007163ab 100644 --- a/models/migrations/v1_17/v220.go +++ b/models/migrations/v1_17/v220.go @@ -12,14 +12,10 @@ import ( ) func AddContainerRepositoryProperty(x *xorm.Engine) (err error) { - switch x.Dialect().URI().DBType { - case schemas.SQLITE: + if x.Dialect().URI().DBType == schemas.SQLITE { _, err = x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, u.lower_name || '/' || p.lower_name FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?", packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer) - case schemas.MSSQL: - _, err = x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, u.lower_name + '/' + p.lower_name FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?", - packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer) - default: + } else { _, err = x.Exec("INSERT INTO package_property (ref_type, ref_id, name, value) SELECT ?, p.id, ?, CONCAT(u.lower_name, '/', p.lower_name) FROM package p JOIN `user` u ON p.owner_id = u.id WHERE p.type = ?", packages_model.PropertyTypePackage, container_module.PropertyRepository, packages_model.TypeContainer) } diff --git a/models/migrations/v1_17/v223.go b/models/migrations/v1_17/v223.go index 018451ee4c..3592eb1be6 100644 --- a/models/migrations/v1_17/v223.go +++ b/models/migrations/v1_17/v223.go @@ -64,16 +64,11 @@ func RenameCredentialIDBytes(x *xorm.Engine) error { } } - switch { - case setting.Database.Type.IsMySQL(): + if setting.Database.Type.IsMySQL() { if _, err := sess.Exec("ALTER TABLE `webauthn_credential` CHANGE credential_id_bytes credential_id VARBINARY(1024)"); err != nil { return err } - case setting.Database.Type.IsMSSQL(): - if _, err := sess.Exec("sp_rename 'webauthn_credential.credential_id_bytes', 'credential_id', 'COLUMN'"); err != nil { - return err - } - default: + } else { if _, err := sess.Exec("ALTER TABLE `webauthn_credential` RENAME COLUMN credential_id_bytes TO credential_id"); err != nil { return err } diff --git a/models/migrations/v1_20/v245.go b/models/migrations/v1_20/v245.go index ab58d12880..b0d4c21502 100644 --- a/models/migrations/v1_20/v245.go +++ b/models/migrations/v1_20/v245.go @@ -50,8 +50,7 @@ func RenameWebhookOrgToOwner(x *xorm.Engine) error { } } - switch { - case setting.Database.Type.IsMySQL(): + if setting.Database.Type.IsMySQL() { inferredTable, err := x.TableInfo(new(Webhook)) if err != nil { return err @@ -60,11 +59,7 @@ func RenameWebhookOrgToOwner(x *xorm.Engine) error { if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `webhook` CHANGE org_id owner_id %s", sqlType)); err != nil { return err } - case setting.Database.Type.IsMSSQL(): - if _, err := sess.Exec("sp_rename 'webhook.org_id', 'owner_id', 'COLUMN'"); err != nil { - return err - } - default: + } else { if _, err := sess.Exec("ALTER TABLE `webhook` RENAME COLUMN org_id TO owner_id"); err != nil { return err } diff --git a/models/migrations/v1_20/v250.go b/models/migrations/v1_20/v250.go index a09957b291..86388ef0b8 100644 --- a/models/migrations/v1_20/v250.go +++ b/models/migrations/v1_20/v250.go @@ -104,7 +104,7 @@ func ChangeContainerMetadataMultiArch(x *xorm.Engine) error { // Convert to new metadata format - new := &MetadataNew{ + newMetadata := &MetadataNew{ Type: old.Type, IsTagged: old.IsTagged, Platform: old.Platform, @@ -119,7 +119,7 @@ func ChangeContainerMetadataMultiArch(x *xorm.Engine) error { Manifests: manifests, } - metadataJSON, err := json.Marshal(new) + metadataJSON, err := json.Marshal(newMetadata) if err != nil { return err } diff --git a/models/migrations/v1_22/v283.go b/models/migrations/v1_22/v283.go index 0a45c51245..86946d1c39 100644 --- a/models/migrations/v1_22/v283.go +++ b/models/migrations/v1_22/v283.go @@ -4,10 +4,7 @@ package v1_22 //nolint import ( - "fmt" - "xorm.io/xorm" - "xorm.io/xorm/schemas" ) func AddCombinedIndexToIssueUser(x *xorm.Engine) error { @@ -23,18 +20,12 @@ func AddCombinedIndexToIssueUser(x *xorm.Engine) error { return err } for _, issueUser := range duplicatedIssueUsers { - if x.Dialect().URI().DBType == schemas.MSSQL { - if _, err := x.Exec(fmt.Sprintf("delete from issue_user where id in (SELECT top %d id FROM issue_user WHERE issue_id = ? and uid = ?)", issueUser.Cnt-1), issueUser.IssueID, issueUser.UID); err != nil { - return err - } - } else { - var ids []int64 - if err := x.SQL("SELECT id FROM issue_user WHERE issue_id = ? and uid = ? limit ?", issueUser.IssueID, issueUser.UID, issueUser.Cnt-1).Find(&ids); err != nil { - return err - } - if _, err := x.Table("issue_user").In("id", ids).Delete(); err != nil { - return err - } + var ids []int64 + if err := x.SQL("SELECT id FROM issue_user WHERE issue_id = ? and uid = ? limit ?", issueUser.IssueID, issueUser.UID, issueUser.Cnt-1).Find(&ids); err != nil { + return err + } + if _, err := x.Table("issue_user").In("id", ids).Delete(); err != nil { + return err } } diff --git a/models/migrations/v1_22/v286.go b/models/migrations/v1_22/v286.go index fbbd87344f..2d62d7788c 100644 --- a/models/migrations/v1_22/v286.go +++ b/models/migrations/v1_22/v286.go @@ -3,7 +3,6 @@ package v1_22 //nolint import ( - "errors" "fmt" "code.gitea.io/gitea/modules/log" @@ -33,27 +32,10 @@ func expandHashReferencesToSha256(x *xorm.Engine) error { } if !setting.Database.Type.IsSQLite3() { - if setting.Database.Type.IsMSSQL() { - // drop indexes that need to be re-created afterwards - droppedIndexes := []string{ - "DROP INDEX IF EXISTS [IDX_commit_status_context_hash] ON [commit_status]", - "DROP INDEX IF EXISTS [UQE_review_state_pull_commit_user] ON [review_state]", - "DROP INDEX IF EXISTS [UQE_repo_archiver_s] ON [repo_archiver]", - } - for _, s := range droppedIndexes { - _, err := db.Exec(s) - if err != nil { - return errors.New(s + " " + err.Error()) - } - } - } - for _, alts := range alteredTables { var err error if setting.Database.Type.IsMySQL() { _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(64)", alts[0], alts[1])) - } else if setting.Database.Type.IsMSSQL() { - _, err = db.Exec(fmt.Sprintf("ALTER TABLE [%s] ALTER COLUMN [%s] VARCHAR(64)", alts[0], alts[1])) } else { _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` TYPE VARCHAR(64)", alts[0], alts[1])) } @@ -61,20 +43,6 @@ func expandHashReferencesToSha256(x *xorm.Engine) error { return fmt.Errorf("alter column '%s' of table '%s' failed: %w", alts[1], alts[0], err) } } - - if setting.Database.Type.IsMSSQL() { - recreateIndexes := []string{ - "CREATE INDEX IDX_commit_status_context_hash ON commit_status(context_hash)", - "CREATE UNIQUE INDEX UQE_review_state_pull_commit_user ON review_state(user_id, pull_id, commit_sha)", - "CREATE UNIQUE INDEX UQE_repo_archiver_s ON repo_archiver(repo_id, type, commit_id)", - } - for _, s := range recreateIndexes { - _, err := db.Exec(s) - if err != nil { - return errors.New(s + " " + err.Error()) - } - } - } } log.Debug("Updated database tables to hold SHA256 git hash references") diff --git a/models/migrations/v1_22/v294.go b/models/migrations/v1_22/v294.go new file mode 100644 index 0000000000..314b4519f1 --- /dev/null +++ b/models/migrations/v1_22/v294.go @@ -0,0 +1,44 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "xorm.io/xorm" +) + +// AddUniqueIndexForProjectIssue adds unique indexes for project issue table +func AddUniqueIndexForProjectIssue(x *xorm.Engine) error { + // remove possible duplicated records in table project_issue + type result struct { + IssueID int64 + ProjectID int64 + Cnt int + } + var results []result + if err := x.Select("issue_id, project_id, count(*) as cnt"). + Table("project_issue"). + GroupBy("issue_id, project_id"). + Having("count(*) > 1"). + Find(&results); err != nil { + return err + } + for _, r := range results { + var ids []int64 + if err := x.SQL("SELECT id FROM project_issue WHERE issue_id = ? and project_id = ? limit ?", r.IssueID, r.ProjectID, r.Cnt-1).Find(&ids); err != nil { + return err + } + if _, err := x.Table("project_issue").In("id", ids).Delete(); err != nil { + return err + } + } + + // add unique index for project_issue table + type ProjectIssue struct { //revive:disable-line:exported + ID int64 `xorm:"pk autoincr"` + IssueID int64 `xorm:"INDEX unique(s)"` + ProjectID int64 `xorm:"INDEX unique(s)"` + } + + return x.Sync(new(ProjectIssue)) +} diff --git a/models/migrations/v1_22/v294_test.go b/models/migrations/v1_22/v294_test.go new file mode 100644 index 0000000000..82a3bcd602 --- /dev/null +++ b/models/migrations/v1_22/v294_test.go @@ -0,0 +1,52 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "slices" + "testing" + + "code.gitea.io/gitea/models/migrations/base" + + "github.com/stretchr/testify/assert" + "xorm.io/xorm/schemas" +) + +func Test_AddUniqueIndexForProjectIssue(t *testing.T) { + type ProjectIssue struct { //revive:disable-line:exported + ID int64 `xorm:"pk autoincr"` + IssueID int64 `xorm:"INDEX"` + ProjectID int64 `xorm:"INDEX"` + } + + // Prepare and load the testing database + x, deferable := base.PrepareTestEnv(t, 0, new(ProjectIssue)) + defer deferable() + if x == nil || t.Failed() { + return + } + + cnt, err := x.Table("project_issue").Where("project_id=1 AND issue_id=1").Count() + assert.NoError(t, err) + assert.EqualValues(t, 2, cnt) + + assert.NoError(t, AddUniqueIndexForProjectIssue(x)) + + cnt, err = x.Table("project_issue").Where("project_id=1 AND issue_id=1").Count() + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + tables, err := x.DBMetas() + assert.NoError(t, err) + assert.EqualValues(t, 1, len(tables)) + found := false + for _, index := range tables[0].Indexes { + if index.Type == schemas.UniqueType { + found = true + slices.Equal(index.Cols, []string{"project_id", "issue_id"}) + break + } + } + assert.True(t, found) +} diff --git a/models/migrations/v1_22/v295.go b/models/migrations/v1_22/v295.go new file mode 100644 index 0000000000..17bdadb4ad --- /dev/null +++ b/models/migrations/v1_22/v295.go @@ -0,0 +1,18 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import "xorm.io/xorm" + +func AddCommitStatusSummary(x *xorm.Engine) error { + type CommitStatusSummary struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX UNIQUE(repo_id_sha)"` + SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_id_sha)"` + State string `xorm:"VARCHAR(7) NOT NULL"` + } + // there is no migrations because if there is no data on this table, it will fall back to get data + // from commit status + return x.Sync2(new(CommitStatusSummary)) +} diff --git a/models/migrations/v1_22/v296.go b/models/migrations/v1_22/v296.go new file mode 100644 index 0000000000..1ecacab95f --- /dev/null +++ b/models/migrations/v1_22/v296.go @@ -0,0 +1,16 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import "xorm.io/xorm" + +func AddCommitStatusSummary2(x *xorm.Engine) error { + type CommitStatusSummary struct { + ID int64 `xorm:"pk autoincr"` + TargetURL string `xorm:"TEXT"` + } + // there is no migrations because if there is no data on this table, it will fall back to get data + // from commit status + return x.Sync(new(CommitStatusSummary)) +} diff --git a/models/migrations/v1_22/v298.go b/models/migrations/v1_22/v298.go new file mode 100644 index 0000000000..b9f3b95ade --- /dev/null +++ b/models/migrations/v1_22/v298.go @@ -0,0 +1,10 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import "xorm.io/xorm" + +func DropWronglyCreatedTable(x *xorm.Engine) error { + return x.DropTables("o_auth2_application") +} diff --git a/models/migrations/v1_23/main_test.go b/models/migrations/v1_23/main_test.go new file mode 100644 index 0000000000..b7948bd4dd --- /dev/null +++ b/models/migrations/v1_23/main_test.go @@ -0,0 +1,14 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_23 //nolint + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" +) + +func TestMain(m *testing.M) { + base.MainTest(m) +} diff --git a/models/migrations/v1_6/v71.go b/models/migrations/v1_6/v71.go index 4e50ca9219..586187228b 100644 --- a/models/migrations/v1_6/v71.go +++ b/models/migrations/v1_6/v71.go @@ -61,7 +61,6 @@ func AddScratchHash(x *xorm.Engine) error { if _, err := sess.ID(tfa.ID).Cols("scratch_salt, scratch_hash").Update(tfa); err != nil { return fmt.Errorf("couldn't add in scratch_hash and scratch_salt: %w", err) } - } } diff --git a/models/migrations/v1_8/v81.go b/models/migrations/v1_8/v81.go index a100dc1ef7..734fc24641 100644 --- a/models/migrations/v1_8/v81.go +++ b/models/migrations/v1_8/v81.go @@ -18,8 +18,6 @@ func ChangeU2FCounterType(x *xorm.Engine) error { _, err = x.Exec("ALTER TABLE `u2f_registration` MODIFY `counter` BIGINT") case schemas.POSTGRES: _, err = x.Exec("ALTER TABLE `u2f_registration` ALTER COLUMN `counter` SET DATA TYPE bigint") - case schemas.MSSQL: - _, err = x.Exec("ALTER TABLE `u2f_registration` ALTER COLUMN `counter` BIGINT") } if err != nil { diff --git a/models/migrations/v1_9/v85.go b/models/migrations/v1_9/v85.go index 9419ee1aae..a23d7c5d6e 100644 --- a/models/migrations/v1_9/v85.go +++ b/models/migrations/v1_9/v85.go @@ -81,7 +81,6 @@ func HashAppToken(x *xorm.Engine) error { if _, err := sess.ID(token.ID).Cols("token_hash, token_salt, token_last_eight, sha1").Update(token); err != nil { return fmt.Errorf("couldn't add in sha1, token_hash, token_salt and token_last_eight: %w", err) } - } } diff --git a/models/organization/team.go b/models/organization/team.go index 17db11c42d..1b737c2d3d 100644 --- a/models/organization/team.go +++ b/models/organization/team.go @@ -222,9 +222,8 @@ func GetTeamIDsByNames(ctx context.Context, orgID int64, names []string, ignoreN if err != nil { if ignoreNonExistent { continue - } else { - return nil, err } + return nil, err } ids = append(ids, u.ID) } diff --git a/models/project/board.go b/models/project/board.go index 5f142a356c..7faabc52c5 100644 --- a/models/project/board.go +++ b/models/project/board.go @@ -110,13 +110,11 @@ func createBoardsForProjectsType(ctx context.Context, project *Project) error { var items []string switch project.BoardType { - case BoardTypeBugTriage: items = setting.Project.ProjectBoardBugTriageType case BoardTypeBasicKanban: items = setting.Project.ProjectBoardBasicKanbanType - case BoardTypeNone: fallthrough default: diff --git a/models/repo/archive_download_count.go b/models/repo/archive_download_count.go new file mode 100644 index 0000000000..9c1382e0ac --- /dev/null +++ b/models/repo/archive_download_count.go @@ -0,0 +1,90 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "context" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/git" + api "code.gitea.io/gitea/modules/structs" +) + +// RepoArchiveDownloadCount counts all archive downloads for a tag +type RepoArchiveDownloadCount struct { //nolint:revive + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"index unique(s)"` + ReleaseID int64 `xorm:"index unique(s)"` + Type git.ArchiveType `xorm:"unique(s)"` + Count int64 +} + +func init() { + db.RegisterModel(new(RepoArchiveDownloadCount)) +} + +// CountArchiveDownload adds one download the the given archive +func CountArchiveDownload(ctx context.Context, repoID, releaseID int64, tp git.ArchiveType) error { + updateCount, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("release_id = ?", releaseID).And("`type` = ?", tp).Incr("count").Update(new(RepoArchiveDownloadCount)) + if err != nil { + return err + } + + if updateCount != 0 { + // The count was updated, so we can exit + return nil + } + + // The archive does not esxists in the databse, so let's add it + newCounter := &RepoArchiveDownloadCount{ + RepoID: repoID, + ReleaseID: releaseID, + Type: tp, + Count: 1, + } + + _, err = db.GetEngine(ctx).Insert(newCounter) + return err +} + +// GetArchiveDownloadCount returns the download count of a tag +func GetArchiveDownloadCount(ctx context.Context, repoID, releaseID int64) (*api.TagArchiveDownloadCount, error) { + downloadCountList := make([]RepoArchiveDownloadCount, 0) + err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("release_id = ?", releaseID).Find(&downloadCountList) + if err != nil { + return nil, err + } + + tagCounter := new(api.TagArchiveDownloadCount) + + for _, singleCount := range downloadCountList { + switch singleCount.Type { + case git.ZIP: + tagCounter.Zip = singleCount.Count + case git.TARGZ: + tagCounter.TarGz = singleCount.Count + } + } + + return tagCounter, nil +} + +// GetDownloadCountForTagName returns the download count of a tag with the given name +func GetArchiveDownloadCountForTagName(ctx context.Context, repoID int64, tagName string) (*api.TagArchiveDownloadCount, error) { + release, err := GetRelease(ctx, repoID, tagName) + if err != nil { + if IsErrReleaseNotExist(err) { + return new(api.TagArchiveDownloadCount), nil + } + return nil, err + } + + return GetArchiveDownloadCount(ctx, repoID, release.ID) +} + +// DeleteArchiveDownloadCountForRelease deletes the release from the repo_archive_download_count table +func DeleteArchiveDownloadCountForRelease(ctx context.Context, releaseID int64) error { + _, err := db.GetEngine(ctx).Delete(&RepoArchiveDownloadCount{ReleaseID: releaseID}) + return err +} diff --git a/models/repo/archive_download_count_test.go b/models/repo/archive_download_count_test.go new file mode 100644 index 0000000000..53bdf9a1e0 --- /dev/null +++ b/models/repo/archive_download_count_test.go @@ -0,0 +1,65 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo_test + +import ( + "testing" + + "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/git" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRepoArchiveDownloadCount(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + release, err := repo_model.GetReleaseByID(db.DefaultContext, 1) + require.NoError(t, err) + + // We have no count, so it should return 0 + downloadCount, err := repo_model.GetArchiveDownloadCount(db.DefaultContext, release.RepoID, release.ID) + require.NoError(t, err) + assert.Equal(t, int64(0), downloadCount.Zip) + assert.Equal(t, int64(0), downloadCount.TarGz) + + // Set the TarGz counter to 1 + err = repo_model.CountArchiveDownload(db.DefaultContext, release.RepoID, release.ID, git.TARGZ) + require.NoError(t, err) + + downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName) + require.NoError(t, err) + assert.Equal(t, int64(0), downloadCount.Zip) + assert.Equal(t, int64(1), downloadCount.TarGz) + + // Set the TarGz counter to 2 + err = repo_model.CountArchiveDownload(db.DefaultContext, release.RepoID, release.ID, git.TARGZ) + require.NoError(t, err) + + downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName) + require.NoError(t, err) + assert.Equal(t, int64(0), downloadCount.Zip) + assert.Equal(t, int64(2), downloadCount.TarGz) + + // Set the Zip counter to 1 + err = repo_model.CountArchiveDownload(db.DefaultContext, release.RepoID, release.ID, git.ZIP) + require.NoError(t, err) + + downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName) + require.NoError(t, err) + assert.Equal(t, int64(1), downloadCount.Zip) + assert.Equal(t, int64(2), downloadCount.TarGz) + + // Delete the count + err = repo_model.DeleteArchiveDownloadCountForRelease(db.DefaultContext, release.ID) + require.NoError(t, err) + + downloadCount, err = repo_model.GetArchiveDownloadCountForTagName(db.DefaultContext, release.RepoID, release.TagName) + require.NoError(t, err) + assert.Equal(t, int64(0), downloadCount.Zip) + assert.Equal(t, int64(0), downloadCount.TarGz) +} diff --git a/models/repo/archiver.go b/models/repo/archiver.go index 14ffa1d89b..3f05fcf752 100644 --- a/models/repo/archiver.go +++ b/models/repo/archiver.go @@ -35,6 +35,7 @@ type RepoArchiver struct { //revive:disable-line:exported Status ArchiverStatus CommitID string `xorm:"VARCHAR(64) unique(s)"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"` + ReleaseID int64 `xorm:"-"` } func init() { diff --git a/models/repo/attachment.go b/models/repo/attachment.go index 64df6b166e..546e409de7 100644 --- a/models/repo/attachment.go +++ b/models/repo/attachment.go @@ -24,7 +24,7 @@ type Attachment struct { IssueID int64 `xorm:"INDEX"` // maybe zero when creating ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating UploaderID int64 `xorm:"INDEX DEFAULT 0"` // Notice: will be zero before this column added - CommentID int64 + CommentID int64 `xorm:"INDEX"` Name string DownloadCount int64 `xorm:"DEFAULT 0"` Size int64 `xorm:"DEFAULT 0"` diff --git a/models/repo/release.go b/models/repo/release.go index a9f65f6c3e..075e287174 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -65,28 +65,30 @@ func (err ErrReleaseNotExist) Unwrap() error { // Release represents a release of repository. type Release struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"INDEX UNIQUE(n)"` - Repo *Repository `xorm:"-"` - PublisherID int64 `xorm:"INDEX"` - Publisher *user_model.User `xorm:"-"` - TagName string `xorm:"INDEX UNIQUE(n)"` - OriginalAuthor string - OriginalAuthorID int64 `xorm:"index"` - LowerTagName string - Target string - TargetBehind string `xorm:"-"` // to handle non-existing or empty target - Title string - Sha1 string `xorm:"VARCHAR(64)"` - NumCommits int64 - NumCommitsBehind int64 `xorm:"-"` - Note string `xorm:"TEXT"` - RenderedNote template.HTML `xorm:"-"` - IsDraft bool `xorm:"NOT NULL DEFAULT false"` - IsPrerelease bool `xorm:"NOT NULL DEFAULT false"` - IsTag bool `xorm:"NOT NULL DEFAULT false"` // will be true only if the record is a tag and has no related releases - Attachments []*Attachment `xorm:"-"` - CreatedUnix timeutil.TimeStamp `xorm:"INDEX"` + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX UNIQUE(n)"` + Repo *Repository `xorm:"-"` + PublisherID int64 `xorm:"INDEX"` + Publisher *user_model.User `xorm:"-"` + TagName string `xorm:"INDEX UNIQUE(n)"` + OriginalAuthor string + OriginalAuthorID int64 `xorm:"index"` + LowerTagName string + Target string + TargetBehind string `xorm:"-"` // to handle non-existing or empty target + Title string + Sha1 string `xorm:"VARCHAR(64)"` + HideArchiveLinks bool `xorm:"NOT NULL DEFAULT false"` + NumCommits int64 + NumCommitsBehind int64 `xorm:"-"` + Note string `xorm:"TEXT"` + RenderedNote template.HTML `xorm:"-"` + IsDraft bool `xorm:"NOT NULL DEFAULT false"` + IsPrerelease bool `xorm:"NOT NULL DEFAULT false"` + IsTag bool `xorm:"NOT NULL DEFAULT false"` // will be true only if the record is a tag and has no related releases + Attachments []*Attachment `xorm:"-"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX"` + ArchiveDownloadCount *structs.TagArchiveDownloadCount `xorm:"-"` } func init() { @@ -112,9 +114,22 @@ func (r *Release) LoadAttributes(ctx context.Context) error { } } } + + err = r.LoadArchiveDownloadCount(ctx) + if err != nil { + return err + } + return GetReleaseAttachments(ctx, r) } +// LoadArchiveDownloadCount loads the download count for the source archives +func (r *Release) LoadArchiveDownloadCount(ctx context.Context) error { + var err error + r.ArchiveDownloadCount, err = GetArchiveDownloadCount(ctx, r.RepoID, r.ID) + return err +} + // APIURL the api url for a release. release must have attributes loaded func (r *Release) APIURL() string { return r.Repo.APIURL() + "/releases/" + strconv.FormatInt(r.ID, 10) @@ -447,6 +462,18 @@ func PushUpdateDeleteTagsContext(ctx context.Context, repo *Repository, tags []s lowerTags = append(lowerTags, strings.ToLower(tag)) } + for _, tag := range tags { + release, err := GetRelease(ctx, repo.ID, tag) + if err != nil { + return fmt.Errorf("GetRelease: %w", err) + } + + err = DeleteArchiveDownloadCountForRelease(ctx, release.ID) + if err != nil { + return fmt.Errorf("DeleteTagArchiveDownloadCount: %w", err) + } + } + if _, err := db.GetEngine(ctx). Where("repo_id = ? AND is_tag = ?", repo.ID, true). In("lower_tag_name", lowerTags). diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index cb7cd47a8d..987c7df9b0 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -104,18 +104,19 @@ func (repos RepositoryList) LoadAttributes(ctx context.Context) error { return nil } - set := make(container.Set[int64]) + userIDs := container.FilterSlice(repos, func(repo *Repository) (int64, bool) { + return repo.OwnerID, true + }) repoIDs := make([]int64, len(repos)) for i := range repos { - set.Add(repos[i].OwnerID) repoIDs[i] = repos[i].ID } // Load owners. - users := make(map[int64]*user_model.User, len(set)) + users := make(map[int64]*user_model.User, len(userIDs)) if err := db.GetEngine(ctx). Where("id > 0"). - In("id", set.Values()). + In("id", userIDs). Find(&users); err != nil { return fmt.Errorf("find users: %w", err) } diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go index 330b1e5546..d3fbd961bd 100644 --- a/models/repo/user_repo.go +++ b/models/repo/user_repo.go @@ -95,10 +95,7 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us // and just waste 1 unit is cheaper than re-allocate memory once. users := make([]*user_model.User, 0, len(uniqueUserIDs)+1) if len(userIDs) > 0 { - if err = e.In("id", uniqueUserIDs.Values()). - Where(builder.Eq{"`user`.is_active": true}). - OrderBy(user_model.GetOrderByName()). - Find(&users); err != nil { + if err = e.In("id", uniqueUserIDs.Values()).OrderBy(user_model.GetOrderByName()).Find(&users); err != nil { return nil, err } } @@ -120,8 +117,7 @@ func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64) return nil, err } - cond := builder.And(builder.Neq{"`user`.id": posterID}). - And(builder.Eq{"`user`.is_active": true}) + cond := builder.And(builder.Neq{"`user`.id": posterID}) if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate { // This a private repository: @@ -139,7 +135,6 @@ func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64) // the owner of a private repo needs to be explicitly added. cond = cond.Or(builder.Eq{"`user`.id": repo.Owner.ID}) } - } else { // This is a "public" repository: // Any user that has read access, is a watcher or organization member can be requested to review diff --git a/models/repo/user_repo_test.go b/models/repo/user_repo_test.go index 0433ff83d8..ad794beb9b 100644 --- a/models/repo/user_repo_test.go +++ b/models/repo/user_repo_test.go @@ -26,17 +26,10 @@ func TestRepoAssignees(t *testing.T) { repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}) users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21) assert.NoError(t, err) - if assert.Len(t, users, 3) { - assert.ElementsMatch(t, []int64{15, 16, 18}, []int64{users[0].ID, users[1].ID, users[2].ID}) - } - - // do not return deactivated users - assert.NoError(t, user_model.UpdateUserCols(db.DefaultContext, &user_model.User{ID: 15, IsActive: false}, "is_active")) - users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21) - assert.NoError(t, err) - if assert.Len(t, users, 2) { - assert.NotContains(t, []int64{users[0].ID, users[1].ID}, 15) - } + assert.Len(t, users, 3) + assert.Equal(t, users[0].ID, int64(15)) + assert.Equal(t, users[1].ID, int64(18)) + assert.Equal(t, users[2].ID, int64(16)) } func TestRepoGetReviewers(t *testing.T) { @@ -48,19 +41,17 @@ func TestRepoGetReviewers(t *testing.T) { ctx := db.DefaultContext reviewers, err := repo_model.GetReviewers(ctx, repo1, 2, 2) assert.NoError(t, err) - if assert.Len(t, reviewers, 3) { - assert.ElementsMatch(t, []int64{1, 4, 11}, []int64{reviewers[0].ID, reviewers[1].ID, reviewers[2].ID}) - } + assert.Len(t, reviewers, 4) // should include doer if doer is not PR poster. reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 2) assert.NoError(t, err) - assert.Len(t, reviewers, 3) + assert.Len(t, reviewers, 4) // should not include PR poster, if PR poster would be otherwise eligible reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 4) assert.NoError(t, err) - assert.Len(t, reviewers, 2) + assert.Len(t, reviewers, 3) // test private user repo repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) diff --git a/models/repo_transfer.go b/models/repo_transfer.go index f20c5bcdc0..0c23d759f9 100644 --- a/models/repo_transfer.go +++ b/models/repo_transfer.go @@ -5,6 +5,7 @@ package models import ( "context" + "errors" "fmt" "code.gitea.io/gitea/models/db" @@ -120,7 +121,7 @@ func DeleteRepositoryTransfer(ctx context.Context, repoID int64) error { func TestRepositoryReadyForTransfer(status repo_model.RepositoryStatus) error { switch status { case repo_model.RepositoryBeingMigrated: - return fmt.Errorf("repo is not ready, currently migrating") + return errors.New("repo is not ready, currently migrating") case repo_model.RepositoryPendingTransfer: return ErrRepoTransferInProgress{} } diff --git a/models/unittest/fixtures.go b/models/unittest/fixtures.go index 9ce0909589..63b26a0af7 100644 --- a/models/unittest/fixtures.go +++ b/models/unittest/fixtures.go @@ -60,8 +60,6 @@ func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) { dialect = "postgres" case schemas.MYSQL: dialect = "mysql" - case schemas.MSSQL: - dialect = "mssql" case schemas.SQLITE: dialect = "sqlite3" default: diff --git a/models/user/fixtures/user.yml b/models/user/fixtures/user.yml new file mode 100644 index 0000000000..b1892f331b --- /dev/null +++ b/models/user/fixtures/user.yml @@ -0,0 +1,36 @@ +- + id: 1041 + lower_name: remote01 + name: remote01 + full_name: Remote01 + email: remote01@example.com + keep_email_private: false + email_notifications_preference: onmention + passwd: ZogKvWdyEx:password + passwd_hash_algo: dummy + must_change_password: false + login_source: 1001 + login_name: 123 + type: 5 + salt: ZogKvWdyEx + max_repo_creation: -1 + is_active: true + is_admin: false + is_restricted: false + allow_git_hook: false + allow_import_local: false + allow_create_organization: true + prohibit_login: true + avatar: avatarremote01 + avatar_email: avatarremote01@example.com + use_custom_avatar: false + num_followers: 0 + num_following: 0 + num_stars: 0 + num_repos: 0 + num_teams: 0 + num_members: 0 + visibility: 0 + repo_admin_change_team_access: false + theme: "" + keep_activity_private: false diff --git a/models/user/search.go b/models/user/search.go index 45b051187e..04c434e4fa 100644 --- a/models/user/search.go +++ b/models/user/search.go @@ -45,7 +45,11 @@ type SearchUserOptions struct { func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session { var cond builder.Cond - cond = builder.Eq{"type": opts.Type} + if opts.Type == UserTypeIndividual { + cond = builder.In("type", UserTypeIndividual, UserTypeRemoteUser) + } else { + cond = builder.Eq{"type": opts.Type} + } if opts.IncludeReserved { if opts.Type == UserTypeIndividual { cond = cond.Or(builder.Eq{"type": UserTypeUserReserved}).Or( @@ -140,7 +144,7 @@ func SearchUsers(ctx context.Context, opts *SearchUserOptions) (users []*User, _ sessQuery := opts.toSearchQueryBase(ctx).OrderBy(opts.OrderBy.String()) defer sessQuery.Close() - if opts.Page != 0 { + if opts.PageSize > 0 { sessQuery = db.SetSessionPagination(sessQuery, opts) } diff --git a/models/user/user.go b/models/user/user.go index ff85c2cfa0..10c4915f5e 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -216,7 +216,7 @@ func (u *User) GetEmail() string { // GetAllUsers returns a slice of all individual users found in DB. func GetAllUsers(ctx context.Context) ([]*User, error) { users := make([]*User, 0) - return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users) + return users, db.GetEngine(ctx).OrderBy("id").In("type", UserTypeIndividual, UserTypeRemoteUser).Find(&users) } // GetAllAdmins returns a slice of all adminusers found in DB. @@ -416,6 +416,10 @@ func (u *User) IsBot() bool { return u.Type == UserTypeBot } +func (u *User) IsRemote() bool { + return u.Type == UserTypeRemoteUser +} + // DisplayName returns full name if it's not empty, // returns username otherwise. func (u *User) DisplayName() string { @@ -918,7 +922,8 @@ func GetUserByName(ctx context.Context, name string) (*User, error) { if len(name) == 0 { return nil, ErrUserNotExist{Name: name} } - u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual} + // adding Type: UserTypeIndividual is a noop because it is zero and discarded + u := &User{LowerName: strings.ToLower(name)} has, err := db.GetEngine(ctx).Get(u) if err != nil { return nil, err @@ -1002,9 +1007,8 @@ func GetUserIDsByNames(ctx context.Context, names []string, ignoreNonExistent bo if err != nil { if ignoreNonExistent { continue - } else { - return nil, err } + return nil, err } ids = append(ids, u.ID) } @@ -1246,3 +1250,21 @@ func GetOrderByName() string { } return "name" } + +// IsFeatureDisabledWithLoginType checks if a user feature is disabled, taking into account the login type of the +// user if applicable +func IsFeatureDisabledWithLoginType(user *User, feature string) bool { + // NOTE: in the long run it may be better to check the ExternalLoginUser table rather than user.LoginType + return (user != nil && user.LoginType > auth.Plain && setting.Admin.ExternalUserDisableFeatures.Contains(feature)) || + setting.Admin.UserDisabledFeatures.Contains(feature) +} + +// DisabledFeaturesWithLoginType returns the set of user features disabled, taking into account the login type +// of the user if applicable +func DisabledFeaturesWithLoginType(user *User) *container.Set[string] { + // NOTE: in the long run it may be better to check the ExternalLoginUser table rather than user.LoginType + if user != nil && user.LoginType > auth.Plain { + return &setting.Admin.ExternalUserDisableFeatures + } + return &setting.Admin.UserDisabledFeatures +} diff --git a/models/user/user_test.go b/models/user/user_test.go index bb72743301..4bf8c71369 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -5,8 +5,8 @@ package user_test import ( "context" + "crypto/rand" "fmt" - "math/rand" "strings" "testing" "time" @@ -16,10 +16,12 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/password/hash" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" ) @@ -32,6 +34,35 @@ func TestOAuth2Application_LoadUser(t *testing.T) { assert.NotNil(t, user) } +func TestGetUserByName(t *testing.T) { + defer tests.AddFixtures("models/user/fixtures/")() + assert.NoError(t, unittest.PrepareTestDatabase()) + + { + _, err := user_model.GetUserByName(db.DefaultContext, "") + assert.True(t, user_model.IsErrUserNotExist(err), err) + } + { + _, err := user_model.GetUserByName(db.DefaultContext, "UNKNOWN") + assert.True(t, user_model.IsErrUserNotExist(err), err) + } + { + user, err := user_model.GetUserByName(db.DefaultContext, "USER2") + assert.NoError(t, err) + assert.Equal(t, user.Name, "user2") + } + { + user, err := user_model.GetUserByName(db.DefaultContext, "org3") + assert.NoError(t, err) + assert.Equal(t, user.Name, "org3") + } + { + user, err := user_model.GetUserByName(db.DefaultContext, "remote01") + assert.NoError(t, err) + assert.Equal(t, user.Name, "remote01") + } +} + func TestGetUserEmailsByNames(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) @@ -60,7 +91,24 @@ func TestCanCreateOrganization(t *testing.T) { assert.False(t, user.CanCreateOrganization()) } +func TestGetAllUsers(t *testing.T) { + defer tests.AddFixtures("models/user/fixtures/")() + assert.NoError(t, unittest.PrepareTestDatabase()) + + users, err := user_model.GetAllUsers(db.DefaultContext) + assert.NoError(t, err) + + found := make(map[user_model.UserType]bool, 0) + for _, user := range users { + found[user.Type] = true + } + assert.True(t, found[user_model.UserTypeIndividual], users) + assert.True(t, found[user_model.UserTypeRemoteUser], users) + assert.False(t, found[user_model.UserTypeOrganization], users) +} + func TestSearchUsers(t *testing.T) { + defer tests.AddFixtures("models/user/fixtures/")() assert.NoError(t, unittest.PrepareTestDatabase()) testSuccess := func(opts *user_model.SearchUserOptions, expectedUserOrOrgIDs []int64) { users, _, err := user_model.SearchUsers(db.DefaultContext, opts) @@ -101,13 +149,13 @@ func TestSearchUsers(t *testing.T) { } testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}}, - []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40}) + []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40, 1041}) testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(false)}, []int64{9}) testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)}, - []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40}) + []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40, 1041}) testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)}, []int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) @@ -123,7 +171,7 @@ func TestSearchUsers(t *testing.T) { []int64{29}) testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: optional.Some(true)}, - []int64{37}) + []int64{1041, 37}) testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsTwoFactorEnabled: optional.Some(true)}, []int64{24}) @@ -542,3 +590,37 @@ func Test_NormalizeUserFromEmail(t *testing.T) { } } } + +func TestDisabledUserFeatures(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + testValues := container.SetOf(setting.UserFeatureDeletion, + setting.UserFeatureManageSSHKeys, + setting.UserFeatureManageGPGKeys) + + oldSetting := setting.Admin.ExternalUserDisableFeatures + defer func() { + setting.Admin.ExternalUserDisableFeatures = oldSetting + }() + setting.Admin.ExternalUserDisableFeatures = testValues + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + assert.Len(t, setting.Admin.UserDisabledFeatures.Values(), 0) + + // no features should be disabled with a plain login type + assert.LessOrEqual(t, user.LoginType, auth.Plain) + assert.Len(t, user_model.DisabledFeaturesWithLoginType(user).Values(), 0) + for _, f := range testValues.Values() { + assert.False(t, user_model.IsFeatureDisabledWithLoginType(user, f)) + } + + // check disabled features with external login type + user.LoginType = auth.OAuth2 + + // all features should be disabled + assert.NotEmpty(t, user_model.DisabledFeaturesWithLoginType(user).Values()) + for _, f := range testValues.Values() { + assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f)) + } +} diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go index 4ab806573b..f3370f3db5 100644 --- a/models/webhook/webhook.go +++ b/models/webhook/webhook.go @@ -361,6 +361,15 @@ func (w Webhook) HeaderAuthorization() (string, error) { return secret.DecryptSecret(setting.SecretKey, w.HeaderAuthorizationEncrypted) } +// HeaderAuthorizationTrimPrefix returns the decrypted Authorization with a specified prefix trimmed. +func (w Webhook) HeaderAuthorizationTrimPrefix(prefix string) (string, error) { + s, err := w.HeaderAuthorization() + if err != nil { + return "", err + } + return strings.TrimPrefix(s, prefix), nil +} + // SetHeaderAuthorization encrypts and sets the Authorization header. func (w *Webhook) SetHeaderAuthorization(cleartext string) error { if cleartext == "" { diff --git a/modules/auth/password/password.go b/modules/auth/password/password.go index 27074358a9..85f9780709 100644 --- a/modules/auth/password/password.go +++ b/modules/auth/password/password.go @@ -63,16 +63,16 @@ func NewComplexity() { func setupComplexity(values []string) { if len(values) != 1 || values[0] != "off" { for _, val := range values { - if complex, ok := charComplexities[val]; ok { - validChars += complex.ValidChars - requiredList = append(requiredList, complex) + if complexity, ok := charComplexities[val]; ok { + validChars += complexity.ValidChars + requiredList = append(requiredList, complexity) } } if len(requiredList) == 0 { // No valid character classes found; use all classes as default - for _, complex := range charComplexities { - validChars += complex.ValidChars - requiredList = append(requiredList, complex) + for _, complexity := range charComplexities { + validChars += complexity.ValidChars + requiredList = append(requiredList, complexity) } } } diff --git a/modules/auth/password/pwn/pwn_test.go b/modules/auth/password/pwn/pwn_test.go index f9deadc8d7..a2a6b3a174 100644 --- a/modules/auth/password/pwn/pwn_test.go +++ b/modules/auth/password/pwn/pwn_test.go @@ -4,9 +4,8 @@ package pwn import ( - "math/rand" + "math/rand/v2" "net/http" - "os" "strings" "testing" "time" @@ -18,11 +17,6 @@ var client = New(WithHTTP(&http.Client{ Timeout: time.Second * 2, })) -func TestMain(m *testing.M) { - rand.Seed(time.Now().Unix()) - os.Exit(m.Run()) -} - func TestPassword(t *testing.T) { // Check input error _, err := client.CheckPassword("", false) @@ -81,24 +75,24 @@ func testPassword() string { // Set special character for i := 0; i < 5; i++ { - random := rand.Intn(len(specialCharSet)) + random := rand.IntN(len(specialCharSet)) password.WriteString(string(specialCharSet[random])) } // Set numeric for i := 0; i < 5; i++ { - random := rand.Intn(len(numberSet)) + random := rand.IntN(len(numberSet)) password.WriteString(string(numberSet[random])) } // Set uppercase for i := 0; i < 5; i++ { - random := rand.Intn(len(upperCharSet)) + random := rand.IntN(len(upperCharSet)) password.WriteString(string(upperCharSet[random])) } for i := 0; i < 5; i++ { - random := rand.Intn(len(allCharSet)) + random := rand.IntN(len(allCharSet)) password.WriteString(string(allCharSet[random])) } inRune := []rune(password.String()) diff --git a/modules/container/filter.go b/modules/container/filter.go new file mode 100644 index 0000000000..37ec7c3d56 --- /dev/null +++ b/modules/container/filter.go @@ -0,0 +1,21 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package container + +import "slices" + +// FilterSlice ranges over the slice and calls include() for each element. +// If the second returned value is true, the first returned value will be included in the resulting +// slice (after deduplication). +func FilterSlice[E any, T comparable](s []E, include func(E) (T, bool)) []T { + filtered := make([]T, 0, len(s)) // slice will be clipped before returning + seen := make(map[T]bool, len(s)) + for i := range s { + if v, ok := include(s[i]); ok && !seen[v] { + filtered = append(filtered, v) + seen[v] = true + } + } + return slices.Clip(filtered) +} diff --git a/modules/container/filter_test.go b/modules/container/filter_test.go new file mode 100644 index 0000000000..ad304e5abb --- /dev/null +++ b/modules/container/filter_test.go @@ -0,0 +1,28 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package container + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFilterMapUnique(t *testing.T) { + result := FilterSlice([]int{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + }, func(i int) (int, bool) { + switch i { + case 0: + return 0, true // included later + case 1: + return 0, true // duplicate of previous (should be ignored) + case 2: + return 2, false // not included + default: + return i, true + } + }) + assert.Equal(t, []int{0, 3, 4, 5, 6, 7, 8, 9}, result) +} diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go index 043dbb44bd..c988d6ab86 100644 --- a/modules/git/batch_reader.go +++ b/modules/git/batch_reader.go @@ -307,10 +307,10 @@ func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBu // Deal with the binary hash idx = 0 - len := objectFormat.FullLength() / 2 - for idx < len { + length := objectFormat.FullLength() / 2 + for idx < length { var read int - read, err = rd.Read(shaBuf[idx:len]) + read, err = rd.Read(shaBuf[idx:length]) n += read if err != nil { return mode, fname, sha, n, err diff --git a/modules/git/commit.go b/modules/git/commit.go index 00681e3b2f..b5ae2e0e52 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -462,7 +462,7 @@ func parseCommitFileStatus(fileStatus *CommitFileStatus, stdout io.Reader) { _, _ = rd.Discard(1) } for { - modifier, err := rd.ReadSlice('\x00') + modifier, err := rd.ReadString('\x00') if err != nil { if err != io.EOF { log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err) diff --git a/modules/git/commit_reader.go b/modules/git/commit_reader.go index 49159cc418..8e2523d7fb 100644 --- a/modules/git/commit_reader.go +++ b/modules/git/commit_reader.go @@ -49,9 +49,8 @@ readLoop: if len(line) > 0 && line[0] == ' ' { _, _ = signatureSB.Write(line[1:]) continue - } else { - pgpsig = false } + pgpsig = false } if !message { diff --git a/modules/git/diff_test.go b/modules/git/diff_test.go index 0f865c52a8..8fa47a943c 100644 --- a/modules/git/diff_test.go +++ b/modules/git/diff_test.go @@ -159,22 +159,6 @@ func BenchmarkCutDiffAroundLine(b *testing.B) { } } -func ExampleCutDiffAroundLine() { - const diff = `diff --git a/README.md b/README.md ---- a/README.md -+++ b/README.md -@@ -1,3 +1,6 @@ - # gitea-github-migrator -+ -+ Build Status -- Latest Release - Docker Pulls -+ cut off -+ cut off` - result, _ := CutDiffAroundLine(strings.NewReader(diff), 4, false, 3) - println(result) -} - func TestParseDiffHunkString(t *testing.T) { leftLine, leftHunk, rightLine, rightHunk := ParseDiffHunkString("@@ -19,3 +19,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER") assert.EqualValues(t, 19, leftLine) diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go index a3ee883968..349cfbd9ce 100644 --- a/modules/git/pipeline/lfs_nogogit.go +++ b/modules/git/pipeline/lfs_nogogit.go @@ -213,7 +213,6 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err errChan <- err break } - } }() diff --git a/modules/git/repo_attribute_test.go b/modules/git/repo_attribute_test.go index 9b7b43d510..e9f7454413 100644 --- a/modules/git/repo_attribute_test.go +++ b/modules/git/repo_attribute_test.go @@ -225,13 +225,13 @@ func TestGitAttributeCheckerError(t *testing.T) { ac, err := gitRepo.GitAttributeChecker("", "linguist-language") require.NoError(t, err) - // calling CheckPath before would allow git to cache part of it and succesfully return later + // calling CheckPath before would allow git to cache part of it and successfully return later assert.NoError(t, os.RemoveAll(gitRepo.Path)) _, err = ac.CheckPath("i-am-a-python.p") if err == nil { t.Skip( - "git check-attr started too fast and CheckPath was succesful (and likely cached)", + "git check-attr started too fast and CheckPath was successful (and likely cached)", "https://codeberg.org/forgejo/forgejo/issues/2948", ) } diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 44273d2253..f9168bef7e 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -251,18 +251,18 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) return nil, err } - len := objectFormat.FullLength() + length := objectFormat.FullLength() commits := []*Commit{} - shaline := make([]byte, len+1) + shaline := make([]byte, length+1) for { n, err := io.ReadFull(stdoutReader, shaline) - if err != nil || n < len { + if err != nil || n < length { if err == io.EOF { err = nil } return commits, err } - objectID, err := NewIDFromString(string(shaline[0:len])) + objectID, err := NewIDFromString(string(shaline[0:length])) if err != nil { return nil, err } diff --git a/modules/git/submodule.go b/modules/git/submodule.go index 37813ea4c7..b99c81582b 100644 --- a/modules/git/submodule.go +++ b/modules/git/submodule.go @@ -64,7 +64,6 @@ func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string { // ex: git@try.gitea.io:go-gitea/gitea match := scpSyntax.FindAllStringSubmatch(refURI, -1) if len(match) > 0 { - m := match[0] refHostname := m[2] pth := m[3] diff --git a/modules/git/tag.go b/modules/git/tag.go index 1fe4c16b5d..04f50e8db8 100644 --- a/modules/git/tag.go +++ b/modules/git/tag.go @@ -8,6 +8,7 @@ import ( "sort" "strings" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" ) @@ -20,13 +21,14 @@ const ( // Tag represents a Git tag. type Tag struct { - Name string - ID ObjectID - Object ObjectID // The id of this commit object - Type string - Tagger *Signature - Message string - Signature *ObjectSignature + Name string + ID ObjectID + Object ObjectID // The id of this commit object + Type string + Tagger *Signature + Message string + Signature *ObjectSignature + ArchiveDownloadCount *api.TagArchiveDownloadCount } // Commit return the commit of the tag reference diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go index d7ab3f7afd..4ee47b7a13 100644 --- a/modules/highlight/highlight.go +++ b/modules/highlight/highlight.go @@ -216,8 +216,8 @@ func PlainText(code []byte) []template.HTML { } func formatLexerName(name string) string { - if name == "fallback" { - return "Plaintext" + if name == "fallback" || name == "plaintext" { + return "Text" } return util.ToTitleCaseNoLower(name) diff --git a/modules/highlight/highlight_test.go b/modules/highlight/highlight_test.go index 659688bd0f..dd15b97847 100644 --- a/modules/highlight/highlight_test.go +++ b/modules/highlight/highlight_test.go @@ -58,7 +58,7 @@ func TestFile(t *testing.T) { name: "tags.txt", code: "<>", want: lines("<>"), - lexerName: "Plaintext", + lexerName: "Text", }, { name: "tags.py", diff --git a/modules/httplib/url_test.go b/modules/httplib/url_test.go index 72033b1208..2842edd514 100644 --- a/modules/httplib/url_test.go +++ b/modules/httplib/url_test.go @@ -7,20 +7,39 @@ import ( "testing" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" ) func TestIsRiskyRedirectURL(t *testing.T) { - setting.AppURL = "http://localhost:3000/" + defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")() + defer test.MockVariableValue(&setting.AppSubURL, "/sub")() + tests := []struct { input string want bool }{ {"", false}, {"foo", false}, - {"/", false}, + {"./", false}, + {"?key=val", false}, + {"/sub/", false}, + {"http://localhost:3000/sub/", false}, + {"/sub/foo", false}, + {"http://localhost:3000/sub/foo", false}, + {"http://localhost:3000/sub/test?param=false", false}, + // FIXME: should probably be true (would requires resolving references using setting.appURL.ResolveReference(u)) + {"/sub/../", false}, + {"http://localhost:3000/sub/../", false}, + {"/sUb/", false}, + {"http://localhost:3000/sUb/foo", false}, + {"/sub", false}, {"/foo?k=%20#abc", false}, + {"/", false}, + {"a/", false}, + {"test?param=false", false}, + {"/hey/hey/hey#3244", false}, {"//", true}, {"\\\\", true}, @@ -28,7 +47,73 @@ func TestIsRiskyRedirectURL(t *testing.T) { {"\\/", true}, {"mail:a@b.com", true}, {"https://test.com", true}, - {setting.AppURL + "/foo", false}, + {"http://localhost:3000/foo", true}, + {"http://localhost:3000/sub", true}, + {"http://localhost:3000/sub?key=val", true}, + {"https://example.com/", true}, + {"//example.com", true}, + {"http://example.com", true}, + {"http://localhost:3000/test?param=false", true}, + {"//localhost:3000/test?param=false", true}, + {"://missing protocol scheme", true}, + // FIXME: should probably be false + {"//localhost:3000/sub/test?param=false", true}, + } + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + assert.Equal(t, tt.want, IsRiskyRedirectURL(tt.input)) + }) + } +} + +func TestIsRiskyRedirectURLWithoutSubURL(t *testing.T) { + defer test.MockVariableValue(&setting.AppURL, "https://next.forgejo.org/")() + defer test.MockVariableValue(&setting.AppSubURL, "")() + + tests := []struct { + input string + want bool + }{ + {"", false}, + {"foo", false}, + {"./", false}, + {"?key=val", false}, + {"/sub/", false}, + {"https://next.forgejo.org/sub/", false}, + {"/sub/foo", false}, + {"https://next.forgejo.org/sub/foo", false}, + {"https://next.forgejo.org/sub/test?param=false", false}, + {"https://next.forgejo.org/sub/../", false}, + {"/sub/../", false}, + {"/sUb/", false}, + {"https://next.forgejo.org/sUb/foo", false}, + {"/sub", false}, + {"/foo?k=%20#abc", false}, + {"/", false}, + {"a/", false}, + {"test?param=false", false}, + {"/hey/hey/hey#3244", false}, + {"https://next.forgejo.org/test?param=false", false}, + {"https://next.forgejo.org/foo", false}, + {"https://next.forgejo.org/sub", false}, + {"https://next.forgejo.org/sub?key=val", false}, + + {"//", true}, + {"\\\\", true}, + {"/\\", true}, + {"\\/", true}, + {"mail:a@b.com", true}, + {"https://test.com", true}, + {"https://example.com/", true}, + {"//example.com", true}, + {"http://example.com", true}, + {"://missing protocol scheme", true}, + {"https://forgejo.org", true}, + {"https://example.org?url=https://next.forgejo.org", true}, + // FIXME: should probably be false + {"https://next.forgejo.org", true}, + {"//next.forgejo.org/test?param=false", true}, + {"//next.forgejo.org/sub/test?param=false", true}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { diff --git a/modules/indexer/code/bleve/bleve.go b/modules/indexer/code/bleve/bleve.go index ff0e37ca29..66724a3445 100644 --- a/modules/indexer/code/bleve/bleve.go +++ b/modules/indexer/code/bleve/bleve.go @@ -193,7 +193,6 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository, batch func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error { batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize) if len(changes.Updates) > 0 { - // Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first! if err := git.EnsureValidGitRepository(ctx, repo.RepoPath()); err != nil { log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err) @@ -337,7 +336,6 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int if result, err = b.inner.Indexer.Search(facetRequest); err != nil { return 0, nil, nil, err } - } languagesFacet := result.Facets["languages"] for _, term := range languagesFacet.Terms.Terms() { diff --git a/modules/indexer/issues/dboptions.go b/modules/indexer/issues/dboptions.go index 4a98b4588a..8f94088742 100644 --- a/modules/indexer/issues/dboptions.go +++ b/modules/indexer/issues/dboptions.go @@ -68,7 +68,7 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp searchOpt.Paginator = opts.Paginator switch opts.SortType { - case "": + case "", "latest": searchOpt.SortBy = SortByCreatedDesc case "oldest": searchOpt.SortBy = SortByCreatedAsc @@ -86,7 +86,7 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp searchOpt.SortBy = SortByDeadlineDesc case "priority", "priorityrepo", "project-column-sorting": // Unsupported sort type for search - searchOpt.SortBy = SortByUpdatedDesc + fallthrough default: searchOpt.SortBy = SortByUpdatedDesc } diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go index 53b383c8d5..c7cb59f2cf 100644 --- a/modules/indexer/issues/elasticsearch/elasticsearch.go +++ b/modules/indexer/issues/elasticsearch/elasticsearch.go @@ -145,7 +145,6 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( query := elastic.NewBoolQuery() if options.Keyword != "" { - searchType := esMultiMatchTypePhrasePrefix if options.IsFuzzyKeyword { searchType = esMultiMatchTypeBestFields diff --git a/modules/log/event_format.go b/modules/log/event_format.go index 524ca3dd87..d9dbebf831 100644 --- a/modules/log/event_format.go +++ b/modules/log/event_format.go @@ -125,7 +125,6 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms if mode.Colorize { buf = append(buf, resetBytes...) } - } if flags&(Lshortfile|Llongfile) != 0 { if mode.Colorize { diff --git a/modules/markup/html.go b/modules/markup/html.go index 56f63b8a76..f73221a37f 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -54,7 +54,7 @@ var ( shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`) // anySHA1Pattern splits url containing SHA into parts - anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~_%.a-zA-Z0-9/]+)?(#[-+~_%.a-zA-Z0-9]+)?`) + anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~_%.a-zA-Z0-9/]+)?(\?[-+~_%\.a-zA-Z0-9=&]+)?(#[-+~_%.a-zA-Z0-9]+)?`) // comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash" comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?`) @@ -969,10 +969,10 @@ func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) { subpath = node.Data[m[4]:m[5]] } - // 4th capture group matches a optional url hash + // 5th capture group matches a optional url hash hash := "" - if m[7] > 0 { - hash = node.Data[m[6]:m[7]][1:] + if m[9] > 0 { + hash = node.Data[m[8]:m[9]][1:] } start := m[0] diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index e313be7040..917f280c73 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -403,28 +403,39 @@ func TestRegExp_anySHA1Pattern(t *testing.T) { "https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js#L2703": { "a644101ed04d0beacea864ce805e0c4f86ba1cd1", "/test/unit/event.js", + "", "#L2703", }, "https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js": { "a644101ed04d0beacea864ce805e0c4f86ba1cd1", "/test/unit/event.js", "", + "", }, "https://github.com/jquery/jquery/commit/0705be475092aede1eddae01319ec931fb9c65fc": { "0705be475092aede1eddae01319ec931fb9c65fc", "", "", + "", }, "https://github.com/jquery/jquery/tree/0705be475092aede1eddae01319ec931fb9c65fc/src": { "0705be475092aede1eddae01319ec931fb9c65fc", "/src", "", + "", }, "https://try.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2": { "d8a994ef243349f321568f9e36d5c3f444b99cae", "", + "", "#diff-2", }, + "https://codeberg.org/forgejo/forgejo/src/commit/949ab9a5c4cac742f84ae5a9fa186f8d6eb2cdc0/RELEASE-NOTES.md?display=source&w=1#L7-L9": { + "949ab9a5c4cac742f84ae5a9fa186f8d6eb2cdc0", + "/RELEASE-NOTES.md", + "?display=source&w=1", + "#L7-L9", + }, } for k, v := range testCases { diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index a1a99c1a7f..cfd1a66a18 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -127,6 +127,12 @@ func TestRender_CrossReferences(t *testing.T) { test( util.URLJoin(markup.TestAppURL, "gogitea", "some-repo-name", "issues", "12345"), `

gogitea/some-repo-name#12345

`) + + sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d" + urlWithQuery := util.URLJoin(markup.TestAppURL, "forgejo", "some-repo-name", "commit", sha, "README.md") + "?display=source#L1-L5" + test( + urlWithQuery, + `

`+sha[:10]+`/README.md (L1-L5)

`) } func TestMisc_IsSameDomain(t *testing.T) { diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index 5a481a31fd..8289e28677 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -4,17 +4,12 @@ package markdown import ( - "bytes" "fmt" "regexp" - "slices" "strings" - "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/markup/common" "code.gitea.io/gitea/modules/setting" - giteautil "code.gitea.io/gitea/modules/util" "github.com/yuin/goldmark/ast" east "github.com/yuin/goldmark/extension/ast" @@ -30,6 +25,12 @@ var byteMailto = []byte("mailto:") // ASTTransformer is a default transformer of the goldmark tree. type ASTTransformer struct{} +func (g *ASTTransformer) applyElementDir(n ast.Node) { + if markup.DefaultProcessorHelper.ElementDir != "" { + n.SetAttributeString("dir", []byte(markup.DefaultProcessorHelper.ElementDir)) + } +} + // Transform transforms the given AST tree. func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { firstChild := node.FirstChild() @@ -46,12 +47,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa tocMode = rc.TOC } - applyElementDir := func(n ast.Node) { - if markup.DefaultProcessorHelper.ElementDir != "" { - n.SetAttributeString("dir", []byte(markup.DefaultProcessorHelper.ElementDir)) - } - } - _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { return ast.WalkContinue, nil @@ -59,135 +54,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa switch v := n.(type) { case *ast.Heading: - for _, attr := range v.Attributes() { - if _, ok := attr.Value.([]byte); !ok { - v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value))) - } - } - txt := n.Text(reader.Source()) - header := markup.Header{ - Text: util.BytesToReadOnlyString(txt), - Level: v.Level, - } - if id, found := v.AttributeString("id"); found { - header.ID = util.BytesToReadOnlyString(id.([]byte)) - } - tocList = append(tocList, header) - applyElementDir(v) + g.transformHeading(ctx, v, reader, &tocList) case *ast.Paragraph: - applyElementDir(v) + g.applyElementDir(v) case *ast.Image: - // Images need two things: - // - // 1. Their src needs to munged to be a real value - // 2. If they're not wrapped with a link they need a link wrapper - - // Check if the destination is a real link - if len(v.Destination) > 0 && !markup.IsLink(v.Destination) { - v.Destination = []byte(giteautil.URLJoin( - ctx.Links.ResolveMediaLink(ctx.IsWiki), - strings.TrimLeft(string(v.Destination), "/"), - )) - } - - parent := n.Parent() - // Create a link around image only if parent is not already a link - if _, ok := parent.(*ast.Link); !ok && parent != nil { - next := n.NextSibling() - - // Create a link wrapper - wrap := ast.NewLink() - wrap.Destination = v.Destination - wrap.Title = v.Title - wrap.SetAttributeString("target", []byte("_blank")) - - // Duplicate the current image node - image := ast.NewImage(ast.NewLink()) - image.Destination = v.Destination - image.Title = v.Title - for _, attr := range v.Attributes() { - image.SetAttribute(attr.Name, attr.Value) - } - for child := v.FirstChild(); child != nil; { - next := child.NextSibling() - image.AppendChild(image, child) - child = next - } - - // Append our duplicate image to the wrapper link - wrap.AppendChild(wrap, image) - - // Wire in the next sibling - wrap.SetNextSibling(next) - - // Replace the current node with the wrapper link - parent.ReplaceChild(parent, n, wrap) - - // But most importantly ensure the next sibling is still on the old image too - v.SetNextSibling(next) - } + g.transformImage(ctx, v, reader) case *ast.Link: - // Links need their href to munged to be a real value - link := v.Destination - - // Do not process the link if it's not a link, starts with an hashtag - // (indicating it's an anchor link), starts with `mailto:` or any of the - // custom markdown URLs. - processLink := len(link) > 0 && !markup.IsLink(link) && - link[0] != '#' && !bytes.HasPrefix(link, byteMailto) && - !slices.ContainsFunc(setting.Markdown.CustomURLSchemes, func(s string) bool { - return bytes.HasPrefix(link, []byte(s+":")) - }) - - if processLink { - var base string - if ctx.IsWiki { - base = ctx.Links.WikiLink() - } else if ctx.Links.HasBranchInfo() { - base = ctx.Links.SrcLink() - } else { - base = ctx.Links.Base - } - - link = []byte(giteautil.URLJoin(base, string(link))) - } - if len(link) > 0 && link[0] == '#' { - link = []byte("#user-content-" + string(link)[1:]) - } - v.Destination = link + g.transformLink(ctx, v, reader) case *ast.List: - if v.HasChildren() { - children := make([]ast.Node, 0, v.ChildCount()) - child := v.FirstChild() - for child != nil { - children = append(children, child) - child = child.NextSibling() - } - v.RemoveChildren(v) - - for _, child := range children { - listItem := child.(*ast.ListItem) - if !child.HasChildren() || !child.FirstChild().HasChildren() { - v.AppendChild(v, child) - continue - } - taskCheckBox, ok := child.FirstChild().FirstChild().(*east.TaskCheckBox) - if !ok { - v.AppendChild(v, child) - continue - } - newChild := NewTaskCheckBoxListItem(listItem) - newChild.IsChecked = taskCheckBox.IsChecked - newChild.SetAttributeString("class", []byte("task-list-item")) - segments := newChild.FirstChild().Lines() - if segments.Len() > 0 { - segment := segments.At(0) - newChild.SourcePosition = rc.metaLength + segment.Start - } - v.AppendChild(v, newChild) - } - } - applyElementDir(v) + g.transformList(ctx, v, reader, rc) case *ast.Text: if v.SoftLineBreak() && !v.HardLineBreak() { if ctx.Metas["mode"] != "document" { @@ -197,10 +72,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa } } case *ast.CodeSpan: - colorContent := n.Text(reader.Source()) - if matchColor(strings.ToLower(string(colorContent))) { - v.AppendChild(v, NewColorPreview(colorContent)) - } + g.transformCodeSpan(ctx, v, reader) } return ast.WalkContinue, nil }) @@ -222,50 +94,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa } } -type prefixedIDs struct { - values container.Set[string] -} - -// Generate generates a new element id. -func (p *prefixedIDs) Generate(value []byte, kind ast.NodeKind) []byte { - dft := []byte("id") - if kind == ast.KindHeading { - dft = []byte("heading") - } - return p.GenerateWithDefault(value, dft) -} - -// Generate generates a new element id. -func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte { - result := common.CleanValue(value) - if len(result) == 0 { - result = dft - } - if !bytes.HasPrefix(result, []byte("user-content-")) { - result = append([]byte("user-content-"), result...) - } - if p.values.Add(util.BytesToReadOnlyString(result)) { - return result - } - for i := 1; ; i++ { - newResult := fmt.Sprintf("%s-%d", result, i) - if p.values.Add(newResult) { - return []byte(newResult) - } - } -} - -// Put puts a given element id to the used ids table. -func (p *prefixedIDs) Put(value []byte) { - p.values.Add(util.BytesToReadOnlyString(value)) -} - -func newPrefixedIDs() *prefixedIDs { - return &prefixedIDs{ - values: make(container.Set[string]), - } -} - // NewHTMLRenderer creates a HTMLRenderer to render // in the gitea form. func NewHTMLRenderer(opts ...html.Option) renderer.NodeRenderer { @@ -295,38 +123,6 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox) } -// renderCodeSpan renders CodeSpan elements (like goldmark upstream does) but also renders ColorPreview elements. -// See #21474 for reference -func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { - if entering { - if n.Attributes() != nil { - _, _ = w.WriteString("') - } else { - _, _ = w.WriteString("") - } - for c := n.FirstChild(); c != nil; c = c.NextSibling() { - switch v := c.(type) { - case *ast.Text: - segment := v.Segment - value := segment.Value(source) - if bytes.HasSuffix(value, []byte("\n")) { - r.Writer.RawWrite(w, value[:len(value)-1]) - r.Writer.RawWrite(w, []byte(" ")) - } else { - r.Writer.RawWrite(w, value) - } - case *ColorPreview: - _, _ = w.WriteString(fmt.Sprintf(``, string(v.Color))) - } - } - return ast.WalkSkipChildren, nil - } - _, _ = w.WriteString("") - return ast.WalkContinue, nil -} - func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { n := node.(*ast.Document) @@ -415,38 +211,3 @@ func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node return ast.WalkContinue, nil } - -func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { - n := node.(*TaskCheckBoxListItem) - if entering { - if n.Attributes() != nil { - _, _ = w.WriteString("') - } else { - _, _ = w.WriteString("
  • ") - } - fmt.Fprintf(w, ``) - } else { - _ = w.WriteByte('>') - } - fc := n.FirstChild() - if fc != nil { - if _, ok := fc.(*ast.TextBlock); !ok { - _ = w.WriteByte('\n') - } - } - } else { - _, _ = w.WriteString("
  • \n") - } - return ast.WalkContinue, nil -} - -func (r *HTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { - return ast.WalkContinue, nil -} diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 3e1d75b291..278494d95c 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -460,7 +460,6 @@ func TestColorPreview(t *testing.T) { res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase) assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase) assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase) - } negativeTests := []string{ @@ -555,7 +554,6 @@ func TestMathBlock(t *testing.T) { res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase) assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase) assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase) - } } diff --git a/modules/markup/markdown/prefixed_id.go b/modules/markup/markdown/prefixed_id.go new file mode 100644 index 0000000000..9c60949202 --- /dev/null +++ b/modules/markup/markdown/prefixed_id.go @@ -0,0 +1,59 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markdown + +import ( + "bytes" + "fmt" + + "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/markup/common" + + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/util" +) + +type prefixedIDs struct { + values container.Set[string] +} + +// Generate generates a new element id. +func (p *prefixedIDs) Generate(value []byte, kind ast.NodeKind) []byte { + dft := []byte("id") + if kind == ast.KindHeading { + dft = []byte("heading") + } + return p.GenerateWithDefault(value, dft) +} + +// GenerateWithDefault generates a new element id. +func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte { + result := common.CleanValue(value) + if len(result) == 0 { + result = dft + } + if !bytes.HasPrefix(result, []byte("user-content-")) { + result = append([]byte("user-content-"), result...) + } + if p.values.Add(util.BytesToReadOnlyString(result)) { + return result + } + for i := 1; ; i++ { + newResult := fmt.Sprintf("%s-%d", result, i) + if p.values.Add(newResult) { + return []byte(newResult) + } + } +} + +// Put puts a given element id to the used ids table. +func (p *prefixedIDs) Put(value []byte) { + p.values.Add(util.BytesToReadOnlyString(value)) +} + +func newPrefixedIDs() *prefixedIDs { + return &prefixedIDs{ + values: make(container.Set[string]), + } +} diff --git a/modules/markup/markdown/transform_codespan.go b/modules/markup/markdown/transform_codespan.go new file mode 100644 index 0000000000..0cf1169dee --- /dev/null +++ b/modules/markup/markdown/transform_codespan.go @@ -0,0 +1,56 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markdown + +import ( + "bytes" + "fmt" + "strings" + + "code.gitea.io/gitea/modules/markup" + + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/renderer/html" + "github.com/yuin/goldmark/text" + "github.com/yuin/goldmark/util" +) + +// renderCodeSpan renders CodeSpan elements (like goldmark upstream does) but also renders ColorPreview elements. +// See #21474 for reference +func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { + if entering { + if n.Attributes() != nil { + _, _ = w.WriteString("') + } else { + _, _ = w.WriteString("") + } + for c := n.FirstChild(); c != nil; c = c.NextSibling() { + switch v := c.(type) { + case *ast.Text: + segment := v.Segment + value := segment.Value(source) + if bytes.HasSuffix(value, []byte("\n")) { + r.Writer.RawWrite(w, value[:len(value)-1]) + r.Writer.RawWrite(w, []byte(" ")) + } else { + r.Writer.RawWrite(w, value) + } + case *ColorPreview: + _, _ = w.WriteString(fmt.Sprintf(``, string(v.Color))) + } + } + return ast.WalkSkipChildren, nil + } + _, _ = w.WriteString("") + return ast.WalkContinue, nil +} + +func (g *ASTTransformer) transformCodeSpan(ctx *markup.RenderContext, v *ast.CodeSpan, reader text.Reader) { + colorContent := v.Text(reader.Source()) + if matchColor(strings.ToLower(string(colorContent))) { + v.AppendChild(v, NewColorPreview(colorContent)) + } +} diff --git a/modules/markup/markdown/transform_heading.go b/modules/markup/markdown/transform_heading.go new file mode 100644 index 0000000000..ce585a37de --- /dev/null +++ b/modules/markup/markdown/transform_heading.go @@ -0,0 +1,32 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markdown + +import ( + "fmt" + + "code.gitea.io/gitea/modules/markup" + + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/text" + "github.com/yuin/goldmark/util" +) + +func (g *ASTTransformer) transformHeading(ctx *markup.RenderContext, v *ast.Heading, reader text.Reader, tocList *[]markup.Header) { + for _, attr := range v.Attributes() { + if _, ok := attr.Value.([]byte); !ok { + v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value))) + } + } + txt := v.Text(reader.Source()) + header := markup.Header{ + Text: util.BytesToReadOnlyString(txt), + Level: v.Level, + } + if id, found := v.AttributeString("id"); found { + header.ID = util.BytesToReadOnlyString(id.([]byte)) + } + *tocList = append(*tocList, header) + g.applyElementDir(v) +} diff --git a/modules/markup/markdown/transform_image.go b/modules/markup/markdown/transform_image.go new file mode 100644 index 0000000000..db449e9dd9 --- /dev/null +++ b/modules/markup/markdown/transform_image.go @@ -0,0 +1,66 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markdown + +import ( + "strings" + + "code.gitea.io/gitea/modules/markup" + giteautil "code.gitea.io/gitea/modules/util" + + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/text" +) + +func (g *ASTTransformer) transformImage(ctx *markup.RenderContext, v *ast.Image, reader text.Reader) { + // Images need two things: + // + // 1. Their src needs to munged to be a real value + // 2. If they're not wrapped with a link they need a link wrapper + + // Check if the destination is a real link + if len(v.Destination) > 0 && !markup.IsLink(v.Destination) { + v.Destination = []byte(giteautil.URLJoin( + ctx.Links.ResolveMediaLink(ctx.IsWiki), + strings.TrimLeft(string(v.Destination), "/"), + )) + } + + parent := v.Parent() + // Create a link around image only if parent is not already a link + if _, ok := parent.(*ast.Link); !ok && parent != nil { + next := v.NextSibling() + + // Create a link wrapper + wrap := ast.NewLink() + wrap.Destination = v.Destination + wrap.Title = v.Title + wrap.SetAttributeString("target", []byte("_blank")) + + // Duplicate the current image node + image := ast.NewImage(ast.NewLink()) + image.Destination = v.Destination + image.Title = v.Title + for _, attr := range v.Attributes() { + image.SetAttribute(attr.Name, attr.Value) + } + for child := v.FirstChild(); child != nil; { + next := child.NextSibling() + image.AppendChild(image, child) + child = next + } + + // Append our duplicate image to the wrapper link + wrap.AppendChild(wrap, image) + + // Wire in the next sibling + wrap.SetNextSibling(next) + + // Replace the current node with the wrapper link + parent.ReplaceChild(parent, v, wrap) + + // But most importantly ensure the next sibling is still on the old image too + v.SetNextSibling(next) + } +} diff --git a/modules/markup/markdown/transform_link.go b/modules/markup/markdown/transform_link.go new file mode 100644 index 0000000000..aceae5b74f --- /dev/null +++ b/modules/markup/markdown/transform_link.go @@ -0,0 +1,47 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markdown + +import ( + "bytes" + "slices" + + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/setting" + giteautil "code.gitea.io/gitea/modules/util" + + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/text" +) + +func (g *ASTTransformer) transformLink(ctx *markup.RenderContext, v *ast.Link, reader text.Reader) { + // Links need their href to munged to be a real value + link := v.Destination + + // Do not process the link if it's not a link, starts with an hashtag + // (indicating it's an anchor link), starts with `mailto:` or any of the + // custom markdown URLs. + processLink := len(link) > 0 && !markup.IsLink(link) && + link[0] != '#' && !bytes.HasPrefix(link, byteMailto) && + !slices.ContainsFunc(setting.Markdown.CustomURLSchemes, func(s string) bool { + return bytes.HasPrefix(link, []byte(s+":")) + }) + + if processLink { + var base string + if ctx.IsWiki { + base = ctx.Links.WikiLink() + } else if ctx.Links.HasBranchInfo() { + base = ctx.Links.SrcLink() + } else { + base = ctx.Links.Base + } + + link = []byte(giteautil.URLJoin(base, string(link))) + } + if len(link) > 0 && link[0] == '#' { + link = []byte("#user-content-" + string(link)[1:]) + } + v.Destination = link +} diff --git a/modules/markup/markdown/transform_list.go b/modules/markup/markdown/transform_list.go new file mode 100644 index 0000000000..6563e2dd64 --- /dev/null +++ b/modules/markup/markdown/transform_list.go @@ -0,0 +1,86 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markdown + +import ( + "fmt" + + "code.gitea.io/gitea/modules/markup" + + "github.com/yuin/goldmark/ast" + east "github.com/yuin/goldmark/extension/ast" + "github.com/yuin/goldmark/renderer/html" + "github.com/yuin/goldmark/text" + "github.com/yuin/goldmark/util" +) + +func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + n := node.(*TaskCheckBoxListItem) + if entering { + if n.Attributes() != nil { + _, _ = w.WriteString("') + } else { + _, _ = w.WriteString("
  • ") + } + fmt.Fprintf(w, ``) + } else { + _ = w.WriteByte('>') + } + fc := n.FirstChild() + if fc != nil { + if _, ok := fc.(*ast.TextBlock); !ok { + _ = w.WriteByte('\n') + } + } + } else { + _, _ = w.WriteString("
  • \n") + } + return ast.WalkContinue, nil +} + +func (r *HTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + return ast.WalkContinue, nil +} + +func (g *ASTTransformer) transformList(ctx *markup.RenderContext, v *ast.List, reader text.Reader, rc *RenderConfig) { + if v.HasChildren() { + children := make([]ast.Node, 0, v.ChildCount()) + child := v.FirstChild() + for child != nil { + children = append(children, child) + child = child.NextSibling() + } + v.RemoveChildren(v) + + for _, child := range children { + listItem := child.(*ast.ListItem) + if !child.HasChildren() || !child.FirstChild().HasChildren() { + v.AppendChild(v, child) + continue + } + taskCheckBox, ok := child.FirstChild().FirstChild().(*east.TaskCheckBox) + if !ok { + v.AppendChild(v, child) + continue + } + newChild := NewTaskCheckBoxListItem(listItem) + newChild.IsChecked = taskCheckBox.IsChecked + newChild.SetAttributeString("class", []byte("task-list-item")) + segments := newChild.FirstChild().Lines() + if segments.Len() > 0 { + segment := segments.At(0) + newChild.SourcePosition = rc.metaLength + segment.Start + } + v.AppendChild(v, newChild) + } + } + g.applyElementDir(v) +} diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index 8efe4e395d..391ee6c12b 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -147,7 +147,6 @@ func (r *Writer) resolveLink(node org.Node) string { } if len(link) > 0 && !markup.IsLinkStr(link) && link[0] != '#' && !strings.HasPrefix(link, mailto) { - var base string if r.Ctx.IsWiki { base = r.Ctx.Links.WikiLink() diff --git a/modules/optional/serialization.go b/modules/optional/serialization.go index 6688e78cd1..b120a0edf6 100644 --- a/modules/optional/serialization.go +++ b/modules/optional/serialization.go @@ -35,7 +35,7 @@ func (o *Option[T]) UnmarshalYAML(value *yaml.Node) error { return nil } -func (o Option[T]) MarshalYAML() (interface{}, error) { +func (o Option[T]) MarshalYAML() (any, error) { if !o.Has() { return nil, nil } diff --git a/modules/packages/rubygems/marshal.go b/modules/packages/rubygems/marshal.go index 8878dcf973..4e6a5fc5f8 100644 --- a/modules/packages/rubygems/marshal.go +++ b/modules/packages/rubygems/marshal.go @@ -147,35 +147,35 @@ func (e *MarshalEncoder) marshalIntInternal(i int64) error { return e.w.WriteByte(byte(i - 5)) } - var len int + var length int if 122 < i && i <= 0xff { - len = 1 + length = 1 } else if 0xff < i && i <= 0xffff { - len = 2 + length = 2 } else if 0xffff < i && i <= 0xffffff { - len = 3 + length = 3 } else if 0xffffff < i && i <= 0x3fffffff { - len = 4 + length = 4 } else if -0x100 <= i && i < -123 { - len = -1 + length = -1 } else if -0x10000 <= i && i < -0x100 { - len = -2 + length = -2 } else if -0x1000000 <= i && i < -0x100000 { - len = -3 + length = -3 } else if -0x40000000 <= i && i < -0x1000000 { - len = -4 + length = -4 } else { return ErrInvalidIntRange } - if err := e.w.WriteByte(byte(len)); err != nil { + if err := e.w.WriteByte(byte(length)); err != nil { return err } - if len < 0 { - len = -len + if length < 0 { + length = -length } - for c := 0; c < len; c++ { + for c := 0; c < length; c++ { if err := e.w.WriteByte(byte(i >> uint(8*c) & 0xff)); err != nil { return err } @@ -244,13 +244,13 @@ func (e *MarshalEncoder) marshalArray(arr reflect.Value) error { return err } - len := arr.Len() + length := arr.Len() - if err := e.marshalIntInternal(int64(len)); err != nil { + if err := e.marshalIntInternal(int64(length)); err != nil { return err } - for i := 0; i < len; i++ { + for i := 0; i < length; i++ { if err := e.marshal(arr.Index(i).Interface()); err != nil { return err } diff --git a/modules/process/manager_stacktraces.go b/modules/process/manager_stacktraces.go index 49bd5071f6..e260893113 100644 --- a/modules/process/manager_stacktraces.go +++ b/modules/process/manager_stacktraces.go @@ -339,7 +339,6 @@ func (pm *Manager) ProcessStacktraces(flat, noSystem bool) ([]*Process, int, int } sort.Slice(processes, after(processes)) if !flat { - var sortChildren func(process *Process) sortChildren = func(process *Process) { diff --git a/modules/repository/temp.go b/modules/repository/temp.go index 53646718e0..04faa9db3d 100644 --- a/modules/repository/temp.go +++ b/modules/repository/temp.go @@ -32,7 +32,6 @@ func CreateTemporaryPath(prefix string) (string, error) { if err != nil { log.Error("Unable to create temporary directory: %s-*.git (%v)", prefix, err) return "", fmt.Errorf("Failed to create dir %s-*.git: %w", prefix, err) - } return basePath, nil } diff --git a/modules/setting/admin.go b/modules/setting/admin.go index 35ffa9efbf..07b8ef0899 100644 --- a/modules/setting/admin.go +++ b/modules/setting/admin.go @@ -3,7 +3,9 @@ package setting -import "code.gitea.io/gitea/modules/container" +import ( + "code.gitea.io/gitea/modules/container" +) // Admin settings var Admin struct { @@ -11,6 +13,7 @@ var Admin struct { DefaultEmailNotification string SendNotificationEmailOnNewUser bool UserDisabledFeatures container.Set[string] + ExternalUserDisableFeatures container.Set[string] } func loadAdminFrom(rootCfg ConfigProvider) { @@ -18,6 +21,7 @@ func loadAdminFrom(rootCfg ConfigProvider) { Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false) Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled") Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...) + Admin.ExternalUserDisableFeatures = container.SetOf(sec.Key("EXTERNAL_USER_DISABLE_FEATURES").Strings(",")...) } const ( diff --git a/modules/setting/database.go b/modules/setting/database.go index 7433896455..76fae27164 100644 --- a/modules/setting/database.go +++ b/modules/setting/database.go @@ -12,15 +12,13 @@ import ( "path/filepath" "strings" "time" - - "code.gitea.io/gitea/modules/log" ) var ( // SupportedDatabaseTypes includes all XORM supported databases type, sqlite3 maybe added by `database_sqlite3.go` - SupportedDatabaseTypes = []string{"mysql", "postgres", "mssql"} + SupportedDatabaseTypes = []string{"mysql", "postgres"} // DatabaseTypeNames contains the friendly names for all database types - DatabaseTypeNames = map[string]string{"mysql": "MySQL", "postgres": "PostgreSQL", "mssql": "MSSQL", "sqlite3": "SQLite3"} + DatabaseTypeNames = map[string]string{"mysql": "MySQL", "postgres": "PostgreSQL", "sqlite3": "SQLite3"} // EnableSQLite3 use SQLite3, set by build flag EnableSQLite3 bool @@ -64,11 +62,6 @@ func loadDBSetting(rootCfg ConfigProvider) { sec := rootCfg.Section("database") Database.Type = DatabaseType(sec.Key("DB_TYPE").String()) - if Database.Type.IsMSSQL() { - log.Error("Your Forgejo instance uses Microsoft SQL Server as its database which is scheduled for removal in v8.0. Please file an issue https://codeberg.org/forgejo/forgejo/issues/new to get help migrating to another database. Waiting 60 seconds before starting Forgejo") - time.Sleep(time.Second * 60) - } - Database.Host = sec.Key("HOST").String() Database.Name = sec.Key("NAME").String() Database.User = sec.Key("USER").String() @@ -90,7 +83,7 @@ func loadDBSetting(rootCfg ConfigProvider) { Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFETIME").MustDuration(0) } Database.ConnMaxIdleTime = sec.Key("CONN_MAX_IDLETIME").MustDuration(0) - Database.MaxOpenConns = sec.Key("MAX_OPEN_CONNS").MustInt(0) + Database.MaxOpenConns = sec.Key("MAX_OPEN_CONNS").MustInt(100) Database.IterateBufferSize = sec.Key("ITERATE_BUFFER_SIZE").MustInt(50) Database.LogSQL = sec.Key("LOG_SQL").MustBool(false) @@ -127,9 +120,6 @@ func DBConnStr() (string, error) { Database.User, Database.Passwd, connType, Database.Host, Database.Name, paramSep, tls) case "postgres": connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, Database.SSLMode) - case "mssql": - host, port := ParseMSSQLHostPort(Database.Host) - connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, Database.Name, Database.User, Database.Passwd) case "sqlite3": if !EnableSQLite3 { return "", errors.New("this Gitea binary was not built with SQLite3 support") @@ -195,28 +185,6 @@ func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbsslMode s return connURL.String() } -// ParseMSSQLHostPort splits the host into host and port -func ParseMSSQLHostPort(info string) (string, string) { - // the default port "0" might be related to MSSQL's dynamic port, maybe it should be double-confirmed in the future - host, port := "127.0.0.1", "0" - if strings.Contains(info, ":") { - host = strings.Split(info, ":")[0] - port = strings.Split(info, ":")[1] - } else if strings.Contains(info, ",") { - host = strings.Split(info, ",")[0] - port = strings.TrimSpace(strings.Split(info, ",")[1]) - } else if len(info) > 0 { - host = info - } - if host == "" { - host = "127.0.0.1" - } - if port == "" { - port = "0" - } - return host, port -} - type DatabaseType string func (t DatabaseType) String() string { @@ -231,10 +199,6 @@ func (t DatabaseType) IsMySQL() bool { return t == "mysql" } -func (t DatabaseType) IsMSSQL() bool { - return t == "mssql" -} - func (t DatabaseType) IsPostgreSQL() bool { return t == "postgres" } diff --git a/modules/setting/incoming_email.go b/modules/setting/incoming_email.go index 75337a312f..314fdea137 100644 --- a/modules/setting/incoming_email.go +++ b/modules/setting/incoming_email.go @@ -38,6 +38,24 @@ func loadIncomingEmailFrom(rootCfg ConfigProvider) { return } + // Handle aliases + sec := rootCfg.Section("email.incoming") + if sec.HasKey("USER") && !sec.HasKey("USERNAME") { + IncomingEmail.Username = sec.Key("USER").String() + } + if sec.HasKey("PASSWD") && !sec.HasKey("PASSWORD") { + IncomingEmail.Password = sec.Key("PASSWD").String() + } + + // Infer Port if not set + if IncomingEmail.Port == 0 { + if IncomingEmail.UseTLS { + IncomingEmail.Port = 993 + } else { + IncomingEmail.Port = 143 + } + } + if err := checkReplyToAddress(IncomingEmail.ReplyToAddress); err != nil { log.Fatal("Invalid incoming_mail.REPLY_TO_ADDRESS (%s): %v", IncomingEmail.ReplyToAddress, err) } diff --git a/modules/setting/incoming_email_test.go b/modules/setting/incoming_email_test.go new file mode 100644 index 0000000000..0fdd44d333 --- /dev/null +++ b/modules/setting/incoming_email_test.go @@ -0,0 +1,74 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_loadIncomingEmailFrom(t *testing.T) { + makeBaseConfig := func() (ConfigProvider, ConfigSection) { + cfg, _ := NewConfigProviderFromData("") + sec := cfg.Section("email.incoming") + sec.NewKey("ENABLED", "true") + sec.NewKey("REPLY_TO_ADDRESS", "forge+%{token}@example.com") + + return cfg, sec + } + resetIncomingEmailPort := func() func() { + return func() { + IncomingEmail.Port = 0 + } + } + + t.Run("aliases", func(t *testing.T) { + cfg, sec := makeBaseConfig() + sec.NewKey("USER", "jane.doe@example.com") + sec.NewKey("PASSWD", "y0u'll n3v3r gUess th1S!!1") + + loadIncomingEmailFrom(cfg) + + assert.EqualValues(t, "jane.doe@example.com", IncomingEmail.Username) + assert.EqualValues(t, "y0u'll n3v3r gUess th1S!!1", IncomingEmail.Password) + }) + + t.Run("Port settings", func(t *testing.T) { + t.Run("no port, no tls", func(t *testing.T) { + defer resetIncomingEmailPort()() + cfg, sec := makeBaseConfig() + + // False is the default, but we test it explicitly. + sec.NewKey("USE_TLS", "false") + + loadIncomingEmailFrom(cfg) + + assert.EqualValues(t, 143, IncomingEmail.Port) + }) + + t.Run("no port, with tls", func(t *testing.T) { + defer resetIncomingEmailPort()() + cfg, sec := makeBaseConfig() + + sec.NewKey("USE_TLS", "true") + + loadIncomingEmailFrom(cfg) + + assert.EqualValues(t, 993, IncomingEmail.Port) + }) + + t.Run("port overrides tls", func(t *testing.T) { + defer resetIncomingEmailPort()() + cfg, sec := makeBaseConfig() + + sec.NewKey("PORT", "1993") + sec.NewKey("USE_TLS", "true") + + loadIncomingEmailFrom(cfg) + + assert.EqualValues(t, 1993, IncomingEmail.Port) + }) + }) +} diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go index a2bc2df444..e9ce640c7f 100644 --- a/modules/setting/mailer.go +++ b/modules/setting/mailer.go @@ -134,6 +134,14 @@ func loadMailerFrom(rootCfg ConfigProvider) { sec.Key("PROTOCOL").SetValue("smtp+starttls") } + // Handle aliases + if sec.HasKey("USERNAME") && !sec.HasKey("USER") { + sec.Key("USER").SetValue(sec.Key("USERNAME").String()) + } + if sec.HasKey("PASSWORD") && !sec.HasKey("PASSWD") { + sec.Key("PASSWD").SetValue(sec.Key("PASSWORD").String()) + } + // Set default values & validate sec.Key("NAME").MustString(AppName) sec.Key("PROTOCOL").In("", []string{"smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy"}) diff --git a/modules/setting/mailer_test.go b/modules/setting/mailer_test.go index fbabf11378..f8af4a78c1 100644 --- a/modules/setting/mailer_test.go +++ b/modules/setting/mailer_test.go @@ -38,4 +38,17 @@ func Test_loadMailerFrom(t *testing.T) { assert.EqualValues(t, kase.SMTPPort, MailService.SMTPPort) }) } + + t.Run("property aliases", func(t *testing.T) { + cfg, _ := NewConfigProviderFromData("") + sec := cfg.Section("mailer") + sec.NewKey("ENABLED", "true") + sec.NewKey("USERNAME", "jane.doe@example.com") + sec.NewKey("PASSWORD", "y0u'll n3v3r gUess th1S!!1") + + loadMailerFrom(cfg) + + assert.EqualValues(t, "jane.doe@example.com", MailService.User) + assert.EqualValues(t, "y0u'll n3v3r gUess th1S!!1", MailService.Passwd) + }) } diff --git a/modules/setting/other.go b/modules/setting/other.go index 706cb1e3d9..4ba494765b 100644 --- a/modules/setting/other.go +++ b/modules/setting/other.go @@ -8,6 +8,7 @@ import "code.gitea.io/gitea/modules/log" type OtherConfig struct { ShowFooterVersion bool ShowFooterTemplateLoadTime bool + ShowFooterPoweredBy bool EnableFeed bool EnableSitemap bool } @@ -15,6 +16,7 @@ type OtherConfig struct { var Other = OtherConfig{ ShowFooterVersion: true, ShowFooterTemplateLoadTime: true, + ShowFooterPoweredBy: true, EnableSitemap: true, EnableFeed: true, } diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 50f0fd704c..6086dd1d57 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -162,7 +162,7 @@ var ( PreferredLicenses: []string{"Apache-2.0", "MIT"}, DisableHTTPGit: false, AccessControlAllowOrigin: "", - UseCompatSSHURI: false, + UseCompatSSHURI: true, DefaultCloseIssuesViaCommitsInAnyBranch: false, EnablePushCreateUser: false, EnablePushCreateOrg: false, diff --git a/modules/setting/time.go b/modules/setting/time.go index 6d2aa80f5b..39acba12ef 100644 --- a/modules/setting/time.go +++ b/modules/setting/time.go @@ -19,9 +19,8 @@ func loadTimeFrom(rootCfg ConfigProvider) { DefaultUILocation, err = time.LoadLocation(zone) if err != nil { log.Fatal("Load time zone failed: %v", err) - } else { - log.Info("Default UI Location is %v", zone) } + log.Info("Default UI Location is %v", zone) } if DefaultUILocation == nil { DefaultUILocation = time.Local diff --git a/modules/structs/admin_user.go b/modules/structs/admin_user.go index ad86f4ca03..5b7df127b4 100644 --- a/modules/structs/admin_user.go +++ b/modules/structs/admin_user.go @@ -30,10 +30,8 @@ type CreateUserOption struct { // EditUserOption edit user options type EditUserOption struct { - // required: true - SourceID int64 `json:"source_id"` - // required: true - LoginName string `json:"login_name" binding:"Required"` + SourceID *int64 `json:"source_id"` + LoginName *string `json:"login_name"` // swagger:strfmt email Email *string `json:"email" binding:"MaxSize(254)"` FullName *string `json:"full_name" binding:"MaxSize(100)"` diff --git a/modules/structs/release.go b/modules/structs/release.go index c7378645c2..d8da924f54 100644 --- a/modules/structs/release.go +++ b/modules/structs/release.go @@ -9,43 +9,47 @@ import ( // Release represents a repository release type Release struct { - ID int64 `json:"id"` - TagName string `json:"tag_name"` - Target string `json:"target_commitish"` - Title string `json:"name"` - Note string `json:"body"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - TarURL string `json:"tarball_url"` - ZipURL string `json:"zipball_url"` - UploadURL string `json:"upload_url"` - IsDraft bool `json:"draft"` - IsPrerelease bool `json:"prerelease"` + ID int64 `json:"id"` + TagName string `json:"tag_name"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + TarURL string `json:"tarball_url"` + ZipURL string `json:"zipball_url"` + HideArchiveLinks bool `json:"hide_archive_links"` + UploadURL string `json:"upload_url"` + IsDraft bool `json:"draft"` + IsPrerelease bool `json:"prerelease"` // swagger:strfmt date-time CreatedAt time.Time `json:"created_at"` // swagger:strfmt date-time - PublishedAt time.Time `json:"published_at"` - Publisher *User `json:"author"` - Attachments []*Attachment `json:"assets"` + PublishedAt time.Time `json:"published_at"` + Publisher *User `json:"author"` + Attachments []*Attachment `json:"assets"` + ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count"` } // CreateReleaseOption options when creating a release type CreateReleaseOption struct { // required: true - TagName string `json:"tag_name" binding:"Required"` - Target string `json:"target_commitish"` - Title string `json:"name"` - Note string `json:"body"` - IsDraft bool `json:"draft"` - IsPrerelease bool `json:"prerelease"` + TagName string `json:"tag_name" binding:"Required"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + IsDraft bool `json:"draft"` + IsPrerelease bool `json:"prerelease"` + HideArchiveLinks bool `json:"hide_archive_links"` } // EditReleaseOption options when editing a release type EditReleaseOption struct { - TagName string `json:"tag_name"` - Target string `json:"target_commitish"` - Title string `json:"name"` - Note string `json:"body"` - IsDraft *bool `json:"draft"` - IsPrerelease *bool `json:"prerelease"` + TagName string `json:"tag_name"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + IsDraft *bool `json:"draft"` + IsPrerelease *bool `json:"prerelease"` + HideArchiveLinks *bool `json:"hide_archive_links"` } diff --git a/modules/structs/repo.go b/modules/structs/repo.go index f6cc9803a4..1158160b53 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -89,6 +89,7 @@ type Repository struct { HasWiki bool `json:"has_wiki"` ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"` WikiBranch string `json:"wiki_branch,omitempty"` + GloballyEditableWiki bool `json:"globally_editable_wiki"` HasPullRequests bool `json:"has_pull_requests"` HasProjects bool `json:"has_projects"` HasReleases bool `json:"has_releases"` @@ -185,6 +186,8 @@ type EditRepoOption struct { HasWiki *bool `json:"has_wiki,omitempty"` // set this structure to use external wiki instead of internal ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"` + // set the globally editable state of the wiki + GloballyEditableWiki *bool `json:"globally_editable_wiki,omitempty"` // sets the default branch for this repository. DefaultBranch *string `json:"default_branch,omitempty"` // sets the branch used for this repository's wiki. diff --git a/modules/structs/repo_compare.go b/modules/structs/repo_compare.go new file mode 100644 index 0000000000..8a12498705 --- /dev/null +++ b/modules/structs/repo_compare.go @@ -0,0 +1,10 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// Compare represents a comparison between two commits. +type Compare struct { + TotalCommits int `json:"total_commits"` // Total number of commits in the comparison. + Commits []*Commit `json:"commits"` // List of commits in the comparison. +} diff --git a/modules/structs/repo_tag.go b/modules/structs/repo_tag.go index 4a7d895288..961ca4e53b 100644 --- a/modules/structs/repo_tag.go +++ b/modules/structs/repo_tag.go @@ -5,23 +5,25 @@ package structs // Tag represents a repository tag type Tag struct { - Name string `json:"name"` - Message string `json:"message"` - ID string `json:"id"` - Commit *CommitMeta `json:"commit"` - ZipballURL string `json:"zipball_url"` - TarballURL string `json:"tarball_url"` + Name string `json:"name"` + Message string `json:"message"` + ID string `json:"id"` + Commit *CommitMeta `json:"commit"` + ZipballURL string `json:"zipball_url"` + TarballURL string `json:"tarball_url"` + ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count"` } // AnnotatedTag represents an annotated tag type AnnotatedTag struct { - Tag string `json:"tag"` - SHA string `json:"sha"` - URL string `json:"url"` - Message string `json:"message"` - Tagger *CommitUser `json:"tagger"` - Object *AnnotatedTagObject `json:"object"` - Verification *PayloadCommitVerification `json:"verification"` + Tag string `json:"tag"` + SHA string `json:"sha"` + URL string `json:"url"` + Message string `json:"message"` + Tagger *CommitUser `json:"tagger"` + Object *AnnotatedTagObject `json:"object"` + Verification *PayloadCommitVerification `json:"verification"` + ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count"` } // AnnotatedTagObject contains meta information of the tag object @@ -38,3 +40,9 @@ type CreateTagOption struct { Message string `json:"message"` Target string `json:"target"` } + +// TagArchiveDownloadCount counts how many times a archive was downloaded +type TagArchiveDownloadCount struct { + Zip int64 `json:"zip"` + TarGz int64 `json:"tar_gz"` +} diff --git a/modules/structs/user.go b/modules/structs/user.go index 82b565e5e7..ad529c966e 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -19,6 +19,8 @@ type User struct { // the user's authentication sign-in name. // default: empty LoginName string `json:"login_name"` + // The ID of the user's Authentication Source + SourceID int64 `json:"source_id"` // the user's full name FullName string `json:"full_name"` // swagger:strfmt email diff --git a/modules/structs/variable.go b/modules/structs/variable.go new file mode 100644 index 0000000000..cc846cf0ec --- /dev/null +++ b/modules/structs/variable.go @@ -0,0 +1,37 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// CreateVariableOption the option when creating variable +// swagger:model +type CreateVariableOption struct { + // Value of the variable to create + // + // required: true + Value string `json:"value" binding:"Required"` +} + +// UpdateVariableOption the option when updating variable +// swagger:model +type UpdateVariableOption struct { + // New name for the variable. If the field is empty, the variable name won't be updated. + Name string `json:"name"` + // Value of the variable to update + // + // required: true + Value string `json:"value" binding:"Required"` +} + +// ActionVariable return value of the query API +// swagger:model +type ActionVariable struct { + // the owner to which the variable belongs + OwnerID int64 `json:"owner_id"` + // the repository to which the variable belongs + RepoID int64 `json:"repo_id"` + // the name of the variable + Name string `json:"name"` + // the value of the variable + Data string `json:"data"` +} diff --git a/modules/templates/helper.go b/modules/templates/helper.go index c9799a38ec..2268b8b0fb 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -53,13 +53,13 @@ func NewFuncMap() template.FuncMap { "JsonUtils": NewJsonUtils, // ----------------------------------------------------------------- - // svg / avatar / icon + // svg / avatar / icon / color "svg": svg.RenderHTML, "EntryIcon": base.EntryIcon, "MigrationIcon": MigrationIcon, "ActionIcon": ActionIcon, - - "SortArrow": SortArrow, + "SortArrow": SortArrow, + "ContrastColor": util.ContrastColor, // ----------------------------------------------------------------- // time / number / format @@ -109,6 +109,9 @@ func NewFuncMap() template.FuncMap { "ShowFooterTemplateLoadTime": func() bool { return setting.Other.ShowFooterTemplateLoadTime }, + "ShowFooterPoweredBy": func() bool { + return setting.Other.ShowFooterPoweredBy + }, "AllowedReactions": func() []string { return setting.UI.Reactions }, diff --git a/modules/templates/htmlrenderer.go b/modules/templates/htmlrenderer.go index 8661653adf..55a55dd7f4 100644 --- a/modules/templates/htmlrenderer.go +++ b/modules/templates/htmlrenderer.go @@ -138,10 +138,9 @@ func wrapTmplErrMsg(msg string) { if setting.IsProd { // in prod mode, Forgejo must have correct templates to run log.Fatal("Forgejo can't run with template errors: %s", msg) - } else { - // in dev mode, do not need to really exit, because the template errors could be fixed by developer soon and the templates get reloaded - log.Error("There are template errors but Forgejo continues to run in dev mode: %s", msg) } + // in dev mode, do not need to really exit, because the template errors could be fixed by developer soon and the templates get reloaded + log.Error("There are template errors but Forgejo continues to run in dev mode: %s", msg) } type templateErrorPrettier struct { diff --git a/modules/templates/mailer.go b/modules/templates/mailer.go index f1832cba0e..7c97e1ea89 100644 --- a/modules/templates/mailer.go +++ b/modules/templates/mailer.go @@ -84,9 +84,8 @@ func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) { if err = buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, tmplName, content); err != nil { if firstRun { log.Fatal("Failed to parse mail template, err: %v", err) - } else { - log.Error("Failed to parse mail template, err: %v", err) } + log.Error("Failed to parse mail template, err: %v", err) } } } diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index 5446741287..c4c5376afd 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -135,16 +135,9 @@ func RenderIssueTitle(ctx context.Context, text string, metas map[string]string) func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML { var ( archivedCSSClass string - textColor = "#111" + textColor = util.ContrastColor(label.Color) labelScope = label.ExclusiveScope() ) - r, g, b := util.HexToRBGColor(label.Color) - - // Determine if label text should be light or dark to be readable on background color - // this doesn't account for saturation or transparency - if util.UseLightTextOnBackground(r, g, b) { - textColor = "#eee" - } description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description)) @@ -168,7 +161,7 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m // Make scope and item background colors slightly darker and lighter respectively. // More contrast needed with higher luminance, empirically tweaked. - luminance := util.GetLuminance(r, g, b) + luminance := util.GetRelativeLuminance(label.Color) contrast := 0.01 + luminance*0.03 // Ensure we add the same amount of contrast also near 0 and 1. darken := contrast + math.Max(luminance+contrast-1.0, 0.0) @@ -178,6 +171,7 @@ func RenderLabel(ctx context.Context, locale translation.Locale, label *issues_m lightenFactor := math.Min(luminance+lighten, 1.0) / math.Max(luminance, 1.0/255.0) opacity := GetLabelOpacityByte(label.IsArchived()) + r, g, b := util.HexToRBGColor(label.Color) scopeBytes := []byte{ uint8(math.Min(math.Round(r*darkenFactor), 255)), uint8(math.Min(math.Round(g*darkenFactor), 255)), diff --git a/modules/util/color.go b/modules/util/color.go index 240b045c28..9c520dce78 100644 --- a/modules/util/color.go +++ b/modules/util/color.go @@ -4,22 +4,10 @@ package util import ( "fmt" - "math" "strconv" "strings" ) -// Check similar implementation in web_src/js/utils/color.js and keep synchronization - -// Return R, G, B values defined in reletive luminance -func getLuminanceRGB(channel float64) float64 { - sRGB := channel / 255 - if sRGB <= 0.03928 { - return sRGB / 12.92 - } - return math.Pow((sRGB+0.055)/1.055, 2.4) -} - // Get color as RGB values in 0..255 range from the hex color string (with or without #) func HexToRBGColor(colorString string) (float64, float64, float64) { hexString := colorString @@ -47,19 +35,23 @@ func HexToRBGColor(colorString string) (float64, float64, float64) { return r, g, b } -// return luminance given RGB channels -// Reference from: https://www.w3.org/WAI/GL/wiki/Relative_luminance -func GetLuminance(r, g, b float64) float64 { - R := getLuminanceRGB(r) - G := getLuminanceRGB(g) - B := getLuminanceRGB(b) - luminance := 0.2126*R + 0.7152*G + 0.0722*B - return luminance +// Returns relative luminance for a SRGB color - https://en.wikipedia.org/wiki/Relative_luminance +// Keep this in sync with web_src/js/utils/color.js +func GetRelativeLuminance(color string) float64 { + r, g, b := HexToRBGColor(color) + return (0.2126729*r + 0.7151522*g + 0.0721750*b) / 255 } -// Reference from: https://firsching.ch/github_labels.html -// In the future WCAG 3 APCA may be a better solution. -// Check if text should use light color based on RGB of background -func UseLightTextOnBackground(r, g, b float64) bool { - return GetLuminance(r, g, b) < 0.453 +func UseLightText(backgroundColor string) bool { + return GetRelativeLuminance(backgroundColor) < 0.453 +} + +// Given a background color, returns a black or white foreground color that the highest +// contrast ratio. In the future, the APCA contrast function, or CSS `contrast-color` will be better. +// https://github.com/color-js/color.js/blob/eb7b53f7a13bb716ec8b28c7a56f052cd599acd9/src/contrast/APCA.js#L42 +func ContrastColor(backgroundColor string) string { + if UseLightText(backgroundColor) { + return "#fff" + } + return "#000" } diff --git a/modules/util/color_test.go b/modules/util/color_test.go index d96ac36730..be6e6b122a 100644 --- a/modules/util/color_test.go +++ b/modules/util/color_test.go @@ -33,33 +33,31 @@ func Test_HexToRBGColor(t *testing.T) { } } -func Test_UseLightTextOnBackground(t *testing.T) { +func Test_UseLightText(t *testing.T) { cases := []struct { - r float64 - g float64 - b float64 - expected bool + color string + expected string }{ - {215, 58, 74, true}, - {0, 117, 202, true}, - {207, 211, 215, false}, - {162, 238, 239, false}, - {112, 87, 255, true}, - {0, 134, 114, true}, - {228, 230, 105, false}, - {216, 118, 227, true}, - {255, 255, 255, false}, - {43, 134, 133, true}, - {43, 135, 134, true}, - {44, 135, 134, true}, - {59, 182, 179, true}, - {124, 114, 104, true}, - {126, 113, 108, true}, - {129, 112, 109, true}, - {128, 112, 112, true}, + {"#d73a4a", "#fff"}, + {"#0075ca", "#fff"}, + {"#cfd3d7", "#000"}, + {"#a2eeef", "#000"}, + {"#7057ff", "#fff"}, + {"#008672", "#fff"}, + {"#e4e669", "#000"}, + {"#d876e3", "#000"}, + {"#ffffff", "#000"}, + {"#2b8684", "#fff"}, + {"#2b8786", "#fff"}, + {"#2c8786", "#000"}, + {"#3bb6b3", "#000"}, + {"#7c7268", "#fff"}, + {"#7e716c", "#fff"}, + {"#81706d", "#fff"}, + {"#807070", "#fff"}, + {"#84b6eb", "#000"}, } for n, c := range cases { - result := UseLightTextOnBackground(c.r, c.g, c.b) - assert.Equal(t, c.expected, result, "case %d: error should match", n) + assert.Equal(t, c.expected, ContrastColor(c.color), "case %d: error should match", n) } } diff --git a/modules/util/util.go b/modules/util/util.go index e4d658d7f8..b6ea283551 100644 --- a/modules/util/util.go +++ b/modules/util/util.go @@ -220,3 +220,12 @@ func Iif[T any](condition bool, trueVal, falseVal T) T { } return falseVal } + +func ReserveLineBreakForTextarea(input string) string { + // Since the content is from a form which is a textarea, the line endings are \r\n. + // It's a standard behavior of HTML. + // But we want to store them as \n like what GitHub does. + // And users are unlikely to really need to keep the \r. + // Other than this, we should respect the original content, even leading or trailing spaces. + return strings.ReplaceAll(input, "\r\n", "\n") +} diff --git a/modules/util/util_test.go b/modules/util/util_test.go index 819e12ee91..de8f065cad 100644 --- a/modules/util/util_test.go +++ b/modules/util/util_test.go @@ -121,9 +121,9 @@ func Test_NormalizeEOL(t *testing.T) { } func Test_RandomInt(t *testing.T) { - int, err := CryptoRandomInt(255) - assert.True(t, int >= 0) - assert.True(t, int <= 255) + randInt, err := CryptoRandomInt(255) + assert.True(t, randInt >= 0) + assert.True(t, randInt <= 255) assert.NoError(t, err) } @@ -235,3 +235,8 @@ func TestToPointer(t *testing.T) { val123 := 123 assert.False(t, &val123 == ToPointer(val123)) } + +func TestReserveLineBreakForTextarea(t *testing.T) { + assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata"), "test\ndata") + assert.Equal(t, ReserveLineBreakForTextarea("test\r\ndata\r\n"), "test\ndata\n") +} diff --git a/options/license/AMD-newlib b/options/license/AMD-newlib new file mode 100644 index 0000000000..1b2f1abd6f --- /dev/null +++ b/options/license/AMD-newlib @@ -0,0 +1,11 @@ +Copyright 1989, 1990 Advanced Micro Devices, Inc. + +This software is the property of Advanced Micro Devices, Inc (AMD) which +specifically grants the user the right to modify, use and distribute this +software provided this notice is not removed or altered. All other rights +are reserved by AMD. + +AMD MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH REGARD TO THIS +SOFTWARE. IN NO EVENT SHALL AMD BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL +DAMAGES IN CONNECTION WITH OR ARISING FROM THE FURNISHING, PERFORMANCE, OR +USE OF THIS SOFTWARE. diff --git a/options/license/APL-1.0 b/options/license/APL-1.0 index 261f2d687c..0748f90cd9 100644 --- a/options/license/APL-1.0 +++ b/options/license/APL-1.0 @@ -210,21 +210,21 @@ PART 1: INITIAL CONTRIBUTOR AND DESIGNATED WEB SITE The Initial Contributor is: ____________________________________________________ -  + [Enter full name of Initial Contributor] Address of Initial Contributor: ________________________________________________ -  + ________________________________________________ -  + ________________________________________________ -  + [Enter address above] The Designated Web Site is: __________________________________________________ -  + [Enter URL for Designated Web Site of Initial Contributor] NOTE: The Initial Contributor is to complete this Part 1, along with Parts 2, 3, and 5, and, if applicable, Parts 4 and 6. @@ -237,27 +237,27 @@ The date on which the Initial Work was first available under this License: _____ PART 3: GOVERNING JURISDICTION -For the purposes of this License, the Governing Jurisdiction is _________________________________________________. 
[Initial Contributor to Enter Governing Jurisdiction here] +For the purposes of this License, the Governing Jurisdiction is _________________________________________________. [Initial Contributor to Enter Governing Jurisdiction here] PART 4: THIRD PARTIES For the purposes of this License, "Third Party" has the definition set forth below in the ONE paragraph selected by the Initial Contributor from paragraphs A, B, C, D and E when the Initial Work is distributed or otherwise made available by the Initial Contributor. To select one of the following paragraphs, the Initial Contributor must place an "X" or "x" in the selection box alongside the one respective paragraph selected. SELECTION -  + BOX PARAGRAPH -[  ] A. "THIRD PARTY" means any third party. -  -  -[  ] B. "THIRD PARTY" means any third party except for any of the following: (a) a wholly owned subsidiary of the Subsequent Contributor in question; (b) a legal entity (the "PARENT") that wholly owns the Subsequent Contributor in question; or (c) a wholly owned subsidiary of the wholly owned subsidiary in (a) or of the Parent in (b). -  -  -[  ] C. "THIRD PARTY" means any third party except for any of the following: (a) any Person directly or indirectly owning a majority of the voting interest in the Subsequent Contributor or (b) any Person in which the Subsequent Contributor directly or indirectly owns a majority voting interest. -  -  -[  ] D. "THIRD PARTY" means any third party except for any Person directly or indirectly controlled by the Subsequent Contributor. For purposes of this definition, "control" shall mean the power to direct or cause the direction of, the management and policies of such Person whether through the ownership of voting interests, by contract, or otherwise. -  -  -[  ] E. "THIRD PARTY" means any third party except for any Person directly or indirectly controlling, controlled by, or under common control with the Subsequent Contributor. For purposes of this definition, "control" shall mean the power to direct or cause the direction of, the management and policies of such Person whether through the ownership of voting interests, by contract, or otherwise. +[ ] A. "THIRD PARTY" means any third party. + + +[ ] B. "THIRD PARTY" means any third party except for any of the following: (a) a wholly owned subsidiary of the Subsequent Contributor in question; (b) a legal entity (the "PARENT") that wholly owns the Subsequent Contributor in question; or (c) a wholly owned subsidiary of the wholly owned subsidiary in (a) or of the Parent in (b). + + +[ ] C. "THIRD PARTY" means any third party except for any of the following: (a) any Person directly or indirectly owning a majority of the voting interest in the Subsequent Contributor or (b) any Person in which the Subsequent Contributor directly or indirectly owns a majority voting interest. + + +[ ] D. "THIRD PARTY" means any third party except for any Person directly or indirectly controlled by the Subsequent Contributor. For purposes of this definition, "control" shall mean the power to direct or cause the direction of, the management and policies of such Person whether through the ownership of voting interests, by contract, or otherwise. + + +[ ] E. "THIRD PARTY" means any third party except for any Person directly or indirectly controlling, controlled by, or under common control with the Subsequent Contributor. For purposes of this definition, "control" shall mean the power to direct or cause the direction of, the management and policies of such Person whether through the ownership of voting interests, by contract, or otherwise. The default definition of "THIRD PARTY" is the definition set forth in paragraph A, if NONE OR MORE THAN ONE of paragraphs A, B, C, D or E in this Part 4 are selected by the Initial Contributor. PART 5: NOTICE @@ -271,8 +271,8 @@ PART 6: PATENT LICENSING TERMS For the purposes of this License, paragraphs A, B, C, D and E of this Part 6 of Exhibit A are only incorporated and form part of the terms of the License if the Initial Contributor places an "X" or "x" in the selection box alongside the YES answer to the question immediately below. Is this a Patents-Included License pursuant to Section 2.2 of the License? -YES [      ] -NO [      ] +YES [ ] +NO [ ] By default, if YES is not selected by the Initial Contributor, the answer is NO. diff --git a/options/license/BSD-2-clause-first-lines b/options/license/BSD-2-clause-first-lines new file mode 100644 index 0000000000..3774caf24a --- /dev/null +++ b/options/license/BSD-2-clause-first-lines @@ -0,0 +1,27 @@ +Copyright (C) 2006,2007,2009 NTT (Nippon Telegraph and Telephone +Corporation). All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer as the first lines of this file unmodified. + +2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +THIS SOFTWARE IS PROVIDED BY NTT "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL NTT BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/options/license/HPND-UC-export-US b/options/license/HPND-UC-export-US new file mode 100644 index 0000000000..015556c5f9 --- /dev/null +++ b/options/license/HPND-UC-export-US @@ -0,0 +1,10 @@ +Copyright (C) 1985, 1990 Regents of the University of California. + +Permission to use, copy, modify, and distribute this +software and its documentation for any purpose and without +fee is hereby granted, provided that the above copyright +notice appear in all copies. The University of California +makes no representations about the suitability of this +software for any purpose. It is provided "as is" without +express or implied warranty. Export of this software outside +of the United States of America may require an export license. diff --git a/options/license/IBM-pibs b/options/license/IBM-pibs index 49454b8b1e..ee9c7be36d 100644 --- a/options/license/IBM-pibs +++ b/options/license/IBM-pibs @@ -4,5 +4,5 @@ Any user of this software should understand that IBM cannot provide technical su Any person who transfers this source code or any derivative work must include the IBM copyright notice, this paragraph, and the preceding two paragraphs in the transferred software. -COPYRIGHT   I B M   CORPORATION 2002 -LICENSED MATERIAL  -  PROGRAM PROPERTY OF I B M +COPYRIGHT I B M CORPORATION 2002 +LICENSED MATERIAL - PROGRAM PROPERTY OF I B M diff --git a/options/license/NCGL-UK-2.0 b/options/license/NCGL-UK-2.0 index 31fbad6f83..15c4f63c22 100644 --- a/options/license/NCGL-UK-2.0 +++ b/options/license/NCGL-UK-2.0 @@ -12,15 +12,15 @@ The Licensor grants you a worldwide, royalty-free, perpetual, non-exclusive lice This licence does not affect your freedom under fair dealing or fair use or any other copyright or database right exceptions and limitations. You are free to: - copy, publish, distribute and transmit the Information; + copy, publish, distribute and transmit the Information; adapt the Information; exploit the Information for Non-Commercial purposes for example, by combining it with other information in your own product or application. You are not permitted to: - exercise any of the rights granted to you by this licence in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation. + exercise any of the rights granted to you by this licence in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation. You must, where you do any of the above: - acknowledge the source of the Information by including any attribution statement specified by the Information Provider(s) and, where possible, provide a link to this licence; + acknowledge the source of the Information by including any attribution statement specified by the Information Provider(s) and, where possible, provide a link to this licence; If the Information Provider does not provide a specific attribution statement, you must use the following: Contains information licensed under the Non-Commercial Government Licence v2.0. diff --git a/options/license/NCL b/options/license/NCL new file mode 100644 index 0000000000..3bfb658c26 --- /dev/null +++ b/options/license/NCL @@ -0,0 +1,32 @@ +Copyright (c) 2004 the University Corporation for Atmospheric +Research ("UCAR"). All rights reserved. Developed by NCAR's +Computational and Information Systems Laboratory, UCAR, +www.cisl.ucar.edu. + +Redistribution and use of the Software in source and binary forms, +with or without modification, is permitted provided that the +following conditions are met: + +- Neither the names of NCAR's Computational and Information Systems +Laboratory, the University Corporation for Atmospheric Research, +nor the names of its sponsors or contributors may be used to +endorse or promote products derived from this Software without +specific prior written permission. + +- Redistributions of source code must retain the above copyright +notices, this list of conditions, and the disclaimer below. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions, and the disclaimer below in the +documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. diff --git a/options/license/NPL-1.1 b/options/license/NPL-1.1 index 62c5296400..0d5457ff04 100644 --- a/options/license/NPL-1.1 +++ b/options/license/NPL-1.1 @@ -2,7 +2,7 @@ Netscape Public LIcense version 1.1 AMENDMENTS -The Netscape Public License Version 1.1 ("NPL") consists of the Mozilla Public License Version 1.1 with the following Amendments, including Exhibit A-Netscape Public License.  Files identified with "Exhibit A-Netscape Public License" are governed by the Netscape Public License Version 1.1. +The Netscape Public License Version 1.1 ("NPL") consists of the Mozilla Public License Version 1.1 with the following Amendments, including Exhibit A-Netscape Public License. Files identified with "Exhibit A-Netscape Public License" are governed by the Netscape Public License Version 1.1. Additional Terms applicable to the Netscape Public License. @@ -28,7 +28,7 @@ Additional Terms applicable to the Netscape Public License. Notwithstanding the limitations of Section 11 above, the provisions regarding litigation in Section 11(a), (b) and (c) of the License shall apply to all disputes relating to this License. EXHIBIT A-Netscape Public License. -   + "The contents of this file are subject to the Netscape Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/NPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. @@ -37,8 +37,8 @@ The Original Code is Mozilla Communicator client code, released March 31, 1998. The Initial Developer of the Original Code is Netscape Communications Corporation. Portions created by Netscape are Copyright (C) 1998-1999 Netscape Communications Corporation. All Rights Reserved. Contributor(s): ______________________________________. -   -Alternatively, the contents of this file may be used under the terms of the _____ license (the  "[___] License"), in which case the provisions of [______] License are applicable  instead of those above.  If you wish to allow use of your version of this file only under the terms of the [____] License and not to allow others to use your version of this file under the NPL, indicate your decision by deleting  the provisions above and replace  them with the notice and other provisions required by the [___] License.  If you do not delete the provisions above, a recipient may use your version of this file under either the NPL or the [___] License." + +Alternatively, the contents of this file may be used under the terms of the _____ license (the "[___] License"), in which case the provisions of [______] License are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the [____] License and not to allow others to use your version of this file under the NPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the [___] License. If you do not delete the provisions above, a recipient may use your version of this file under either the NPL or the [___] License." Mozilla Public License Version 1.1 diff --git a/options/license/OAR b/options/license/OAR new file mode 100644 index 0000000000..ca5c4b9617 --- /dev/null +++ b/options/license/OAR @@ -0,0 +1,12 @@ +COPYRIGHT (c) 1989-2013, 2015. +On-Line Applications Research Corporation (OAR). + +Permission to use, copy, modify, and distribute this software for any +purpose without fee is hereby granted, provided that this entire notice +is included in all copies of any software which is or includes a copy +or modification of this software. + +THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION +OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS +SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. diff --git a/options/license/OCCT-PL b/options/license/OCCT-PL index 85df3c73c5..9b6fccc1c9 100644 --- a/options/license/OCCT-PL +++ b/options/license/OCCT-PL @@ -6,7 +6,7 @@ OPEN CASCADE releases and makes publicly available the source code of the softwa It is not the purpose of this license to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this license has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. Please read this license carefully and completely before downloading this software. By downloading, using, modifying, distributing and sublicensing this software, you indicate your acceptance to be bound by the terms and conditions of this license. If you do not want to accept or cannot accept for any reasons the terms and conditions of this license, please do not download or use in any manner this software. -  + 1. Definitions Unless there is something in the subject matter or in the context inconsistent therewith, the capitalized terms used in this License shall have the following meaning. @@ -26,13 +26,13 @@ Unless there is something in the subject matter or in the context inconsistent t "Software": means the Original Code, the Modifications, the combination of Original Code and any Modifications or any respective portions thereof. "You" or "Your": means an individual or a legal entity exercising rights under this License -  + 2. Acceptance of license By using, reproducing, modifying, distributing or sublicensing the Software or any portion thereof, You expressly indicate Your acceptance of the terms and conditions of this License and undertake to act in accordance with all the provisions of this License applicable to You. -  + 3. Scope and purpose This License applies to the Software and You may not use, reproduce, modify, distribute, sublicense or circulate the Software, or any portion thereof, except as expressly provided under this License. Any attempt to otherwise use, reproduce, modify, distribute or sublicense the Software is void and will automatically terminate Your rights under this License. -  + 4. Contributor license Subject to the terms and conditions of this License, the Initial Developer and each of the Contributors hereby grant You a world-wide, royalty-free, irrevocable and non-exclusive license under the Applicable Intellectual Property Rights they own or control, to use, reproduce, modify, distribute and sublicense the Software provided that: diff --git a/options/license/OGL-UK-1.0 b/options/license/OGL-UK-1.0 index a761c9916f..867c0e353b 100644 --- a/options/license/OGL-UK-1.0 +++ b/options/license/OGL-UK-1.0 @@ -10,20 +10,20 @@ The Licensor grants you a worldwide, royalty-free, perpetual, non-exclusive lice This licence does not affect your freedom under fair dealing or fair use or any other copyright or database right exceptions and limitations. You are free to: - copy, publish, distribute and transmit the Information; + copy, publish, distribute and transmit the Information; adapt the Information; exploit the Information commercially for example, by combining it with other Information, or by including it in your own product or application. You must, where you do any of the above: - acknowledge the source of the Information by including any attribution statement specified by the Information Provider(s) and, where possible, provide a link to this licence; -  If the Information Provider does not provide a specific attribution statement, or if you are using Information from several Information Providers and multiple attributions are not practical in your product or application, you may consider using the following:
 Contains public sector information licensed under the Open Government Licence v1.0. + acknowledge the source of the Information by including any attribution statement specified by the Information Provider(s) and, where possible, provide a link to this licence; + If the Information Provider does not provide a specific attribution statement, or if you are using Information from several Information Providers and multiple attributions are not practical in your product or application, you may consider using the following: Contains public sector information licensed under the Open Government Licence v1.0. ensure that you do not use the Information in a way that suggests any official status or that the Information Provider endorses you or your use of the Information; ensure that you do not mislead others or misrepresent the Information or its source; ensure that your use of the Information does not breach the Data Protection Act 1998 or the Privacy and Electronic Communications (EC Directive) Regulations 2003. These are important conditions of this licence and if you fail to comply with them the rights granted to you under this licence, or any similar licence granted by the Licensor, will end automatically. - Exemptions + Exemptions This licence does not cover the use of: - personal data in the Information; @@ -48,22 +48,22 @@ Definitions In this licence, the terms below have the following meanings: -‘Information’
means information protected by copyright or by database right (for example, literary and artistic works, content, data and source code) offered for use under the terms of this licence. +‘Information’ means information protected by copyright or by database right (for example, literary and artistic works, content, data and source code) offered for use under the terms of this licence. -‘Information Provider’
means the person or organisation providing the Information under this licence. +‘Information Provider’ means the person or organisation providing the Information under this licence. -‘Licensor’
means any Information Provider which has the authority to offer Information under the terms of this licence or the Controller of Her Majesty’s Stationery Office, who has the authority to offer Information subject to Crown copyright and Crown database rights and Information subject to copyright and database right that has been assigned to or acquired by the Crown, under the terms of this licence. +‘Licensor’ means any Information Provider which has the authority to offer Information under the terms of this licence or the Controller of Her Majesty’s Stationery Office, who has the authority to offer Information subject to Crown copyright and Crown database rights and Information subject to copyright and database right that has been assigned to or acquired by the Crown, under the terms of this licence. -‘Use’
as a verb, means doing any act which is restricted by copyright or database right, whether in the original medium or in any other medium, and includes without limitation distributing, copying, adapting, modifying as may be technically necessary to use it in a different mode or format. +‘Use’ as a verb, means doing any act which is restricted by copyright or database right, whether in the original medium or in any other medium, and includes without limitation distributing, copying, adapting, modifying as may be technically necessary to use it in a different mode or format. -‘You’
means the natural or legal person, or body of persons corporate or incorporate, acquiring rights under this licence. +‘You’ means the natural or legal person, or body of persons corporate or incorporate, acquiring rights under this licence. About the Open Government Licence The Controller of Her Majesty’s Stationery Office (HMSO) has developed this licence as a tool to enable Information Providers in the public sector to license the use and re-use of their Information under a common open licence. The Controller invites public sector bodies owning their own copyright and database rights to permit the use of their Information under this licence. -The Controller of HMSO has authority to license Information subject to copyright and database right owned by the Crown. The extent of the Controller’s offer to license this Information under the terms of this licence is set out in the UK Government Licensing Framework. +The Controller of HMSO has authority to license Information subject to copyright and database right owned by the Crown. The extent of the Controller’s offer to license this Information under the terms of this licence is set out in the UK Government Licensing Framework. This is version 1.0 of the Open Government Licence. The Controller of HMSO may, from time to time, issue new versions of the Open Government Licence. However, you may continue to use Information licensed under this version should you wish to do so. These terms have been aligned to be interoperable with any Creative Commons Attribution Licence, which covers copyright, and Open Data Commons Attribution License, which covers database rights and applicable copyrights. -Further context, best practice and guidance can be found in the UK Government Licensing Framework section on The National Archives website. +Further context, best practice and guidance can be found in the UK Government Licensing Framework section on The National Archives website. diff --git a/options/license/OSET-PL-2.1 b/options/license/OSET-PL-2.1 index 15f0c7758c..e0ed2e1398 100644 --- a/options/license/OSET-PL-2.1 +++ b/options/license/OSET-PL-2.1 @@ -100,7 +100,8 @@ If it is impossible for You to comply with any of the terms of this License with 5.1 Failure to Comply The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60-days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30-days after Your receipt of the notice. - 5.2 Patent Infringement Claims
 If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. + 5.2 Patent Infringement Claims + If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3 Additional Compliance Terms Notwithstanding the foregoing in this Section 5, for purposes of this Section, if You breach Section 3.1 (Distribution of Source Form), Section 3.2 (Distribution of Executable Form), Section 3.3 (Distribution of a Larger Work), or Section 3.4 (Notices), then becoming compliant as described in Section 5.1 must also include, no later than 30 days after receipt by You of notice of such violation by a Contributor, making the Covered Software available in Source Code Form as required by this License on a publicly available computer network for a period of no less than three (3) years. diff --git a/options/license/SHL-2.0 b/options/license/SHL-2.0 index e522a396fe..9218b47a72 100644 --- a/options/license/SHL-2.0 +++ b/options/license/SHL-2.0 @@ -1,22 +1,22 @@ # Solderpad Hardware Licence Version 2.0 -This licence (the “Licence”) operates as a wraparound licence to the Apache License Version 2.0 (the “Apache License”) and grants to You the rights, and imposes the obligations, set out in the Apache License (which can be found here: http://apache.org/licenses/LICENSE-2.0), with the following extensions. It must be read in conjunction with the Apache License. Section 1 below modifies definitions in the Apache License, and section 2 below replaces sections 2 of the Apache License. You may, at your option, choose to treat any Work released under this License as released under the Apache License (thus ignoring all sections written below entirely). Words in italics indicate changes rom the Apache License, but are indicative and not to be taken into account in interpretation. +This licence (the “Licence”) operates as a wraparound licence to the Apache License Version 2.0 (the “Apache License”) and grants to You the rights, and imposes the obligations, set out in the Apache License (which can be found here: http://apache.org/licenses/LICENSE-2.0), with the following extensions. It must be read in conjunction with the Apache License. Section 1 below modifies definitions in the Apache License, and section 2 below replaces sections 2 of the Apache License. You may, at your option, choose to treat any Work released under this License as released under the Apache License (thus ignoring all sections written below entirely). Words in italics indicate changes rom the Apache License, but are indicative and not to be taken into account in interpretation. 1. The definitions set out in the Apache License are modified as follows: -Copyright any reference to ‘copyright’ (whether capitalised or not) includes ‘Rights’ (as defined below). +Copyright any reference to ‘copyright’ (whether capitalised or not) includes ‘Rights’ (as defined below). -Contribution also includes any design, as well as any work of authorship. +Contribution also includes any design, as well as any work of authorship. -Derivative Works shall not include works that remain reversibly separable from, or merely link (or bind by name) or physically connect to or interoperate with the interfaces of the Work and Derivative Works thereof. +Derivative Works shall not include works that remain reversibly separable from, or merely link (or bind by name) or physically connect to or interoperate with the interfaces of the Work and Derivative Works thereof. -Object form shall mean any form resulting from mechanical transformation or translation of a Source form or the application of a Source form to physical material, including but not limited to compiled object code, generated documentation, the instantiation of a hardware design or physical object and conversions to other media types, including intermediate forms such as bytecodes, FPGA bitstreams, moulds, artwork and semiconductor topographies (mask works). +Object form shall mean any form resulting from mechanical transformation or translation of a Source form or the application of a Source form to physical material, including but not limited to compiled object code, generated documentation, the instantiation of a hardware design or physical object and conversions to other media types, including intermediate forms such as bytecodes, FPGA bitstreams, moulds, artwork and semiconductor topographies (mask works). -Rights means copyright and any similar right including design right (whether registered or unregistered), semiconductor topography (mask) rights and database rights (but excluding Patents and Trademarks). +Rights means copyright and any similar right including design right (whether registered or unregistered), semiconductor topography (mask) rights and database rights (but excluding Patents and Trademarks). -Source form shall mean the preferred form for making modifications, including but not limited to source code, net lists, board layouts, CAD files, documentation source, and configuration files. -Work also includes a design or work of authorship, whether in Source form or other Object form. +Source form shall mean the preferred form for making modifications, including but not limited to source code, net lists, board layouts, CAD files, documentation source, and configuration files. +Work also includes a design or work of authorship, whether in Source form or other Object form. 2. Grant of Licence -2.1 Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license under the Rights to reproduce, prepare Derivative Works of, make, adapt, repair, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form and do anything in relation to the Work as if the Rights did not exist. +2.1 Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license under the Rights to reproduce, prepare Derivative Works of, make, adapt, repair, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form and do anything in relation to the Work as if the Rights did not exist. diff --git a/options/license/SHL-2.1 b/options/license/SHL-2.1 index 4815a9e5ed..c9ae53741f 100644 --- a/options/license/SHL-2.1 +++ b/options/license/SHL-2.1 @@ -19,7 +19,7 @@ The following definitions shall replace the corresponding definitions in the Apa "License" shall mean this Solderpad Hardware License version 2.1, being the terms and conditions for use, manufacture, instantiation, adaptation, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the Rights owner or entity authorized by the Rights owner that is granting the License. -  + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship or design. For the purposes of this License, Derivative Works shall not include works that remain reversibly separable from, or merely link (or bind by name) or physically connect to or interoperate with the Work and Derivative Works thereof. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form or the application of a Source form to physical material, including but not limited to compiled object code, generated documentation, the instantiation of a hardware design or physical object or material and conversions to other media types, including intermediate forms such as bytecodes, FPGA bitstreams, moulds, artwork and semiconductor topographies (mask works). diff --git a/options/license/SISSL b/options/license/SISSL index 7d6ad9d66c..af38d02d92 100644 --- a/options/license/SISSL +++ b/options/license/SISSL @@ -36,13 +36,13 @@ Sun Industry Standards Source License - Version 1.1 2.0 SOURCE CODE LICENSE - 2.1 The Initial Developer Grant The Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license, subject to third party intellectual property claims:  + 2.1 The Initial Developer Grant The Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license, subject to third party intellectual property claims: (a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer to use, reproduce, modify, display, perform, sublicense and distribute the Original Code (or portions thereof) with or without Modifications, and/or as part of a Larger Work; and (b) under Patents Claims infringed by the making, using or selling of Original Code, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Code (or portions thereof). (c) the licenses granted in this Section 2.1(a) and (b) are effective on the date Initial Developer first distributes Original Code under the terms of this License. - (d) Notwithstanding Section 2.1(b) above, no patent license is granted: 1) for code that You delete from the Original Code; 2) separate from the Original Code; or 3) for infringements caused by: i) the modification of the Original Code or ii) the combination of the Original Code with other software or devices, including but not limited to Modifications.  + (d) Notwithstanding Section 2.1(b) above, no patent license is granted: 1) for code that You delete from the Original Code; 2) separate from the Original Code; or 3) for infringements caused by: i) the modification of the Original Code or ii) the combination of the Original Code with other software or devices, including but not limited to Modifications. 3.0 DISTRIBUTION OBLIGATIONS @@ -92,14 +92,14 @@ This License represents the complete agreement concerning subject matter hereof. EXHIBIT A - Sun Standards License -"The contents of this file are subject to the Sun Standards License Version 1.1 (the "License"); You may not use this file except in compliance with the License. You may obtain a copy of the License at _______________________________. +"The contents of this file are subject to the Sun Standards License Version 1.1 (the "License"); You may not use this file except in compliance with the License. You may obtain a copy of the License at _______________________________. -Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either  +Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is ______________________________________. -The Initial Developer of the Original Code is:  +The Initial Developer of the Original Code is: Sun Microsystems, Inc.. Portions created by: _______________________________________ diff --git a/options/license/Sun-PPP-2000 b/options/license/Sun-PPP-2000 new file mode 100644 index 0000000000..914c19544a --- /dev/null +++ b/options/license/Sun-PPP-2000 @@ -0,0 +1,13 @@ +Copyright (c) 2000 by Sun Microsystems, Inc. +All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation is hereby granted, provided that the above copyright +notice appears in all copies. + +SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF +THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR +ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR +DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES diff --git a/options/license/W3C-19980720 b/options/license/W3C-19980720 index a8554039ef..134879044d 100644 --- a/options/license/W3C-19980720 +++ b/options/license/W3C-19980720 @@ -4,7 +4,7 @@ Copyright (c) 1994-2002 World Wide Web Consortium, (Massachusetts Institute of T This W3C work (including software, documents, or other related items) is being provided by the copyright holders under the following license. By obtaining, using and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions: -Permission to use, copy, modify, and distribute this software and its documentation, with or without modification,  for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the software and documentation or portions thereof, including modifications, that you make: +Permission to use, copy, modify, and distribute this software and its documentation, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the software and documentation or portions thereof, including modifications, that you make: 1. The full text of this NOTICE in a location viewable to users of the redistributed or derivative work. diff --git a/options/license/pkgconf b/options/license/pkgconf new file mode 100644 index 0000000000..b8b2ffd996 --- /dev/null +++ b/options/license/pkgconf @@ -0,0 +1,7 @@ +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +This software is provided 'as is' and without any warranty, express or +implied. In no event shall the authors be liable for any damages arising +from the use of this software. diff --git a/options/license/xzoom b/options/license/xzoom new file mode 100644 index 0000000000..f312dedbc2 --- /dev/null +++ b/options/license/xzoom @@ -0,0 +1,12 @@ +Copyright Itai Nahshon 1995, 1996. +This program is distributed with no warranty. + +Source files for this program may be distributed freely. +Modifications to this file are okay as long as: + a. This copyright notice and comment are preserved and + left at the top of the file. + b. The man page is fixed to reflect the change. + c. The author of this change adds his name and change + description to the list of changes below. +Executable files may be distributed with sources, or with +exact location where the source code can be obtained. diff --git a/options/locale/locale_ar.ini b/options/locale/locale_ar.ini index 133e84a6e1..bcd4b6af9f 100644 --- a/options/locale/locale_ar.ini +++ b/options/locale/locale_ar.ini @@ -138,7 +138,7 @@ reinstall_confirm_message = إعادة التثبيت باستخدام قاعد db_schema = مخطط reinstall_error = أنت تحاول التثبيت في قاعدة بيانات فورجيو موجودة sqlite_helper = مسار المِلَفّ لقاعدة بيانات SQLite3.
    أدخل المسار المطلق إذا شغلت فورجيو كخدمة. -require_db_desc = فورجيو يحتاج MySQL أو PostgreSQL أو MSSQL أو SQLite3 أو TiDB. +require_db_desc = فورجيو يحتاج MySQL أو PostgreSQL أو SQLite3 أو TiDB. password = كلمة المرور host = المضيف docker_helper = إذا كنت تستخدم فورجيو داخل دوكر، يرجي قراءة المستندات قبل تغيير أي إعدادات. @@ -1548,7 +1548,6 @@ dashboard.sync_tag.started = بدأ تزامن الوسوم dashboard.sync_repo_tags = زامن الوسوم من بيانات جِت إلى قاعدة البيانات self_check = فحص ذاتي self_check.database_collation_case_insensitive = تستخدم قاعدة البيانات تجميع %s ، وهو تجميع غير حساس. على الرغم من أن فورجيو يمكن أن يعمل معها قد تكون هناك حالات نادرة لا تعمل كما هو متوقع. -self_check.database_fix_mssql = بالنسبة لمستخدمي الـإم إس سيكول، يمكنك إصلاح المشكلة بتعديل السيكول يدوياً فقط في الوقت الراهن. monitor.process.cancel_desc = قد يسبب إلغاء العملية فقدانًا للبيانات monitor.queue.type = النوع monitor.process.cancel_notices = أتريد إلغاء: %s؟ diff --git a/options/locale/locale_bg.ini b/options/locale/locale_bg.ini index 7042b42e4a..d7093b85d5 100644 --- a/options/locale/locale_bg.ini +++ b/options/locale/locale_bg.ini @@ -1242,7 +1242,7 @@ admin_name = Потреб. име за администратор confirm_password = Потвърдете паролата title = Първоначална конфигурация domain = Домейн на сървъра -require_db_desc = Forgejo изисква MySQL, PostgreSQL, MSSQL, SQLite3 или TiDB (MySQL протокол). +require_db_desc = Forgejo изисква MySQL, PostgreSQL, SQLite3 или TiDB (MySQL протокол). general_title = Общи настройки email_title = Настройки на ел. поща db_schema = Схема diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 4f0f435d69..49aabeaf05 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -220,7 +220,7 @@ license_desc=Vyzkoušejte dokumentaci. -require_db_desc=Forgejo vyžaduje MySQL, PostgreSQL, MSSQL, SQLite3 nebo TiDB (protokol MySQL). +require_db_desc=Forgejo vyžaduje MySQL, PostgreSQL, SQLite3 nebo TiDB (protokol MySQL). db_title=Nastavení databáze db_type=Typ databáze host=Hostitel @@ -3426,7 +3426,6 @@ self_check = Vlastní kontrola dashboard.sync_tag.started = Synchronizace značek spuštěna dashboard.rebuild_issue_indexer = Přestavit indexer vydání self_check.database_collation_case_insensitive = Databáze používá collation %s. Jedná se o intenzivní collation. Ačkoli s ní Forgejo nejspíše bude pracovat, mohou nastat určité vzácné případy, kdy nebude pracovat tak, jak má. -self_check.database_fix_mssql = Uživatelé MSSQL mohou tento problém vyřešit pouze ručními SQL příkazy „ALTER ... COLLATE ...“. auths.oauth2_map_group_to_team = Zmapovat zabrané skupiny u týmů organizací (volitelné - vyžaduje název claimu výše) monitor.queue.settings.desc = Pooly dynamicky rostou podle blokování fronty jejich workerů. @@ -3435,7 +3434,6 @@ self_check.database_collation_mismatch=Očekávejte, že databáze použije coll self_check.database_collation_case_insensitive=Databáze používá collation %s, což je collation nerozlišující velká a malá písmena. Ačkoli s ní Gitea může pracovat, mohou se vyskytnout vzácné případy, kdy nebude fungovat podle očekávání. self_check.database_inconsistent_collation_columns=Databáze používá collation %s, ale tyto sloupce používají chybné collation. To může způsobit neočekávané problémy. self_check.database_fix_mysql=Pro uživatele MySQL/MariaDB můžete použít příkaz "gitea doctor convert", který opraví problémy s collation, nebo můžete také problém vyřešit příkazem "ALTER ... COLLATE ..." SQL ručně. -self_check.database_fix_mssql=Uživatelé MSSQL mohou problém vyřešit pouze pomocí příkazu "ALTER ... COLLATE ..." SQL ručně. auths.tips.gmail_settings = Nastavení služby Gmail: config_summary = Souhrn config.open_with_editor_app_help = Editory v nabídce „Otevřít pomocí“ v nabídce klonování. Ponechte prázdné pro použití výchozího editoru (zobrazíte jej rozšířením). diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 2a5ab7f44c..07ea493409 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -218,7 +218,7 @@ license_desc=Hole dir Dokumentation, bevor du irgendwelche Einstellungen veränderst. -require_db_desc=Forgejo benötigt MySQL, PostgreSQL, MSSQL, SQLite3 oder TiDB (MySQL-Protokoll). +require_db_desc=Forgejo benötigt MySQL, PostgreSQL, SQLite3 oder TiDB (MySQL-Protokoll). db_title=Datenbankeinstellungen db_type=Datenbanktyp host=Host @@ -3398,7 +3398,6 @@ self_check = Selbstprüfung dashboard.sync_repo_tags = Tags aus Git-Daten zu Datenbank synchronisieren emails.change_email_text = Bist du dir sicher, dass du diese E-Mail-Addresse aktualisieren möchtest? packages.cleanup.success = Abgelaufene Daten erfolgreich gesäubert -self_check.database_fix_mssql = Für MSSQL-Benutzer: Du kannst das Problem im Moment nur mit „ALTER … COLLATE …“-SQLs beheben. self_check.no_problem_found = Noch kein Problem gefunden. self_check.database_inconsistent_collation_columns = Datenbank benutzt Collation %s, doch diese Spalten verwenden Collations, die nicht zusammenpassen. Das könnte ein paar unerwartete Probleme verursachen. self_check.database_collation_mismatch = Erwarte von Datenbank, folgende Collation zu verwenden: %s diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 30383665ad..0be1aab87f 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -217,7 +217,7 @@ license_desc=Κατέβασε το εγχειρίδιο πριν αλλάξετε τις ρυθμίσεις. -require_db_desc=Το Forgejo απαιτεί MySQL, PostgreSQL, MSSQL, SQLite3 ή TiDB (με πρωτόκολλο MySQL). +require_db_desc=Το Forgejo απαιτεί MySQL, PostgreSQL, SQLite3 ή TiDB (με πρωτόκολλο MySQL). db_title=Ρυθμίσεις βάσης δεδομένων db_type=Είδος βάσης δεδομένων host=Διακομιστής @@ -3385,7 +3385,6 @@ notices.desc=Περιγραφή notices.op=Λειτ. notices.delete_success=Οι ειδοποιήσεις του συστήματος έχουν διαγραφεί. self_check.no_problem_found = Μέχρι τώρα, δεν έχει βρεθεί κάποιο πρόβλημα. -self_check.database_fix_mssql = Προς το παρόν, οι χρήστες του MSSQL μπορούν να διορθώσουν το πρόβλημα αυτό χειροκίνητα χρησιμοποιώντας τις εντολές SQL «ALTER ... COLLATE ...». self_check = Αυτοέλεγχος dashboard.sync_repo_tags = Συγχρονισμός tag, χρησιμοποιώντας τα δεδομένα git στην βάση δεδομένων dashboard.sync_tag.started = Ο συγχρονισμός tag έχει ξεκινήσει diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 919f38ad68..9148d57a27 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -239,7 +239,7 @@ license_desc = Go get documentation before changing any settings. -require_db_desc = Forgejo requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol). +require_db_desc = Forgejo requires MySQL, PostgreSQL, SQLite3 or TiDB (MySQL protocol). db_title = Database settings db_type = Database type host = Host @@ -562,6 +562,7 @@ TeamName = Team name AuthName = Authorization name AdminEmail = Admin email To = Branch name +AccessToken = Access token NewBranchName = New branch name CommitSummary = Commit summary @@ -2374,6 +2375,7 @@ settings.slack_token = Token settings.slack_domain = Domain settings.slack_channel = Channel settings.add_web_hook_desc = Integrate %s into your repository. +settings.graphql_url = GraphQL URL settings.web_hook_name_gitea = Gitea settings.web_hook_name_forgejo = Forgejo settings.web_hook_name_gogs = Gogs @@ -2393,10 +2395,10 @@ settings.packagist_api_token = API token settings.packagist_package_url = Packagist package URL settings.web_hook_name_sourcehut_builds = SourceHut Builds settings.sourcehut_builds.manifest_path = Build manifest path -settings.sourcehut_builds.graphql_url = GraphQL URL (e.g. https://builds.sr.ht/query) settings.sourcehut_builds.visibility = Job visibility settings.sourcehut_builds.secrets = Secrets settings.sourcehut_builds.secrets_helper = Give the job access to the build secrets (requires the SECRETS:RO grant) +settings.sourcehut_builds.access_token_helper = Access token that has JOBS:RW grant. Generate a builds.sr.ht token or a builds.sr.ht token with secrets access on meta.sr.ht. settings.deploy_keys = Deploy keys settings.add_deploy_key = Add deploy key settings.deploy_key_desc = Deploy keys have read-only pull access to the repository. @@ -2503,6 +2505,8 @@ settings.thread_id = Thread ID settings.matrix.homeserver_url = Homeserver URL settings.matrix.room_id = Room ID settings.matrix.message_type = Message type +settings.matrix.access_token_helper = It is recommended to setup a dedicated Matrix account for this. The access token can be retrieved from the Element web client (in a private/incognito tab) > User menu (top left) > All settings > Help & About > Advanced > Access Token (right below the Homeserver URL). Close the private/incognito tab (logging out would invalidate the token). +settings.matrix.room_id_helper = The Room ID can be retrieved from the Element web client > Room Settings > Advanced > Internal room ID. Example: %s. settings.archive.button = Archive repo settings.archive.header = Archive this repo settings.archive.text = Archiving the repo will make it entirely read-only. It will be hidden from the dashboard. Nobody (not even you!) will be able to make new commits, or open any issues or pull requests. @@ -2653,6 +2657,8 @@ release.downloads = Downloads release.download_count_one = %s download release.download_count_few = %s downloads release.add_tag_msg = Use the title and content of release as tag message. +release.hide_archive_links = Hide automatically generated archives +release.hide_archive_links_helper = Hide automatically generated source code archives for this release. For example, if you are uploading your own. release.add_tag = Create tag release.releases_for = Releases for %s release.tags_for = Tags for %s @@ -3089,6 +3095,7 @@ auths.attribute_mail = Email attribute auths.attribute_ssh_public_key = Public SSH key attribute auths.attribute_avatar = Avatar attribute auths.attributes_in_bind = Fetch attributes in bind DN context +auths.default_domain_name = Default domain name used for the email address auths.allow_deactivate_all = Allow an empty search result to deactivate all users auths.use_paged_search = Use paged search auths.search_page_size = Page size @@ -3382,7 +3389,6 @@ self_check.database_collation_mismatch = Expect database to use collation: %s self_check.database_collation_case_insensitive = Database is using a collation %s, which is an insensitive collation. Although Forgejo could work with it, there might be some rare cases which don't work as expected. self_check.database_inconsistent_collation_columns = Database is using collation %s, but these columns are using mismatched collations. It might cause some unexpected problems. self_check.database_fix_mysql = For MySQL/MariaDB users, you could use the "gitea doctor convert" command to fix the collation problems, or you could also fix the problem by "ALTER ... COLLATE ..." SQLs manually. -self_check.database_fix_mssql = For MSSQL users, you could only fix the problem by "ALTER ... COLLATE ..." SQLs manually at the moment. [action] create_repo = created repository %s diff --git a/options/locale/locale_eo.ini b/options/locale/locale_eo.ini index 8796d9c86b..02542631eb 100644 --- a/options/locale/locale_eo.ini +++ b/options/locale/locale_eo.ini @@ -226,7 +226,7 @@ disable_gravatar = Malŝalti profilbildojn per Gravatar repo_path_helper = Foraj Git-deponejoj konserviĝos al tiu ĉi dosierujo. sqlite_helper = Dosiervojo por la datumbazo SQLite3.
    Enigu absolutan vojon se vi rulas Forgejon kiel servo. enable_captcha = Ŝalti dumregistriĝan teston de homeco -require_db_desc = Forgejo bezonas kiel datumbazo MySQL, PostgreSQL, MSSQL, SQLite3, aŭ TiDB (MySQL komunikformo). +require_db_desc = Forgejo bezonas kiel datumbazo MySQL, PostgreSQL, SQLite3, aŭ TiDB (MySQL komunikformo). smtp_from = Sendu retleterojn kiel general_title = Ĝeneralaj agordoj password = Pasvorto diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index 3f189a9c46..7727e930a6 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -210,7 +210,7 @@ license_desc=¡Está todo en < documentación antes de realizar cambios en la configuración. -require_db_desc=Forgejo requiere una base de datos MySQL, PostgreSQL, MSSQL, SQLite3 o TiDB (usar el protocolo MySQL). +require_db_desc=Forgejo requiere una base de datos MySQL, PostgreSQL, SQLite3 o TiDB (usar el protocolo MySQL). db_title=Configuración de base de datos db_type=Tipo de base de datos host=Servidor diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 1bb1cd5da6..1560075d6b 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -170,7 +170,7 @@ license_desc=Mene osoitteeseen ohjeet ennen minkään asetuksen muuttamista. -require_db_desc=Forgejo tarvitsee toimiakseen MySQL, PostgreSQL, MSSQL, SQLite3 tai TiDB (MySQL protokolla) tietokannan. +require_db_desc=Forgejo tarvitsee toimiakseen MySQL, PostgreSQL, SQLite3 tai TiDB (MySQL protokolla) tietokannan. db_title=Tietokanta asetukset db_type=Tietokanta tyyppi host=Isäntä diff --git a/options/locale/locale_fil.ini b/options/locale/locale_fil.ini index 881fd2c4d4..45430fe366 100644 --- a/options/locale/locale_fil.ini +++ b/options/locale/locale_fil.ini @@ -212,7 +212,7 @@ reinstall_error = Sinusubukan mong mag-install sa umiiral na Forgejo database install = Pag-install title = Paunang pagsasaayos docker_helper = Kapag tinatakbo mo ang Forgejo sa loob ng Docker, mangyaring basahin ang dokumentasyon bago baguhin ang anumang mga setting. -require_db_desc = Kinakailangan ng Forgejo ang MySQL, PostgreSQL, MSSQL, SQLite3 o TiDB (MySQL protocol). +require_db_desc = Kinakailangan ng Forgejo ang MySQL, PostgreSQL, SQLite3 o TiDB (MySQL protocol). db_title = Mga setting ng database db_type = Uri ng database host = Host diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 9fe60d9ff4..4ffccbca44 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -219,7 +219,7 @@ license_desc=Toutes les sources sont sur documentation avant de modifier les paramètres. -require_db_desc=Forgejo nécessite MySQL, PostgreSQL, MSSQL, SQLite3 ou TiDB (avec le protocole MySQL). +require_db_desc=Forgejo nécessite MySQL, PostgreSQL, SQLite3 ou TiDB (avec le protocole MySQL). db_title=Paramètres de la base de données db_type=Type de base de données host=Hôte @@ -3422,14 +3422,12 @@ self_check.database_collation_mismatch = La base de donnée devrait utiliser la self_check.database_collation_case_insensitive = La base de donnée utilise la collation %s qui n'est pas sensible à la casse. Bien que Forgejo puisse fonctionner de cette façon, il est possible que certains cas limite d'utilisation de la casse ne fonctionne pas comme attendu. self_check.database_inconsistent_collation_columns = La base de donnée utilise la collation %s, mais ces colonnes utilisent des collations incohérentes. Cela peut causer des problèmes inattendus. self_check.database_fix_mysql = Les utilisateurs de MySQL/MariaDB peuvent utiliser la commande "forgejo doctor convert" pour corriger les problèmes de collation, ou bien manuellement avec la commande SQL "ALTER ... COLLATE ...". -self_check.database_fix_mssql = Les utilisateurs de MSSQL sont pour l'instant contraint d'utiliser la commande SQL "ALTER ... COLLATE ..." pour corriger ce problème. self_check.no_problem_found=Aucun problème trouvé pour l’instant. self_check.database_collation_mismatch=Exige que la base de données utilise la collation %s. self_check.database_collation_case_insensitive=La base de données utilise la collation %s, insensible à la casse. Bien que Forgejo soit compatible, il peut y avoir quelques rares cas qui ne fonctionnent pas comme prévu. self_check.database_inconsistent_collation_columns=La base de données utilise la collation %s, mais ces colonnes utilisent des collations différentes. Cela peut causer des problèmes imprévus. self_check.database_fix_mysql=Pour les utilisateurs de MySQL ou MariaDB, vous pouvez utiliser la commande « forgejo doctor convert » dans un terminal ou exécuter une requête du type « ALTER … COLLATE ... » pour résoudre les problèmes de collation. -self_check.database_fix_mssql=Pour les utilisateurs de MSSQL, vous ne pouvez résoudre le problème qu’en exécutant une requête SQL du type « ALTER … COLLATE … ». config_settings = Paramètres config_summary = Résumé auths.tips.gmail_settings = Paramètres Gmail : @@ -3847,4 +3845,4 @@ b = o [markup] filepreview.line = Ligne %[1]d dans %[2]s filepreview.lines = Lignes %[1]d jusqu'à %[2]d dans %[3]s -filepreview.truncated = L'aperçu a été tronqué +filepreview.truncated = L'aperçu a été tronqué \ No newline at end of file diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini index 3948015902..e448ee4568 100644 --- a/options/locale/locale_is-IS.ini +++ b/options/locale/locale_is-IS.ini @@ -141,7 +141,7 @@ license_desc=Sæktu leiðbeiningaritið áður en þú breytir stillingum. -require_db_desc=Forgejo krefst MySQL, PostgreSQL, MSSQL, SQLite3 eða TiDB (MySQL samskiptareglur). +require_db_desc=Forgejo krefst MySQL, PostgreSQL, SQLite3 eða TiDB (MySQL samskiptareglur). db_title=Gagnagrunnsstillingar db_type=Tegund Gagnagrunns host=Hýsill diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index caa4569164..2af6019eae 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -217,7 +217,7 @@ install_desc = Semplicemente la documentazione prima di cambiare qualsiasi impostazione. -require_db_desc=Forgejo requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol). +require_db_desc=Forgejo requires MySQL, PostgreSQL, SQLite3 or TiDB (MySQL protocol). db_title=Impostazioni database db_type=Tipo di database host=Host @@ -3404,7 +3404,6 @@ monitor.queue.settings.remove_all_items_done = Tutti gli elementi in coda sono s self_check.database_collation_mismatch = Pretendi che la base di dati usi la collazione: %s self_check.database_fix_mysql = Per utenti MySQL/MariaDB, potresti usare il comando "gitea doctor convert" per risolvere problemi di collazione, o potresti risolvere il problema manualmente tramite SQL con "ALTER ... COLLATE ...". self_check.database_collation_case_insensitive = La base di dati sta usando la collazione %s, che è una collazione insensibile. Nonostante Forgejo potrebbe lavorarci, ci potrebbero essere rari casi che non vanno come previsto. -self_check.database_fix_mssql = Gli utenti MSSQL possono provare a risolvere il problema tramite SQL con "ALTER ... COLLATE ..." manualmente, per il momento. auths.tip.gitlab_new = Registra una nuova applicazione su https://gitlab.com/-/profile/applications config_summary = Riepilogo config.open_with_editor_app_help = L'editor delle opzioni "Apri con" per il menu di clone. Se lasciato vuoto, verranno usati i default. Espandi per vedere i default. @@ -3812,4 +3811,4 @@ b = B [markup] filepreview.lines = Linee da %[1]d a %[2]d in %[3]s filepreview.truncated = L'anteprima è stata troncata -filepreview.line = Linea %[1]d in %[2]s +filepreview.line = Linea %[1]d in %[2]s \ No newline at end of file diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 35c78fd3c1..d6137f39fe 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -217,7 +217,7 @@ license_desc=Go get ドキュメントを読んでください。 -require_db_desc=Forgejoには、MySQL、PostgreSQL、MSSQL、SQLite3、またはTiDB(MySQL プロトコル) が必要です。 +require_db_desc=Forgejoには、MySQL、PostgreSQL、SQLite3、またはTiDB(MySQL プロトコル) が必要です。 db_title=データベース設定 db_type=データベースのタイプ host=ホスト diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 53e168bff1..e2ab79e3f8 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -197,7 +197,7 @@ license_desc=Iegūsti vadlīninas pirms maināt iestatījumus. -require_db_desc=Forgejo nepieciešams MySQL, PostgreSQL, MSSQL, SQLite3 vai TiDB (izmantojot MySQL protokolu). +require_db_desc=Forgejo nepieciešams MySQL, PostgreSQL, SQLite3 vai TiDB (izmantojot MySQL protokolu). db_title=Datu bāzes iestatījumi db_type=Datu bāzes veids host=Resursdators diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index f380a1129d..bdb6cd50d6 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -217,7 +217,7 @@ install_desc = Draai gewoon documentatie voordat je een instelling aanpast. -require_db_desc=Forgejo vereist MySQL, PostgreSQL, MSSQL, SQLite3 of TiDB (MySQL protocol). +require_db_desc=Forgejo vereist MySQL, PostgreSQL, SQLite3 of TiDB (MySQL protocol). db_title=Database-instellingen db_type=Database-type host=Server @@ -3333,7 +3333,6 @@ dashboard.sync_repo_branches = Synchroniseren gemiste branches van git data naar monitor.processes_count = %d Processen monitor.process.children = Kinderen self_check.database_inconsistent_collation_columns = Database gebruikt collatie %s, maar deze kolommen gebruiken onjuiste collaties. Dit kan onverwachte problemen veroorzaken. -self_check.database_fix_mssql = Voor MSSQL gebruikers kan je het probleem alleen oplossen door "ALTER ... COLLATE ..." SQL's handmatig op te lossen. monitor.stacktrace = Stacktrace monitor.download_diagnosis_report = Diagnoserapport downloaden self_check.database_collation_case_insensitive = Database gebruikt collatie %s, wat een ongevoelige collatie is. Hoewel Forgejo ermee kan werken, kunnen er enkele zeldzame gevallen zijn die niet werken zoals verwacht. diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index 6774dc2167..38109eb2c3 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -212,7 +212,7 @@ install_desc = Po prostu dokumentację przed wprowadzeniem jakichkolwiek zmian. -require_db_desc=Forgejo wymaga MySQL, PostgreSQL, MSSQL, SQLite3 lub TiDB (protokół MySQL). +require_db_desc=Forgejo wymaga MySQL, PostgreSQL, SQLite3 lub TiDB (protokół MySQL). db_title=Ustawienia bazy danych db_type=Typ bazy danych host=Serwer diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index bc0edd51fd..ce752bbddc 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -210,7 +210,7 @@ install_desc = Apenas documentação cuidadosamente antes de alterar qualquer coisa nesta página. -require_db_desc=Forgejo requer MySQL, PostgreSQL, MSSQL, SQLite3 ou TiDB (protocolo MySQL). +require_db_desc=Forgejo requer MySQL, PostgreSQL, SQLite3 ou TiDB (protocolo MySQL). db_title=Configurações de banco de dados db_type=Tipo de banco de dados host=Servidor diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index ced06fb5f7..c7fc74b234 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -218,7 +218,7 @@ license_desc=Vá buscá-lo em documentação antes de alterar quaisquer configurações. -require_db_desc=Forgejo requer MySQL, PostgreSQL, MSSQL, SQLite3 ou TiDB (protocolo MySQL). +require_db_desc=Forgejo requer MySQL, PostgreSQL, SQLite3 ou TiDB (protocolo MySQL). db_title=Configurações da base de dados db_type=Tipo de base de dados host=Servidor @@ -3405,7 +3405,6 @@ self_check.database_collation_mismatch=Supor que a base de dados usa a colação self_check.database_collation_case_insensitive=A base de dados está a usar a colação %s, que é insensível à diferença entre maiúsculas e minúsculas. Embora o Gitea possa trabalhar com ela, pode haver alguns casos raros que não funcionem como esperado. self_check.database_inconsistent_collation_columns=A base de dados está a usar a colação %s, mas estas colunas estão a usar colações diferentes. Isso poderá causar alguns problemas inesperados. self_check.database_fix_mysql=Para utilizadores do MySQL/MariaDB, pode usar o comando "gitea doctor convert" para resolver os problemas de colação. Também pode resolver o problema com comandos SQL "ALTER ... COLLATE ..." aplicados manualmente. -self_check.database_fix_mssql=Para utilizadores do MSSQL só pode resolver o problema aplicando comandos SQL "ALTER ... COLLATE ..." manualmente, por enquanto. config_summary = Resumo auths.tips.gmail_settings = Configurações do Gmail: config_settings = Configurações @@ -3814,4 +3813,4 @@ b = B [markup] filepreview.lines = Linhas %[1]d até %[2]d em %[3]s filepreview.line = Linha %[1]d em %[2]s -filepreview.truncated = A previsão foi truncada +filepreview.truncated = A previsão foi truncada \ No newline at end of file diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 14dd189dfa..7ba9719a05 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -218,7 +218,7 @@ license_desc=Всё это на документацией, прежде чем изменять любые настройки. -require_db_desc=Forgejo требуется MySQL, PostgreSQL, MSSQL, SQLite3 или TiDB (по протоколу MySQL). +require_db_desc=Forgejo требуется MySQL, PostgreSQL, SQLite3 или TiDB (по протоколу MySQL). db_title=Настройки базы данных db_type=Тип базы данных host=Хост @@ -3384,7 +3384,6 @@ notices.delete_success=Уведомления системы были удале self_check.no_problem_found = Пока проблем не обнаружено. auths.tip.gitea = Зарегистрируйте новое приложение OAuth2. Доступна инструкция: https://forgejo.org/docs/latest/user/oauth2-provider auths.tips.oauth2.general.tip = При регистрации нового приложения OAuth2 ссылка обратного перенаправления должна быть: -self_check.database_fix_mssql = В настоящий момент пользователи MSSQL могут исправить проблемы с сопоставлением только ручным прописыванием "ALTER ... COLLATE ..." в SQL. self_check.database_fix_mysql = Пользователи MySQL и MariaDB могут исправить проблемы с сопоставлением командой "gitea doctor convert". Также можно вручную вписать "ALTER ... COLLATE ..." в SQL. dashboard.cleanup_actions = Очистить устаревшие журналы и артефакты Действий dashboard.sync_repo_branches = Синхронизировать ветки из Git в базу данных diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini index 39fa7fef2f..2167d116d0 100644 --- a/options/locale/locale_sk-SK.ini +++ b/options/locale/locale_sk-SK.ini @@ -196,7 +196,7 @@ license_desc=Získajte dokumentáciu pred zmenou akýchkoľvek nastavení. -require_db_desc=Forgejo vyžaduje MySQL, PostgreSQL, MSSQL, SQLite3 alebo TiDB (MySQL protokol). +require_db_desc=Forgejo vyžaduje MySQL, PostgreSQL, SQLite3 alebo TiDB (MySQL protokol). db_title=Nastavenie databázy db_type=Typ databázy host=Host diff --git a/options/locale/locale_sl.ini b/options/locale/locale_sl.ini index c3b1dc6ba5..ed6153b70f 100644 --- a/options/locale/locale_sl.ini +++ b/options/locale/locale_sl.ini @@ -133,7 +133,7 @@ pull_requests = Zahteve za umik [install] reinstall_confirm_check_3 = Potrjujete, da ste popolnoma prepričani, da se ta program Forgejo izvaja s pravilno lokacijo app.ini, in da ste prepričani, da ga morate znova namestiti. Potrjujete, da se zavedate zgoraj navedenih tveganj. -require_db_desc = Forgejo zahteva MySQL, PostgreSQL, MSSQL, SQLite3 ali TiDB (protokol MySQL). +require_db_desc = Forgejo zahteva MySQL, PostgreSQL, SQLite3 ali TiDB (protokol MySQL). password_algorithm_helper = Nastavite algoritem za stiskanje gesla. Algoritmi imajo različne zahteve in moč. Algoritem argon2 je precej varen, vendar porabi veliko pomnilnika in je lahko neprimeren za majhne sisteme. reinstall_confirm_message = Ponovna namestitev z obstoječo zbirko podatkov Forgejo lahko povzroči več težav. V večini primerov morate za zagon programa Forgejo uporabiti obstoječi "app.ini". Če veste, kaj počnete, potrdite naslednje: err_admin_name_is_reserved = Administrator Uporabniško ime je neveljavno, uporabniško ime je rezervirano diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index e9da230fea..c2c5385421 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -212,7 +212,7 @@ license_desc=Gidin ve belgeleri okuyun. -require_db_desc=Forgejo MySQL, PostgreSQL, MSSQL, SQLite3 veya TiDB (MySQL protokolü) gerektirir. +require_db_desc=Forgejo MySQL, PostgreSQL, SQLite3 veya TiDB (MySQL protokolü) gerektirir. db_title=Veritabanı Ayarları db_type=Veritabanı Türü host=Sunucu diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 05447378e2..dab4287f06 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -218,7 +218,7 @@ license_desc=所有的代码都开源在 官方文档 后再对本页面进行填写。 -require_db_desc=Forgejo 需要使用 MySQL、PostgreSQL、MSSQL、SQLite3 或 TiDB(MySQL 协议)等数据库。 +require_db_desc=Forgejo 需要使用 MySQL、PostgreSQL、SQLite3 或 TiDB(MySQL 协议)等数据库。 db_title=数据库设置 db_type=数据库类型 host=数据库主机 @@ -3410,14 +3410,12 @@ self_check.database_collation_mismatch = 期望数据库使用排序规则:%s self_check.database_collation_case_insensitive = 数据库正在使用 %s 排序规则,这是一种不敏感的排序规则。 尽管 Forgejo 可以使用它,但在极少数情况下可能无法按照预期工作。 self_check.database_inconsistent_collation_columns = 数据库正在使用 %s 排序规则,但与这些列使用的排序规则不匹配。 这可能会导致一些意想不到的问题。 self_check.database_fix_mysql = 对于 MySQL/MariaDB 用户,您可以使用 "gitea doctor convert" 命令来修复排序规则问题,也可以通过SQL命令 "ALTER ... COLLATE ..." 来手动修复问题。 -self_check.database_fix_mssql = 对于 MSSQL 用户,目前您只能通过SQL命令 "ALTER ... COLLATE ..." 来手动修复问题。 self_check.no_problem_found=尚未发现问题。 self_check.database_collation_mismatch=期望数据库使用的校验方式:%s self_check.database_collation_case_insensitive=数据库正在使用一个校验 %s, 这是一个不敏感的校验. 虽然Gitea可以与它合作,但可能有一些罕见的情况不如预期的那样起作用。 self_check.database_inconsistent_collation_columns=数据库正在使用%s的排序规则,但是这些列使用了不匹配的排序规则。这可能会造成一些意外问题。 self_check.database_fix_mysql=对于MySQL/MariaDB用户,您可以使用“gitea doctor convert”命令来解决校验问题。 或者您也可以通过 "ALTER ... COLLATE ..." 这样的SQL 来手动解决这个问题。 -self_check.database_fix_mssql=对于MSSQL用户,您现在只能通过"ALTER ... COLLATE ..."SQLs手动解决这个问题。 auths.tips.gmail_settings = Gmail 设置: auths.tip.gitlab_new = 在 https://gitlab.com/-/profile/applications 上注册新应用 config_settings = 设置 @@ -3827,4 +3825,4 @@ mib = MiB [markup] filepreview.line = %[2]s 中的第 %[1]d 行 filepreview.lines = %[3]s 中的第 %[1]d 到 %[2]d 行 -filepreview.truncated = 预览已被截断 +filepreview.truncated = 预览已被截断 \ No newline at end of file diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 82109204ea..e7334dbd16 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -217,7 +217,7 @@ license_desc=取得 安裝指南再來調整設定。 -require_db_desc=Forgejo 需要 MySQL、PostgreSQL、SQLite3、MSSQL、TiDB (MySQL 協定) 等其中一項。 +require_db_desc=Forgejo 需要 MySQL、PostgreSQL、SQLite3、TiDB (MySQL 協定) 等其中一項。 db_title=資料庫設定 db_type=資料庫類型 host=主機 diff --git a/package-lock.json b/package-lock.json index dff43d2da7..5781507561 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,11 +5,10 @@ "packages": { "": { "dependencies": { - "@citation-js/core": "0.7.9", - "@citation-js/plugin-bibtex": "0.7.9", - "@citation-js/plugin-csl": "0.7.9", + "@citation-js/core": "0.7.11", + "@citation-js/plugin-bibtex": "0.7.11", + "@citation-js/plugin-csl": "0.7.11", "@citation-js/plugin-software-formats": "0.6.1", - "@claviska/jquery-minicolors": "2.3.6", "@github/markdown-toolbar-element": "2.2.3", "@github/relative-time-element": "4.4.0", "@github/text-expander-element": "2.6.1", @@ -21,42 +20,43 @@ "chart.js": "4.4.2", "chartjs-adapter-dayjs-4": "1.0.4", "chartjs-plugin-zoom": "2.0.1", - "clippie": "4.0.7", - "css-loader": "6.10.0", - "dayjs": "1.11.10", + "clippie": "4.1.1", + "css-loader": "7.0.0", + "dayjs": "1.11.11", "dropzone": "6.0.0-beta.2", "easymde": "2.18.0", "esbuild-loader": "4.1.0", "escape-goat": "4.0.0", "fast-glob": "3.3.2", - "htmx.org": "1.9.11", + "htmx.org": "1.9.12", "idiomorph": "0.3.0", "jquery": "3.7.1", "katex": "0.16.10", "license-checker-webpack-plugin": "0.2.1", "mermaid": "10.9.0", - "mini-css-extract-plugin": "2.8.1", - "minimatch": "9.0.3", + "mini-css-extract-plugin": "2.9.0", + "minimatch": "9.0.4", "monaco-editor": "0.47.0", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.0", "postcss": "8.4.38", "postcss-loader": "8.1.1", - "postcss-nesting": "12.1.0", + "postcss-nesting": "12.1.2", "pretty-ms": "9.0.0", "sortablejs": "1.15.2", - "swagger-ui-dist": "5.12.0", - "tailwindcss": "3.4.1", - "temporal-polyfill": "0.2.3", + "swagger-ui-dist": "5.17.2", + "tailwindcss": "3.4.3", + "temporal-polyfill": "0.2.4", "throttle-debounce": "5.0.0", "tinycolor2": "1.6.0", "tippy.js": "6.3.7", "toastify-js": "1.12.0", "tributejs": "5.1.3", "uint8-to-base64": "0.2.0", - "vue": "3.4.21", + "vanilla-colorful": "0.7.2", + "vue": "3.4.26", "vue-bar-graph": "2.0.0", - "vue-chartjs": "5.3.0", + "vue-chartjs": "5.3.1", "vue-loader": "17.4.2", "vue3-calendar-heatmap": "2.0.5", "webpack": "5.91.0", @@ -64,11 +64,11 @@ "wrap-ansi": "9.0.0" }, "devDependencies": { - "@eslint-community/eslint-plugin-eslint-comments": "4.1.0", - "@playwright/test": "1.42.1", - "@stoplight/spectral-cli": "6.11.0", - "@stylistic/eslint-plugin-js": "1.7.0", - "@stylistic/stylelint-plugin": "2.1.0", + "@eslint-community/eslint-plugin-eslint-comments": "4.3.0", + "@playwright/test": "1.43.0", + "@stoplight/spectral-cli": "6.11.1", + "@stylistic/eslint-plugin-js": "1.8.0", + "@stylistic/stylelint-plugin": "2.1.2", "@vitejs/plugin-vue": "5.0.4", "@vue/test-utils": "2.4.5", "eslint": "8.57.0", @@ -78,24 +78,25 @@ "eslint-plugin-jquery": "1.5.1", "eslint-plugin-no-jquery": "2.7.0", "eslint-plugin-no-use-extend-native": "0.5.0", - "eslint-plugin-regexp": "2.4.0", - "eslint-plugin-sonarjs": "0.24.0", - "eslint-plugin-unicorn": "51.0.1", - "eslint-plugin-vitest": "0.4.0", + "eslint-plugin-regexp": "2.5.0", + "eslint-plugin-sonarjs": "0.25.1", + "eslint-plugin-unicorn": "52.0.0", + "eslint-plugin-vitest": "0.5.4", "eslint-plugin-vitest-globals": "1.5.0", - "eslint-plugin-vue": "9.24.0", + "eslint-plugin-vue": "9.25.0", "eslint-plugin-vue-scoped-css": "2.8.0", - "eslint-plugin-wc": "2.0.4", - "happy-dom": "14.3.10", - "markdownlint-cli": "0.39.0", + "eslint-plugin-wc": "2.1.0", + "happy-dom": "14.7.1", + "markdownlint-cli": "0.40.0", "postcss-html": "1.6.0", - "stylelint": "16.3.0", + "stylelint": "16.4.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.4", + "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.2.0", - "updates": "16.0.0", - "vite-string-plugin": "1.1.5", - "vitest": "1.4.0" + "updates": "16.0.1", + "vite-string-plugin": "1.3.1", + "vitest": "1.5.3" }, "engines": { "node": ">= 18.0.0" @@ -234,9 +235,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", - "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", "bin": { "parser": "bin/babel-parser.js" }, @@ -245,9 +246,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", - "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -261,9 +262,9 @@ "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==" }, "node_modules/@citation-js/core": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/@citation-js/core/-/core-0.7.9.tgz", - "integrity": "sha512-fSbkB32JayDChZnAYC/kB+sWHRvxxL7ibVetyBOyzOc+5aCnjb6UVsbcfhnkOIEyAMoRRvWDyFmakEoTtA5ttQ==", + "version": "0.7.11", + "resolved": "https://registry.npmjs.org/@citation-js/core/-/core-0.7.11.tgz", + "integrity": "sha512-evQtyzeW+Gbmq+xWciIq9sbcvXXDbm8q32orD/HDd5ay6RQFKoW/BKxBLp+Nmpxgspb9sxTJn3iFK7+jxOTNTw==", "dependencies": { "@citation-js/date": "^0.5.0", "@citation-js/name": "^0.4.2", @@ -291,9 +292,9 @@ } }, "node_modules/@citation-js/plugin-bibtex": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibtex/-/plugin-bibtex-0.7.9.tgz", - "integrity": "sha512-gIJpCd6vmmTOcRfDrSOjtoNhw2Mi94UwFxmgJ7GwkXyTYcNheW5VlMMo1tlqjakJGARQ0eOsKcI57gSPqJSS2g==", + "version": "0.7.11", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibtex/-/plugin-bibtex-0.7.11.tgz", + "integrity": "sha512-G4vEmLjrQUxgBIp3ffWN5dDOlwjPsrRSi/uTyxDJuFgKBD8GR1eO7Y/ZcePNAOHMqUxG7lxhhBbZJwcJZNVHYw==", "dependencies": { "@citation-js/date": "^0.5.0", "@citation-js/name": "^0.4.2", @@ -319,9 +320,9 @@ } }, "node_modules/@citation-js/plugin-csl": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-csl/-/plugin-csl-0.7.9.tgz", - "integrity": "sha512-mbD7CnUiPOuVnjeJwo+d0RGUcY0PE8n01gHyjq0qpTeS42EGmQ9+LzqfsTUVWWBndTwc6zLRuIF1qFAUHKE4oA==", + "version": "0.7.11", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-csl/-/plugin-csl-0.7.11.tgz", + "integrity": "sha512-4OGZ9wHZDfpgiPU2cOXWGuKt7P+ndGWAeLG95nOG+DXe5U+f9EEZTXfaM4C99x8Ri+g6JklR96A3kuYZxYLllg==", "dependencies": { "@citation-js/date": "^0.5.0", "citeproc": "^2.4.6" @@ -395,14 +396,6 @@ "node": ">=14.0.0" } }, - "node_modules/@claviska/jquery-minicolors": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@claviska/jquery-minicolors/-/jquery-minicolors-2.3.6.tgz", - "integrity": "sha512-8Ro6D4GCrmOl41+6w4NFhEOpx8vjxwVRI69bulXsFDt49uVRKhLU5TnzEV7AmOJrylkVq+ugnYNMiGHBieeKUQ==", - "peerDependencies": { - "jquery": ">= 1.7.x" - } - }, "node_modules/@csstools/css-parser-algorithms": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.1.tgz", @@ -489,9 +482,9 @@ } }, "node_modules/@csstools/selector-specificity": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.2.tgz", - "integrity": "sha512-RpHaZ1h9LE7aALeQXmXrJkRG84ZxIsctEN2biEUmFyKpzFM3zZ35eUMcIzZFsw/2olQE6v69+esEqU2f1MKycg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.3.tgz", + "integrity": "sha512-KEPNw4+WW5AVEIyzC80rTbWEUatTW2lXpN8+8ILC8PiPeWPjwUzrPZDIOZ2wwqDmeqOYTdSGyL3+vE5GC3FB3Q==", "funding": [ { "type": "github", @@ -873,9 +866,9 @@ } }, "node_modules/@eslint-community/eslint-plugin-eslint-comments": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.1.0.tgz", - "integrity": "sha512-B2mwipifrBS5E00vN8vME68laPMZ0h3sNGOEDj5g9iUN9k5EU99Omq0Nc325eKNoFFDnDtiHp3DqIjO+1bstag==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.3.0.tgz", + "integrity": "sha512-6e93KtgsndNkvwCCa07LOQJSwzzLLxwrFll3+huyFoiiQXWG0KBcmo0Q1bVgYQQDLfWOOZl2VPBsXqZL6vHIBQ==", "dev": true, "dependencies": { "escape-string-regexp": "^4.0.0", @@ -885,7 +878,7 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, "node_modules/@eslint-community/eslint-utils": { @@ -1067,9 +1060,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "node_modules/@isaacs/cliui": { @@ -1358,12 +1351,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.42.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz", - "integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==", + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.0.tgz", + "integrity": "sha512-Ebw0+MCqoYflop7wVKj711ccbNlrwTBCtjY5rlbiY9kHL2bCYxq+qltK6uPsVBGGAOb033H2VO0YobcQVxoW7Q==", "dev": true, "dependencies": { - "playwright": "1.42.1" + "playwright": "1.43.0" }, "bin": { "playwright": "cli.js" @@ -1434,9 +1427,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", - "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz", + "integrity": "sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==", "cpu": [ "arm" ], @@ -1447,9 +1440,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", - "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz", + "integrity": "sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==", "cpu": [ "arm64" ], @@ -1460,9 +1453,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", - "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz", + "integrity": "sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==", "cpu": [ "arm64" ], @@ -1473,9 +1466,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", - "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz", + "integrity": "sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==", "cpu": [ "x64" ], @@ -1486,9 +1479,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", - "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz", + "integrity": "sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==", "cpu": [ "arm" ], @@ -1499,9 +1492,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", - "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz", + "integrity": "sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==", "cpu": [ "arm64" ], @@ -1512,9 +1505,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", - "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz", + "integrity": "sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==", "cpu": [ "arm64" ], @@ -1524,10 +1517,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz", + "integrity": "sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==", + "cpu": [ + "ppc64le" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", - "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz", + "integrity": "sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==", "cpu": [ "riscv64" ], @@ -1537,10 +1543,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz", + "integrity": "sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", - "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz", + "integrity": "sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==", "cpu": [ "x64" ], @@ -1551,9 +1570,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", - "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz", + "integrity": "sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==", "cpu": [ "x64" ], @@ -1564,9 +1583,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", - "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz", + "integrity": "sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==", "cpu": [ "arm64" ], @@ -1577,9 +1596,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", - "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz", + "integrity": "sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==", "cpu": [ "ia32" ], @@ -1590,9 +1609,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", - "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz", + "integrity": "sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==", "cpu": [ "x64" ], @@ -1700,9 +1719,9 @@ } }, "node_modules/@stoplight/spectral-cli": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-cli/-/spectral-cli-6.11.0.tgz", - "integrity": "sha512-IURDN47BPIf3q4ZyUPujGpBzuHWFE5yT34w9rTJ1GKA4SgdscEdQO9KoTjOPT4G4cvDlEV3bNxwQ3uRm7+wRlA==", + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-cli/-/spectral-cli-6.11.1.tgz", + "integrity": "sha512-1zqsQ0TOuVSnxxZ9mHBfC0IygV6ex7nAY6Mp59mLmw5fW103U9yPVK5ZcX9ZngCmr3PdteAnMDUIIaoDGso6nA==", "dev": true, "dependencies": { "@stoplight/json": "~3.21.0", @@ -1723,7 +1742,7 @@ "pony-cause": "^1.0.0", "stacktracey": "^2.1.7", "tslib": "^2.3.0", - "yargs": "17.3.1" + "yargs": "~17.7.2" }, "bin": { "spectral": "dist/index.js" @@ -1887,20 +1906,33 @@ } }, "node_modules/@stoplight/spectral-parsers": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-parsers/-/spectral-parsers-1.0.3.tgz", - "integrity": "sha512-J0KW5Rh5cHWnJQ3yN+cr/ijNFVirPSR0pkQbdrNX30VboEl083UEDrQ3yov9kjLVIWEk9t9kKE7Eo3QT/k4JLA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-parsers/-/spectral-parsers-1.0.4.tgz", + "integrity": "sha512-nCTVvtX6q71M8o5Uvv9kxU31Gk1TRmgD6/k8HBhdCmKG6FWcwgjiZouA/R3xHLn/VwTI/9k8SdG5Mkdy0RBqbQ==", "dev": true, "dependencies": { "@stoplight/json": "~3.21.0", - "@stoplight/types": "^13.6.0", - "@stoplight/yaml": "~4.2.3", + "@stoplight/types": "^14.1.1", + "@stoplight/yaml": "~4.3.0", "tslib": "^2.3.1" }, "engines": { "node": "^12.20 || >=14.13" } }, + "node_modules/@stoplight/spectral-parsers/node_modules/@stoplight/types": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-14.1.1.tgz", + "integrity": "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + } + }, "node_modules/@stoplight/spectral-ref-resolver": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@stoplight/spectral-ref-resolver/-/spectral-ref-resolver-1.0.4.tgz", @@ -1969,6 +2001,27 @@ "node": ">=12" } }, + "node_modules/@stoplight/spectral-ruleset-migrator/node_modules/@stoplight/yaml": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@stoplight/yaml/-/yaml-4.2.3.tgz", + "integrity": "sha512-Mx01wjRAR9C7yLMUyYFTfbUf5DimEpHMkRDQ1PKLe9dfNILbgdxyrncsOXM3vCpsQ1Hfj4bPiGl+u4u6e9Akqw==", + "dev": true, + "dependencies": { + "@stoplight/ordered-object-literal": "^1.0.1", + "@stoplight/types": "^13.0.0", + "@stoplight/yaml-ast-parser": "0.0.48", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=10.8" + } + }, + "node_modules/@stoplight/spectral-ruleset-migrator/node_modules/@stoplight/yaml-ast-parser": { + "version": "0.0.48", + "resolved": "https://registry.npmjs.org/@stoplight/yaml-ast-parser/-/yaml-ast-parser-0.0.48.tgz", + "integrity": "sha512-sV+51I7WYnLJnKPn2EMWgS4EUfoP4iWEbrWwbXsj0MZCB/xOK8j6+C9fntIdOM50kpx45ZLC3s6kwKivWuqvyg==", + "dev": true + }, "node_modules/@stoplight/spectral-rulesets": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/@stoplight/spectral-rulesets/-/spectral-rulesets-1.18.1.tgz", @@ -2039,14 +2092,14 @@ } }, "node_modules/@stoplight/yaml": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@stoplight/yaml/-/yaml-4.2.3.tgz", - "integrity": "sha512-Mx01wjRAR9C7yLMUyYFTfbUf5DimEpHMkRDQ1PKLe9dfNILbgdxyrncsOXM3vCpsQ1Hfj4bPiGl+u4u6e9Akqw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@stoplight/yaml/-/yaml-4.3.0.tgz", + "integrity": "sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w==", "dev": true, "dependencies": { - "@stoplight/ordered-object-literal": "^1.0.1", - "@stoplight/types": "^13.0.0", - "@stoplight/yaml-ast-parser": "0.0.48", + "@stoplight/ordered-object-literal": "^1.0.5", + "@stoplight/types": "^14.1.1", + "@stoplight/yaml-ast-parser": "0.0.50", "tslib": "^2.2.0" }, "engines": { @@ -2054,18 +2107,31 @@ } }, "node_modules/@stoplight/yaml-ast-parser": { - "version": "0.0.48", - "resolved": "https://registry.npmjs.org/@stoplight/yaml-ast-parser/-/yaml-ast-parser-0.0.48.tgz", - "integrity": "sha512-sV+51I7WYnLJnKPn2EMWgS4EUfoP4iWEbrWwbXsj0MZCB/xOK8j6+C9fntIdOM50kpx45ZLC3s6kwKivWuqvyg==", + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@stoplight/yaml-ast-parser/-/yaml-ast-parser-0.0.50.tgz", + "integrity": "sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ==", "dev": true }, - "node_modules/@stylistic/eslint-plugin-js": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.7.0.tgz", - "integrity": "sha512-PN6On/+or63FGnhhMKSQfYcWutRlzOiYlVdLM6yN7lquoBTqUJHYnl4TA4MHwiAt46X5gRxDr1+xPZ1lOLcL+Q==", + "node_modules/@stoplight/yaml/node_modules/@stoplight/types": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-14.1.1.tgz", + "integrity": "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g==", "dev": true, "dependencies": { - "@types/eslint": "^8.56.2", + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + } + }, + "node_modules/@stylistic/eslint-plugin-js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.8.0.tgz", + "integrity": "sha512-jdvnzt+pZPg8TfclZlTZPiUbbima93ylvQ+wNgHLNmup3obY6heQvgewSu9i2CfS61BnRByv+F9fxQLPoNeHag==", + "dev": true, + "dependencies": { + "@types/eslint": "^8.56.10", "acorn": "^8.11.3", "escape-string-regexp": "^4.0.0", "eslint-visitor-keys": "^3.4.3", @@ -2079,19 +2145,19 @@ } }, "node_modules/@stylistic/stylelint-plugin": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-2.1.0.tgz", - "integrity": "sha512-mUZEW9uImHSbXeyzbFmHb8WPBv56UTaEnWL/3dGdAiJ54C+8GTfDwDVdI6gbqT9wV7zynkPu7tCXc5746H9mZQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-2.1.2.tgz", + "integrity": "sha512-JsSqu0Y3vsX+PBl+DwULxC0cIv9C1yIcq1MXkx7pBOGtTqU26a75I8MPYMiEYvrsXgsKLi65xVgy1iLVSZquJA==", "dev": true, "dependencies": { - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", - "@csstools/media-query-list-parser": "^2.1.7", + "@csstools/css-parser-algorithms": "^2.6.1", + "@csstools/css-tokenizer": "^2.2.4", + "@csstools/media-query-list-parser": "^2.1.9", "is-plain-object": "^5.0.0", - "postcss-selector-parser": "^6.0.15", + "postcss-selector-parser": "^6.0.16", "postcss-value-parser": "^4.2.0", "style-search": "^0.1.0", - "stylelint": "^16.2.1" + "stylelint": "^16.4.0" }, "engines": { "node": "^18.12 || >=20.9" @@ -2158,9 +2224,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.56.6", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.6.tgz", - "integrity": "sha512-ymwc+qb1XkjT/gfoQwxIeHZ6ixH23A+tCT2ADSA/DPVKzAjwYkTXBMCQ/f6fe4wEa85Lhp26VPeUxI7wMhAi7A==", + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -2210,9 +2276,9 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "node_modules/@types/node": { - "version": "20.11.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", - "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "version": "20.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", + "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", "dependencies": { "undici-types": "~5.26.4" } @@ -2255,16 +2321,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz", - "integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.5.0.tgz", + "integrity": "sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.4.0", - "@typescript-eslint/type-utils": "7.4.0", - "@typescript-eslint/utils": "7.4.0", - "@typescript-eslint/visitor-keys": "7.4.0", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/type-utils": "7.5.0", + "@typescript-eslint/utils": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -2290,15 +2356,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz", - "integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.5.0.tgz", + "integrity": "sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.4.0", - "@typescript-eslint/types": "7.4.0", - "@typescript-eslint/typescript-estree": "7.4.0", - "@typescript-eslint/visitor-keys": "7.4.0", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/typescript-estree": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4" }, "engines": { @@ -2318,13 +2384,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz", - "integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.5.0.tgz", + "integrity": "sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.4.0", - "@typescript-eslint/visitor-keys": "7.4.0" + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2335,13 +2401,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz", - "integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.5.0.tgz", + "integrity": "sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.4.0", - "@typescript-eslint/utils": "7.4.0", + "@typescript-eslint/typescript-estree": "7.5.0", + "@typescript-eslint/utils": "7.5.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -2362,9 +2428,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz", - "integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.5.0.tgz", + "integrity": "sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2375,13 +2441,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz", - "integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.5.0.tgz", + "integrity": "sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.4.0", - "@typescript-eslint/visitor-keys": "7.4.0", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/visitor-keys": "7.5.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2402,18 +2468,33 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/utils": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz", - "integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.5.0.tgz", + "integrity": "sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.4.0", - "@typescript-eslint/types": "7.4.0", - "@typescript-eslint/typescript-estree": "7.4.0", + "@typescript-eslint/scope-manager": "7.5.0", + "@typescript-eslint/types": "7.5.0", + "@typescript-eslint/typescript-estree": "7.5.0", "semver": "^7.5.4" }, "engines": { @@ -2428,12 +2509,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz", - "integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.5.0.tgz", + "integrity": "sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.4.0", + "@typescript-eslint/types": "7.5.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -2464,13 +2545,13 @@ } }, "node_modules/@vitest/expect": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz", - "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.3.tgz", + "integrity": "sha512-y+waPz31pOFr3rD7vWTbwiLe5+MgsMm40jTZbQE8p8/qXyBX3CQsIXRx9XK12IbY7q/t5a5aM/ckt33b4PxK2g==", "dev": true, "dependencies": { - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", + "@vitest/spy": "1.5.3", + "@vitest/utils": "1.5.3", "chai": "^4.3.10" }, "funding": { @@ -2478,12 +2559,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz", - "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.3.tgz", + "integrity": "sha512-7PlfuReN8692IKQIdCxwir1AOaP5THfNkp0Uc4BKr2na+9lALNit7ub9l3/R7MP8aV61+mHKRGiqEKRIwu6iiQ==", "dev": true, "dependencies": { - "@vitest/utils": "1.4.0", + "@vitest/utils": "1.5.3", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -2519,9 +2600,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz", - "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.3.tgz", + "integrity": "sha512-K3mvIsjyKYBhNIDujMD2gfQEzddLe51nNOAf45yKRt/QFJcUIeTQd2trRvv6M6oCBHNVnZwFWbQ4yj96ibiDsA==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -2533,9 +2614,9 @@ } }, "node_modules/@vitest/snapshot/node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "version": "0.30.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz", + "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -2545,9 +2626,9 @@ } }, "node_modules/@vitest/spy": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz", - "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.3.tgz", + "integrity": "sha512-Llj7Jgs6lbnL55WoshJUUacdJfjU2honvGcAJBxhra5TPEzTJH8ZuhI3p/JwqqfnTr4PmP7nDmOXP53MS7GJlg==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -2557,9 +2638,9 @@ } }, "node_modules/@vitest/utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz", - "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.3.tgz", + "integrity": "sha512-rE9DTN1BRhzkzqNQO+kw8ZgfeEBCLXiHJwetk668shmNBpSagQxneT5eSqEBLP+cqSiAeecvQmbpFfdMyLcIQA==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -2587,105 +2668,102 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz", - "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.26.tgz", + "integrity": "sha512-N9Vil6Hvw7NaiyFUFBPXrAyETIGlQ8KcFMkyk6hW1Cl6NvoqvP+Y8p1Eqvx+UdqsnrnI9+HMUEJegzia3mhXmQ==", "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/shared": "3.4.21", + "@babel/parser": "^7.24.4", + "@vue/shared": "3.4.26", "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz", - "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.26.tgz", + "integrity": "sha512-4CWbR5vR9fMg23YqFOhr6t6WB1Fjt62d6xdFPyj8pxrYub7d+OgZaObMsoxaF9yBUHPMiPFK303v61PwAuGvZA==", "dependencies": { - "@vue/compiler-core": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-core": "3.4.26", + "@vue/shared": "3.4.26" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz", - "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.26.tgz", + "integrity": "sha512-It1dp+FAOCgluYSVYlDn5DtZBxk1NCiJJfu2mlQqa/b+k8GL6NG/3/zRbJnHdhV2VhxFghaDq5L4K+1dakW6cw==", "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/compiler-core": "3.4.21", - "@vue/compiler-dom": "3.4.21", - "@vue/compiler-ssr": "3.4.21", - "@vue/shared": "3.4.21", + "@babel/parser": "^7.24.4", + "@vue/compiler-core": "3.4.26", + "@vue/compiler-dom": "3.4.26", + "@vue/compiler-ssr": "3.4.26", + "@vue/shared": "3.4.26", "estree-walker": "^2.0.2", - "magic-string": "^0.30.7", - "postcss": "^8.4.35", - "source-map-js": "^1.0.2" + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-sfc/node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz", - "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.26.tgz", + "integrity": "sha512-FNwLfk7LlEPRY/g+nw2VqiDKcnDTVdCfBREekF8X74cPLiWHUX6oldktf/Vx28yh4STNy7t+/yuLoMBBF7YDiQ==", "dependencies": { - "@vue/compiler-dom": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-dom": "3.4.26", + "@vue/shared": "3.4.26" } }, "node_modules/@vue/reactivity": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz", - "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.26.tgz", + "integrity": "sha512-E/ynEAu/pw0yotJeLdvZEsp5Olmxt+9/WqzvKff0gE67tw73gmbx6tRkiagE/eH0UCubzSlGRebCbidB1CpqZQ==", "dependencies": { - "@vue/shared": "3.4.21" + "@vue/shared": "3.4.26" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz", - "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.26.tgz", + "integrity": "sha512-AFJDLpZvhT4ujUgZSIL9pdNcO23qVFh7zWCsNdGQBw8ecLNxOOnPcK9wTTIYCmBJnuPHpukOwo62a2PPivihqw==", "dependencies": { - "@vue/reactivity": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/reactivity": "3.4.26", + "@vue/shared": "3.4.26" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz", - "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.26.tgz", + "integrity": "sha512-UftYA2hUXR2UOZD/Fc3IndZuCOOJgFxJsWOxDkhfVcwLbsfh2CdXE2tG4jWxBZuDAs9J9PzRTUFt1PgydEtItw==", "dependencies": { - "@vue/runtime-core": "3.4.21", - "@vue/shared": "3.4.21", + "@vue/runtime-core": "3.4.26", + "@vue/shared": "3.4.26", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz", - "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.26.tgz", + "integrity": "sha512-xoGAqSjYDPGAeRWxeoYwqJFD/gw7mpgzOvSxEmjWaFO2rE6qpbD1PC172YRpvKhrihkyHJkNDADFXTfCyVGhKw==", "dependencies": { - "@vue/compiler-ssr": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-ssr": "3.4.26", + "@vue/shared": "3.4.26" }, "peerDependencies": { - "vue": "3.4.21" + "vue": "3.4.26" } }, "node_modules/@vue/shared": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", - "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==" + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.26.tgz", + "integrity": "sha512-Fg4zwR0GNnjzodMt3KRy2AWGMKQXByl56+4HjN87soxLNU9P5xcJkstAlIeEF3cU6UYOzmJl1tV0dVPGIljCnQ==" }, "node_modules/@vue/test-utils": { "version": "2.4.5", @@ -3511,9 +3589,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001600", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", - "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", + "version": "1.0.30001605", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz", + "integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==", "funding": [ { "type": "opencollective", @@ -3701,9 +3779,9 @@ } }, "node_modules/clippie": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/clippie/-/clippie-4.0.7.tgz", - "integrity": "sha512-xmIARCRFQUoCR0kNNu4uIv5f/IFqM1fUts0vQwt1hQEdCPEqs3/dTaG38WenlWOgs3Fcn73PBYXbPIVSlOgFRw==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/clippie/-/clippie-4.1.1.tgz", + "integrity": "sha512-D9OOW77Kkj9YEiDXTQjZJZLvTjJPEmK2IBx8JbGJIZaqVd8RvSvxwIN4KVSEFQfu9Jh0z5FL6Pdc4SIknllFFA==" }, "node_modules/cliui": { "version": "7.0.4", @@ -3912,30 +3990,30 @@ } }, "node_modules/css-functions-list": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.1.tgz", - "integrity": "sha512-Nj5YcaGgBtuUmn1D7oHqPW0c9iui7xsTsj5lIX8ZgevdfhmjFfKB3r8moHJtNJnctnYXJyYX5I1pp90HM4TPgQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.2.tgz", + "integrity": "sha512-c+N0v6wbKVxTu5gOBBFkr9BEdBWaqqjQeiJ8QvSRIJOf+UxlJh930m8e6/WNeODIK0mYLFkoONrnj16i2EcvfQ==", "dev": true, "engines": { "node": ">=12 || >=16" } }, "node_modules/css-loader": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", - "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.0.0.tgz", + "integrity": "sha512-WrO4FVoamxt5zY9CauZjoJgXRi/LZKIk+Ta7YvpSGr5r/eMYPNp5/T9ODlMe4/1rF5DYlycG1avhV4g3A/tiAw==", "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.4", - "postcss-modules-scope": "^3.1.1", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.5.4" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", @@ -3943,7 +4021,7 @@ }, "peerDependencies": { "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" + "webpack": "^5.27.0" }, "peerDependenciesMeta": { "@rspack/core": { @@ -4553,9 +4631,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" }, "node_modules/debug": { "version": "4.3.4", @@ -4860,9 +4938,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.716", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.716.tgz", - "integrity": "sha512-t/MXMzFKQC3UfMDpw7V5wdB/UAB8dWx4hEsy+fpPYJWW3gqh3u5T1uXp6vR+H6dGCPBxkRo+YBcapBLvbGQHRw==" + "version": "1.4.727", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.727.tgz", + "integrity": "sha512-brpv4KTeC4g0Fx2FeIKytLd4UGn1zBQq5Lauy7zEWT9oqkaj5mgsxblEZIAOf1HHLlXxzr6adGViiBy5Z39/CA==" }, "node_modules/elkjs": { "version": "0.9.2", @@ -4933,9 +5011,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.2.tgz", - "integrity": "sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", @@ -4977,11 +5055,11 @@ "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.9", "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.7", + "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.2", "typed-array-byte-length": "^1.0.1", "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.5", + "typed-array-length": "^1.0.6", "unbox-primitive": "^1.0.2", "which-typed-array": "^1.1.15" }, @@ -5692,9 +5770,9 @@ } }, "node_modules/eslint-plugin-regexp": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.4.0.tgz", - "integrity": "sha512-OL2S6VPjQhs9s/NclQ0qattVq1J0GU8ox70/HIVy5Dxw+qbbdd7KQkyucsez2clEQjvdtDe12DTnPphFFUyXFg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.5.0.tgz", + "integrity": "sha512-I7vKcP0o75WS5SHiVNXN+Eshq49sbrweMQIuqSL3AId9AwDe9Dhbfug65vw64LxmOd4v+yf5l5Xt41y9puiq0g==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -5713,9 +5791,9 @@ } }, "node_modules/eslint-plugin-sonarjs": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.24.0.tgz", - "integrity": "sha512-87zp50mbbNrSTuoEOebdRQBPa0mdejA5UEjyuScyIw8hEpEjfWP89Qhkq5xVZfVyVSRQKZc9alVm7yRKQvvUmg==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.25.1.tgz", + "integrity": "sha512-5IOKvj/GMBNqjxBdItfotfRHo7w48496GOu1hxdeXuD0mB1JBlDCViiLHETDTfA8pDAVSBimBEQoetRXYceQEw==", "dev": true, "engines": { "node": ">=16" @@ -5725,9 +5803,9 @@ } }, "node_modules/eslint-plugin-unicorn": { - "version": "51.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-51.0.1.tgz", - "integrity": "sha512-MuR/+9VuB0fydoI0nIn2RDA5WISRn4AsJyNSaNKLVwie9/ONvQhxOBbkfSICBPnzKrB77Fh6CZZXjgTt/4Latw==", + "version": "52.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-52.0.0.tgz", + "integrity": "sha512-1Yzm7/m+0R4djH0tjDjfVei/ju2w3AzUGjG6q8JnuNIL5xIwsflyCooW5sfBvQp2pMYQFSWWCFONsjCax1EHng==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", @@ -5758,18 +5836,18 @@ } }, "node_modules/eslint-plugin-vitest": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.4.0.tgz", - "integrity": "sha512-3oWgZIwdWVBQ5plvkmOBjreIGLQRdYb7x54OP8uIRHeZyRVJIdOn9o/qWVb9292fDMC8jn7H7d9TSFBZqhrykQ==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.5.4.tgz", + "integrity": "sha512-um+odCkccAHU53WdKAw39MY61+1x990uXjSPguUCq3VcEHdqJrOb8OTMrbYlY6f9jAKx7x98kLVlIe3RJeJqoQ==", "dev": true, "dependencies": { - "@typescript-eslint/utils": "^7.2.0" + "@typescript-eslint/utils": "^7.7.1" }, "engines": { "node": "^18.0.0 || >= 20.0.0" }, "peerDependencies": { - "eslint": ">=8.0.0", + "eslint": "^8.57.0 || ^9.0.0", "vitest": "*" }, "peerDependenciesMeta": { @@ -5787,10 +5865,110 @@ "integrity": "sha512-ZSsVOaOIig0oVLzRTyk8lUfBfqzWxr/J3/NFMfGGRIkGQPejJYmDH3gXmSJxAojts77uzAGB/UmVrwi2DC4LYA==", "dev": true }, + "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/scope-manager": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.1.tgz", + "integrity": "sha512-PytBif2SF+9SpEUKynYn5g1RHFddJUcyynGpztX3l/ik7KmZEv19WCMhUBkHXPU9es/VWGD3/zg3wg90+Dh2rA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.7.1", + "@typescript-eslint/visitor-keys": "7.7.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/types": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.1.tgz", + "integrity": "sha512-AmPmnGW1ZLTpWa+/2omPrPfR7BcbUU4oha5VIbSbS1a1Tv966bklvLNXxp3mrbc+P2j4MNOTfDffNsk4o0c6/w==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.1.tgz", + "integrity": "sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.7.1", + "@typescript-eslint/visitor-keys": "7.7.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/utils": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.1.tgz", + "integrity": "sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.7.1", + "@typescript-eslint/types": "7.7.1", + "@typescript-eslint/typescript-estree": "7.7.1", + "semver": "^7.6.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.1.tgz", + "integrity": "sha512-gBL3Eq25uADw1LQ9kVpf3hRM+DWzs0uZknHYK3hq4jcTPqVCClHGDnB6UUUV2SFeBeA4KWHWbbLqmbGcZ4FYbw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.7.1", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/eslint-plugin-vue": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.24.0.tgz", - "integrity": "sha512-9SkJMvF8NGMT9aQCwFc5rj8Wo1XWSMSHk36i7ZwdI614BU7sIOR28ZjuFPKp8YGymZN12BSEbiSwa7qikp+PBw==", + "version": "9.25.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.25.0.tgz", + "integrity": "sha512-tDWlx14bVe6Bs+Nnh3IGrD+hb11kf2nukfm6jLsmJIhmiRQ1SUaksvwY9U5MvPB0pcrg0QK0xapQkfITs3RKOA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", @@ -5806,7 +5984,7 @@ "node": "^14.17.0 || >=16.0.0" }, "peerDependencies": { - "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, "node_modules/eslint-plugin-vue-scoped-css": { @@ -5836,9 +6014,9 @@ } }, "node_modules/eslint-plugin-wc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-2.0.4.tgz", - "integrity": "sha512-ORu7MBv0hXIvq894EJad70m+AvHGbmrDdKT6lcgtCVVhEbuIAyxg0ilfqqqHOmsh8PfcUBeEae3y7CElKvm1KQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-2.1.0.tgz", + "integrity": "sha512-s/BGOtmpgQ2yifR6EC1OM9t0DwYLgg4ZAL07Kw4eXvBb5TYaPafI+65tswvnZvhH8FqcjERLbBZPPvYsvinkfg==", "dev": true, "dependencies": { "is-valid-element-name": "^1.0.0", @@ -6597,9 +6775,9 @@ } }, "node_modules/happy-dom": { - "version": "14.3.10", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.3.10.tgz", - "integrity": "sha512-Rh5li9vA9MF9Gkg85CbFABKTa3uoSAByILRNGb92u/vswDd561gBg2p1UW1ZauvDWWwRxPcbACK5zv3BR+gHnQ==", + "version": "14.7.1", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-14.7.1.tgz", + "integrity": "sha512-v60Q0evZ4clvMcrAh5/F8EdxDdfHdFrtffz/CNe10jKD+nFweZVxM91tW+UyY2L4AtpgIaXdZ7TQmiO1pfcwbg==", "dev": true, "dependencies": { "entities": "^4.5.0", @@ -6746,9 +6924,9 @@ } }, "node_modules/htmx.org": { - "version": "1.9.11", - "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.11.tgz", - "integrity": "sha512-WlVuICn8dfNOOgYmdYzYG8zSnP3++AdHkMHooQAzGZObWpVXYathpz/I37ycF4zikR6YduzfCvEcxk20JkIUsw==" + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz", + "integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw==" }, "node_modules/human-signals": { "version": "5.0.0", @@ -8034,9 +8212,9 @@ } }, "node_modules/markdown-it": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.0.0.tgz", - "integrity": "sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "dependencies": { "argparse": "^2.0.1", @@ -8044,20 +8222,20 @@ "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", - "uc.micro": "^2.0.0" + "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "node_modules/markdownlint": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.33.0.tgz", - "integrity": "sha512-4lbtT14A3m0LPX1WS/3d1m7Blg+ZwiLq36WvjQqFGsX3Gik99NV+VXp/PW3n+Q62xyPdbvGOCfjPqjW+/SKMig==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.34.0.tgz", + "integrity": "sha512-qwGyuyKwjkEMOJ10XN6OTKNOVYvOIi35RNvDLNxTof5s8UmyGHlCdpngRHoRGNvQVGuxO3BJ7uNSgdeX166WXw==", "dev": true, "dependencies": { - "markdown-it": "14.0.0", - "markdownlint-micromark": "0.1.8" + "markdown-it": "14.1.0", + "markdownlint-micromark": "0.1.9" }, "engines": { "node": ">=18" @@ -8067,20 +8245,22 @@ } }, "node_modules/markdownlint-cli": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.39.0.tgz", - "integrity": "sha512-ZuFN7Xpsbn1Nbp0YYkeLOfXOMOfLQBik2lKRy8pVI/llmKQ2uW7x+8k5OMgF6o7XCsTDSYC/OOmeJ+3qplvnJQ==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.40.0.tgz", + "integrity": "sha512-JXhI3dRQcaqwiFYpPz6VJ7aKYheD53GmTz9y4D/d0F1MbZDGOp9pqKlbOfUX/pHP/iAoeiE4wYRmk8/kjLakxA==", "dev": true, "dependencies": { - "commander": "~11.1.0", + "commander": "~12.0.0", "get-stdin": "~9.0.0", - "glob": "~10.3.10", - "ignore": "~5.3.0", + "glob": "~10.3.12", + "ignore": "~5.3.1", "js-yaml": "^4.1.0", "jsonc-parser": "~3.2.1", - "markdownlint": "~0.33.0", - "minimatch": "~9.0.3", - "run-con": "~1.3.2" + "jsonpointer": "5.0.1", + "markdownlint": "~0.34.0", + "minimatch": "~9.0.4", + "run-con": "~1.3.2", + "toml": "~3.0.0" }, "bin": { "markdownlint": "markdownlint.js" @@ -8090,25 +8270,25 @@ } }, "node_modules/markdownlint-cli/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", + "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", "dev": true, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/markdownlint-cli/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -8127,12 +8307,12 @@ "dev": true }, "node_modules/markdownlint-micromark": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.8.tgz", - "integrity": "sha512-1ouYkMRo9/6gou9gObuMDnvZM8jC/ly3QCFQyoSPCS2XV1ZClU0xpKbL1Ar3bWWRT1RnBZkWUEiNKrI2CwiBQA==", + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.9.tgz", + "integrity": "sha512-5hVs/DzAFa8XqYosbEAEg6ok6MF2smDj89ztn9pKkCtdKHVdPQuGMH7frFfYL9mLkvfFe4pTyAMffLbjf3/EyA==", "dev": true, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/DavidAnson" @@ -8732,9 +8912,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", - "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" @@ -8751,9 +8931,9 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -9451,12 +9631,12 @@ "dev": true }, "node_modules/playwright": { - "version": "1.42.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz", - "integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==", + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.0.tgz", + "integrity": "sha512-SiOKHbVjTSf6wHuGCbqrEyzlm6qvXcv7mENP+OZon1I07brfZLGdfWV0l/efAzVx7TF3Z45ov1gPEkku9q25YQ==", "dev": true, "dependencies": { - "playwright-core": "1.42.1" + "playwright-core": "1.43.0" }, "bin": { "playwright": "cli.js" @@ -9469,9 +9649,9 @@ } }, "node_modules/playwright-core": { - "version": "1.42.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz", - "integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==", + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.0.tgz", + "integrity": "sha512-iWFjyBUH97+pUFiyTqSLd8cDMMOS0r2ZYz2qEsPjH8/bX++sbIJT35MSwKnp1r/OQBAqC5XO99xFbJ9XClhf4w==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -9614,9 +9794,9 @@ } }, "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "engines": { "node": "^10 || ^12 || >= 14" }, @@ -9625,9 +9805,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz", - "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^6.0.2", @@ -9641,9 +9821,9 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz", - "integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dependencies": { "postcss-selector-parser": "^6.0.4" }, @@ -9687,9 +9867,9 @@ } }, "node_modules/postcss-nesting": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.0.tgz", - "integrity": "sha512-QOYnosaZ+mlP6plQrAxFw09UUp2Sgtxj1BVHN+rSVbtV0Yx48zRt9/9F/ZOoxOKBBEsaJk2MYhhVRjeRRw5yuw==", + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.2.tgz", + "integrity": "sha512-FUmTHGDNundodutB4PUBxt/EPuhgtpk8FJGRsBhOuy+6FnkR2A8RZWIsyyy6XmhvX2DZQQWIkvu+HB4IbJm+Ew==", "funding": [ { "type": "github", @@ -9702,7 +9882,7 @@ ], "dependencies": { "@csstools/selector-resolve-nested": "^1.1.0", - "@csstools/selector-specificity": "^3.0.2", + "@csstools/selector-specificity": "^3.0.3", "postcss-selector-parser": "^6.0.13" }, "engines": { @@ -10929,17 +11109,23 @@ } }, "node_modules/strip-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz", - "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", "dev": true, "dependencies": { - "js-tokens": "^8.0.2" + "js-tokens": "^9.0.0" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", + "dev": true + }, "node_modules/style-search": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", @@ -10947,20 +11133,20 @@ "dev": true }, "node_modules/stylelint": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.3.0.tgz", - "integrity": "sha512-hqC6xNTbQ5HRGQXfIW4HwXcx09raIFz4W4XFbraeqWqYRVVY/ibYvI0dsu0ORMQY8re2bpDdCAeIa2cm+QJ4Sw==", + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.4.0.tgz", + "integrity": "sha512-uSx7VMuXwLuYcNSIg+0/fFNv0WinsfLAqsVVy7h7p80clKOHiGE8pfY6UjqwylTHiJrRIahTl6a8FPxGezhWoA==", "dev": true, "dependencies": { "@csstools/css-parser-algorithms": "^2.6.1", "@csstools/css-tokenizer": "^2.2.4", "@csstools/media-query-list-parser": "^2.1.9", - "@csstools/selector-specificity": "^3.0.2", + "@csstools/selector-specificity": "^3.0.3", "@dual-bundle/import-meta-resolve": "^4.0.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.0", - "css-functions-list": "^3.2.1", + "css-functions-list": "^3.2.2", "css-tree": "^2.3.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", @@ -10989,7 +11175,7 @@ "strip-ansi": "^7.1.0", "supports-hyperlinks": "^3.0.0", "svg-tags": "^1.0.0", - "table": "^6.8.1", + "table": "^6.8.2", "write-file-atomic": "^5.0.1" }, "bin": { @@ -11027,6 +11213,22 @@ "stylelint": ">=7 <=16" } }, + "node_modules/stylelint-value-no-unknown-custom-properties": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/stylelint-value-no-unknown-custom-properties/-/stylelint-value-no-unknown-custom-properties-6.0.1.tgz", + "integrity": "sha512-N60PTdaTknB35j6D4FhW0GL2LlBRV++bRpXMMldWMQZ240yFQaoltzlLY4lXXs7Z0J5mNUYZQ/gjyVtU2DhCMA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "resolve": "^1.22.8" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": ">=16" + } + }, "node_modules/stylelint/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -11184,15 +11386,15 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -11295,9 +11497,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.12.0.tgz", - "integrity": "sha512-Rt1xUpbHulJVGbiQjq9yy9/r/0Pg6TmpcG+fXTaMePDc8z5WUw4LfaWts5qcNv/8ewPvBIbY7DKq7qReIKNCCQ==" + "version": "5.17.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.2.tgz", + "integrity": "sha512-V/NqUw6QoTrjSpctp2oLQvxrl3vW29UsUtZyq7B1CF0v870KOFbYGDQw8rpKaKm0JxTwHpWnW1SN9YuKZdiCyw==" }, "node_modules/sync-fetch": { "version": "0.4.5", @@ -11328,9 +11530,9 @@ } }, "node_modules/table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", "dev": true, "dependencies": { "ajv": "^8.0.1", @@ -11344,9 +11546,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", - "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -11356,7 +11558,7 @@ "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.19.1", + "jiti": "^1.21.0", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", @@ -11433,22 +11635,22 @@ } }, "node_modules/temporal-polyfill": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.2.3.tgz", - "integrity": "sha512-7ZJRc7wq/1XjrOQYkkNpgo2qfE9XLrUU8D/DS+LAC/T0bYqZ46rW6dow0sOTXTPZS4bwer8bD/0OyuKQBfA3yw==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.2.4.tgz", + "integrity": "sha512-WA5p0CjQTkMjF9m8sP4wSYgpqI8m2d4q7wPUyaJOWhy4bI9mReLb2yGvTV4qf/DPMTe6H6M/Dig5KmTMB7ev6Q==", "dependencies": { - "temporal-spec": "^0.2.0" + "temporal-spec": "^0.2.4" } }, "node_modules/temporal-spec": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.2.0.tgz", - "integrity": "sha512-r1AT0XdEp8TMQ13FLvOt8mOtAxDQsRt2QU5rSWCA7YfshddU651Y1NHVrceLANvixKdf9fYO8B/S9fXHodB7HQ==" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.2.4.tgz", + "integrity": "sha512-lDMFv4nKQrSjlkHKAlHVqKrBG4DyFfa9F74cmBZ3Iy3ed8yvWnlWSIdi4IKfSqwmazAohBNwiN64qGx4y5Q3IQ==" }, "node_modules/terser": { - "version": "5.29.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.2.tgz", - "integrity": "sha512-ZiGkhUBIM+7LwkNjXYJq8svgkd+QK3UUr0wJqY4MieaezBSAIPgbSPZyIx0idM6XWK5CMzSWa8MJIzmRcB8Caw==", + "version": "5.30.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz", + "integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -11631,6 +11833,12 @@ "resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz", "integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==" }, + "node_modules/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", + "dev": true + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -11803,9 +12011,9 @@ } }, "node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", + "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", "devOptional": true, "peer": true, "bin": { @@ -11909,9 +12117,9 @@ } }, "node_modules/updates": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/updates/-/updates-16.0.0.tgz", - "integrity": "sha512-Ra3QUu/rfbSCsG83zNNvoRQt0FVT3qULBSALYTlwTDX6oyz7R5GQAYwqJoIG/RDjYAXpwr3usrInoyHHTP6cag==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/updates/-/updates-16.0.1.tgz", + "integrity": "sha512-If3NQKzGcA3aVgz2VyOXqQ+4uqYjPUPqh2PeZPtD+OKT4CTmxRYqoyFO+T3nwfccy4SiWy5AabWrBXXhVQ89Aw==", "dev": true, "bin": { "updates": "dist/updates.js" @@ -11996,14 +12204,19 @@ "builtins": "^1.0.3" } }, + "node_modules/vanilla-colorful": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz", + "integrity": "sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg==" + }, "node_modules/vite": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.6.tgz", - "integrity": "sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==", + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", + "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==", "dev": true, "dependencies": { "esbuild": "^0.20.1", - "postcss": "^8.4.36", + "postcss": "^8.4.38", "rollup": "^4.13.0" }, "bin": { @@ -12052,9 +12265,9 @@ } }, "node_modules/vite-node": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz", - "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.3.tgz", + "integrity": "sha512-axFo00qiCpU/JLd8N1gu9iEYL3xTbMbMrbe5nDp9GL0nb6gurIdZLkkFogZXWnE8Oyy5kfSLwNVIcVsnhE7lgQ==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -12074,9 +12287,9 @@ } }, "node_modules/vite-string-plugin": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/vite-string-plugin/-/vite-string-plugin-1.1.5.tgz", - "integrity": "sha512-KRCIFX3PWVUuEjpi9O7EKLT9E27OqOA3RimIvVx6cziLAUxvnk2VvHQfMrP+mKkqyqqSmnnYyTig3OyDnK/zlA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/vite-string-plugin/-/vite-string-plugin-1.3.1.tgz", + "integrity": "sha512-0Wu9yNw4QlSVM4SlwozzxR0geMoKFrAIpMldgPuzDvV8lWT1v+0pFXYt+t48qocYXBaxiuVRE3qcsEwFDHBAmA==", "dev": true }, "node_modules/vite/node_modules/@types/estree": { @@ -12100,9 +12313,9 @@ } }, "node_modules/vite/node_modules/rollup": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", - "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.0.tgz", + "integrity": "sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -12115,33 +12328,35 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.13.0", - "@rollup/rollup-android-arm64": "4.13.0", - "@rollup/rollup-darwin-arm64": "4.13.0", - "@rollup/rollup-darwin-x64": "4.13.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", - "@rollup/rollup-linux-arm64-gnu": "4.13.0", - "@rollup/rollup-linux-arm64-musl": "4.13.0", - "@rollup/rollup-linux-riscv64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-gnu": "4.13.0", - "@rollup/rollup-linux-x64-musl": "4.13.0", - "@rollup/rollup-win32-arm64-msvc": "4.13.0", - "@rollup/rollup-win32-ia32-msvc": "4.13.0", - "@rollup/rollup-win32-x64-msvc": "4.13.0", + "@rollup/rollup-android-arm-eabi": "4.14.0", + "@rollup/rollup-android-arm64": "4.14.0", + "@rollup/rollup-darwin-arm64": "4.14.0", + "@rollup/rollup-darwin-x64": "4.14.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.0", + "@rollup/rollup-linux-arm64-gnu": "4.14.0", + "@rollup/rollup-linux-arm64-musl": "4.14.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.0", + "@rollup/rollup-linux-riscv64-gnu": "4.14.0", + "@rollup/rollup-linux-s390x-gnu": "4.14.0", + "@rollup/rollup-linux-x64-gnu": "4.14.0", + "@rollup/rollup-linux-x64-musl": "4.14.0", + "@rollup/rollup-win32-arm64-msvc": "4.14.0", + "@rollup/rollup-win32-ia32-msvc": "4.14.0", + "@rollup/rollup-win32-x64-msvc": "4.14.0", "fsevents": "~2.3.2" } }, "node_modules/vitest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", - "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.3.tgz", + "integrity": "sha512-2oM7nLXylw3mQlW6GXnRriw+7YvZFk/YNV8AxIC3Z3MfFbuziLGWP9GPxxu/7nRlXhqyxBikpamr+lEEj1sUEw==", "dev": true, "dependencies": { - "@vitest/expect": "1.4.0", - "@vitest/runner": "1.4.0", - "@vitest/snapshot": "1.4.0", - "@vitest/spy": "1.4.0", - "@vitest/utils": "1.4.0", + "@vitest/expect": "1.5.3", + "@vitest/runner": "1.5.3", + "@vitest/snapshot": "1.5.3", + "@vitest/spy": "1.5.3", + "@vitest/utils": "1.5.3", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -12153,9 +12368,9 @@ "std-env": "^3.5.0", "strip-literal": "^2.0.0", "tinybench": "^2.5.1", - "tinypool": "^0.8.2", + "tinypool": "^0.8.3", "vite": "^5.0.0", - "vite-node": "1.4.0", + "vite-node": "1.5.3", "why-is-node-running": "^2.2.2" }, "bin": { @@ -12170,8 +12385,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.4.0", - "@vitest/ui": "1.4.0", + "@vitest/browser": "1.5.3", + "@vitest/ui": "1.5.3", "happy-dom": "*", "jsdom": "*" }, @@ -12197,27 +12412,24 @@ } }, "node_modules/vitest/node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" } }, "node_modules/vue": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz", - "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==", + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.26.tgz", + "integrity": "sha512-bUIq/p+VB+0xrJubaemrfhk1/FiW9iX+pDV+62I/XJ6EkspAO9/DXEjbDFoe8pIfOZBqfk45i9BMc41ptP/uRg==", "dependencies": { - "@vue/compiler-dom": "3.4.21", - "@vue/compiler-sfc": "3.4.21", - "@vue/runtime-dom": "3.4.21", - "@vue/server-renderer": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-dom": "3.4.26", + "@vue/compiler-sfc": "3.4.26", + "@vue/runtime-dom": "3.4.26", + "@vue/server-renderer": "3.4.26", + "@vue/shared": "3.4.26" }, "peerDependencies": { "typescript": "*" @@ -12238,9 +12450,9 @@ } }, "node_modules/vue-chartjs": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.0.tgz", - "integrity": "sha512-8XqX0JU8vFZ+WA2/knz4z3ThClduni2Nm0BMe2u0mXgTfd9pXrmJ07QBI+WAij5P/aPmPMX54HCE1seWL37ZdQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.1.tgz", + "integrity": "sha512-rZjqcHBxKiHrBl0CIvcOlVEBwRhpWAVf6rDU3vUfa7HuSRmGtCslc0Oc8m16oAVuk0erzc1FCtH1VCriHsrz+A==", "peerDependencies": { "chart.js": "^4.1.1", "vue": "^3.0.0-0 || ^2.7.0" @@ -12813,18 +13025,18 @@ } }, "node_modules/yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "yargs-parser": "^21.1.1" }, "engines": { "node": ">=12" @@ -12839,6 +13051,37 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index b50c52cf43..b380a33568 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,10 @@ "node": ">= 18.0.0" }, "dependencies": { - "@citation-js/core": "0.7.9", - "@citation-js/plugin-bibtex": "0.7.9", - "@citation-js/plugin-csl": "0.7.9", + "@citation-js/core": "0.7.11", + "@citation-js/plugin-bibtex": "0.7.11", + "@citation-js/plugin-csl": "0.7.11", "@citation-js/plugin-software-formats": "0.6.1", - "@claviska/jquery-minicolors": "2.3.6", "@github/markdown-toolbar-element": "2.2.3", "@github/relative-time-element": "4.4.0", "@github/text-expander-element": "2.6.1", @@ -20,42 +19,43 @@ "chart.js": "4.4.2", "chartjs-adapter-dayjs-4": "1.0.4", "chartjs-plugin-zoom": "2.0.1", - "clippie": "4.0.7", - "css-loader": "6.10.0", - "dayjs": "1.11.10", + "clippie": "4.1.1", + "css-loader": "7.0.0", + "dayjs": "1.11.11", "dropzone": "6.0.0-beta.2", "easymde": "2.18.0", "esbuild-loader": "4.1.0", "escape-goat": "4.0.0", "fast-glob": "3.3.2", - "htmx.org": "1.9.11", + "htmx.org": "1.9.12", "idiomorph": "0.3.0", "jquery": "3.7.1", "katex": "0.16.10", "license-checker-webpack-plugin": "0.2.1", "mermaid": "10.9.0", - "mini-css-extract-plugin": "2.8.1", - "minimatch": "9.0.3", + "mini-css-extract-plugin": "2.9.0", + "minimatch": "9.0.4", "monaco-editor": "0.47.0", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.0", "postcss": "8.4.38", "postcss-loader": "8.1.1", - "postcss-nesting": "12.1.0", + "postcss-nesting": "12.1.2", "pretty-ms": "9.0.0", "sortablejs": "1.15.2", - "swagger-ui-dist": "5.12.0", - "tailwindcss": "3.4.1", - "temporal-polyfill": "0.2.3", + "swagger-ui-dist": "5.17.2", + "tailwindcss": "3.4.3", + "temporal-polyfill": "0.2.4", "throttle-debounce": "5.0.0", "tinycolor2": "1.6.0", "tippy.js": "6.3.7", "toastify-js": "1.12.0", "tributejs": "5.1.3", "uint8-to-base64": "0.2.0", - "vue": "3.4.21", + "vanilla-colorful": "0.7.2", + "vue": "3.4.26", "vue-bar-graph": "2.0.0", - "vue-chartjs": "5.3.0", + "vue-chartjs": "5.3.1", "vue-loader": "17.4.2", "vue3-calendar-heatmap": "2.0.5", "webpack": "5.91.0", @@ -63,11 +63,11 @@ "wrap-ansi": "9.0.0" }, "devDependencies": { - "@eslint-community/eslint-plugin-eslint-comments": "4.1.0", - "@playwright/test": "1.42.1", - "@stoplight/spectral-cli": "6.11.0", - "@stylistic/eslint-plugin-js": "1.7.0", - "@stylistic/stylelint-plugin": "2.1.0", + "@eslint-community/eslint-plugin-eslint-comments": "4.3.0", + "@playwright/test": "1.43.0", + "@stoplight/spectral-cli": "6.11.1", + "@stylistic/eslint-plugin-js": "1.8.0", + "@stylistic/stylelint-plugin": "2.1.2", "@vitejs/plugin-vue": "5.0.4", "@vue/test-utils": "2.4.5", "eslint": "8.57.0", @@ -77,26 +77,25 @@ "eslint-plugin-jquery": "1.5.1", "eslint-plugin-no-jquery": "2.7.0", "eslint-plugin-no-use-extend-native": "0.5.0", - "eslint-plugin-regexp": "2.4.0", - "eslint-plugin-sonarjs": "0.24.0", - "eslint-plugin-unicorn": "51.0.1", - "eslint-plugin-vitest": "0.4.0", + "eslint-plugin-regexp": "2.5.0", + "eslint-plugin-sonarjs": "0.25.1", + "eslint-plugin-unicorn": "52.0.0", + "eslint-plugin-vitest": "0.5.4", "eslint-plugin-vitest-globals": "1.5.0", - "eslint-plugin-vue": "9.24.0", + "eslint-plugin-vue": "9.25.0", "eslint-plugin-vue-scoped-css": "2.8.0", - "eslint-plugin-wc": "2.0.4", - "happy-dom": "14.3.10", - "markdownlint-cli": "0.39.0", + "eslint-plugin-wc": "2.1.0", + "happy-dom": "14.7.1", + "markdownlint-cli": "0.40.0", "postcss-html": "1.6.0", - "stylelint": "16.3.0", + "stylelint": "16.4.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.4", + "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.2.0", - "updates": "16.0.0", - "vite-string-plugin": "1.1.5", - "vitest": "1.4.0" + "updates": "16.0.1", + "vite-string-plugin": "1.3.1", + "vitest": "1.5.3" }, - "browserslist": [ - "defaults" - ] + "browserslist": ["defaults"] } diff --git a/release-notes/8.0.0/3139.md b/release-notes/8.0.0/3139.md new file mode 100644 index 0000000000..cac054bee8 --- /dev/null +++ b/release-notes/8.0.0/3139.md @@ -0,0 +1 @@ +Allow hiding auto generated release archives diff --git a/release-notes/8.0.0/3383.md b/release-notes/8.0.0/3383.md new file mode 100644 index 0000000000..9832030aa3 --- /dev/null +++ b/release-notes/8.0.0/3383.md @@ -0,0 +1 @@ +The default config for `database.MAX_OPEN_CONNS` changed from 0 (unlimited) to 100 to avoid problems if it exceeds the limit by the database server. If you require high concurrency, try to increase this value for both Forgejo **and your database server**. [`Limit database max connections by default`](https://codeberg.org/forgejo/forgejo/pulls/3383) diff --git a/release-notes/8.0.0/3414.md b/release-notes/8.0.0/3414.md new file mode 100644 index 0000000000..2e10483e23 --- /dev/null +++ b/release-notes/8.0.0/3414.md @@ -0,0 +1 @@ +Allow to customize the domain name used as a fallback when synchronizing sources from ldap [`ldap: default domain name`](https://codeberg.org/forgejo/forgejo/pulls/3414) diff --git a/release-notes/8.0.0/feat/3307.md b/release-notes/8.0.0/feat/3307.md new file mode 100644 index 0000000000..6d7dd01415 --- /dev/null +++ b/release-notes/8.0.0/feat/3307.md @@ -0,0 +1 @@ +Support [Proof Key for Code Exchange (PKCE - RFC7636)](https://www.rfc-editor.org/rfc/rfc7636) for external login sources using the OAuth2 flow. diff --git a/release-notes/8.0.0/feat/3434.md b/release-notes/8.0.0/feat/3434.md new file mode 100644 index 0000000000..a8b28eb1c3 --- /dev/null +++ b/release-notes/8.0.0/feat/3434.md @@ -0,0 +1 @@ +When PDFs are displayed in the repository, the [full height of the screen](https://codeberg.org/forgejo/forgejo/pulls/3434) is now used instead of a predefined fixed height diff --git a/release-notes/8.0.0/feat/3615.md b/release-notes/8.0.0/feat/3615.md new file mode 100644 index 0000000000..f2dd891c95 --- /dev/null +++ b/release-notes/8.0.0/feat/3615.md @@ -0,0 +1,5 @@ +Support the [Nix tarball fetcher immutable link protocol](https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md) on archive URLs, so Forgejo-generated tarballs for branches will go into Nix's `flake.lock` as their respective commit URLs and `nix flake update` will just work. This allows natively fetching Forgejo repositories for Nix flake inputs as tarballs rather than as Git repositories, significantly improving fetch times and avoiding depending on Git at runtime. + +Concretely, Forgejo now returns a header of the following format from its archive URLs: `Link: rel="immutable"`. + +Example usage: `inputs.meow.url = "https://my-forgejo/someuser/somerepo/archive/main.tar.gz";` in `flake.nix`. For a private repository, configure `netrc-file` in `nix.conf` and use `https://my-forgejo/api/v1/repos/someuser/somerepo/archive/main.tar.gz` as a URL instead, since the normal archive endpoint doesn't support tokens. diff --git a/release-notes/8.0.0/fix/3363.md b/release-notes/8.0.0/fix/3363.md new file mode 100644 index 0000000000..65b516cabc --- /dev/null +++ b/release-notes/8.0.0/fix/3363.md @@ -0,0 +1,6 @@ +Reverted the rootless container image path in `GITEA_APP_INI` from +`/etc/gitea/app.ini` to its default value of +`/var/lib/gitea/custom/conf/app.ini`. This allows container users to not have +to mount two separate volumes (one for the configuration data and one for the +configuration `.ini` file). A warning is issued for users with the legacy +configuration on how to update to the new path. diff --git a/release-notes/8.0.0/fix/3430.md b/release-notes/8.0.0/fix/3430.md new file mode 100644 index 0000000000..17d91653e9 --- /dev/null +++ b/release-notes/8.0.0/fix/3430.md @@ -0,0 +1 @@ +Fixed a bug where the `/api/v1/repos/{owner}/{repo}/wiki` API endpoints were using a hardcoded "master" branch for the wiki, rather than the branch they really use. diff --git a/release-notes/8.0.0/fix/3442.md b/release-notes/8.0.0/fix/3442.md new file mode 100644 index 0000000000..7c4feafb68 --- /dev/null +++ b/release-notes/8.0.0/fix/3442.md @@ -0,0 +1 @@ +Save updated empty comments instead of skipping the update silently, [which prevented the removal of attachments of such comments](https://codeberg.org/forgejo/forgejo/issues/3424). diff --git a/renovate.json b/renovate.json index 747501a66a..0a6bf6f700 100644 --- a/renovate.json +++ b/renovate.json @@ -3,8 +3,11 @@ "extends": [ "config:best-practices", ":approveMajorUpdates", + ":maintainLockFilesWeekly", "group:postcss", - "group:linters" + "group:linters", + "schedule:daily", + "schedule:automergeDaily" ], "ignorePresets": [ ":semanticPrefixFixDepsChoreOthers", @@ -12,25 +15,54 @@ "helpers:pinGitHubActionDigests" ], "semanticCommits": "disabled", + "automergeStrategy": "merge-commit", "postUpdateOptions": ["gomodTidy", "gomodUpdateImportPaths", "npmDedupe"], - "platformAutomerge": false, "prConcurrentLimit": 5, + "internalChecksFilter": "strict", "packageRules": [ { "description": "Require approval for go and python minor version", - "matchDepNames": ["go", "python", "golang", "docker.io/golang", "docker.io/library/golang"], + "matchDepNames": [ + "go", + "python", + "golang", + "docker.io/golang", + "docker.io/library/golang" + ], "matchUpdateTypes": ["minor"], "dependencyDashboardApproval": true }, + { + "description": "Require dashboard approval for some deps", + "matchDepNames": [ + "bitnami/minio", + "github.com/go-ap/activitypub", + "github.com/nektos/act", + "gitea.com/gitea/act" + ], + "dependencyDashboardApproval": true + }, + { + "description": "Schedule some deps less frequently", + "matchDepNames": ["github.com/google/pprof"], + "extends": ["schedule:quarterly"] + }, { "description": "Group golang packages", - "matchDepNames": ["go", "golang", "docker.io/golang", "docker.io/library/golang"], + "matchDepNames": [ + "go", + "golang", + "docker.io/golang", + "docker.io/library/golang" + ], "groupName": "golang packages" }, { "description": "Group nodejs packages", "matchDepNames": ["node", "docker.io/node", "docker.io/library/node"], - "groupName": "nodejs packages" + "groupName": "nodejs packages", + "versionCompatibility": "^(?[^-]+)(?-.*)?$", + "versioning": "node" }, { "description": "Automerge renovate updates", @@ -40,10 +72,20 @@ "automerge": true }, { - "description": "Update renovate only daily with higher prio to come through rate limit", + "description": "Split minor and patch updates", + "matchDepNames": ["vue", "github.com/urfave/cli/v2", "swagger-ui-dist"], + "separateMinorPatch": true + }, + { + "description": "Automerge patch updates", + "matchDepNames": ["vue", "github.com/urfave/cli/v2", "swagger-ui-dist"], + "matchUpdateTypes": ["patch"], + "automerge": true + }, + { + "description": "Update renovate with higher prio to come through rate limit", "matchDatasources": ["docker"], "matchDepNames": ["ghcr.io/visualon/renovate"], - "extends": ["schedule:daily"], "prPriority": 10 }, { @@ -51,6 +93,17 @@ "matchDepNames": ["actions/cascading-pr"], "matchManagers": ["github-actions"], "enabled": false + }, + { + "description": "Automerge some packages when ci succeeds", + "extends": ["packages:linters"], + "matchDepNames": ["vitest", "vite-string-plugin"], + "automerge": true + }, + { + "description": "Hold back on some package updates for a few days", + "matchDepNames": ["monaco-editor"], + "minimumReleaseAge": "30 days" } ], "customManagers": [ @@ -70,6 +123,14 @@ "matchStrings": ["\\s+node-version: ['\"]?(?.+?)['\"]?\\s"], "depNameTemplate": "node", "datasourceTemplate": "node-version" + }, + { + "description": "Update deps inside Makefile", + "customType": "regex", + "fileMatch": ["^Makefile$"], + "matchStrings": [ + " \\?= (?.+?)@(?.+?) # renovate: datasource=(?.+?)\\s" + ] } ] } diff --git a/routers/api/actions/artifacts.go b/routers/api/actions/artifacts.go index 4306df5cd8..416d7e5181 100644 --- a/routers/api/actions/artifacts.go +++ b/routers/api/actions/artifacts.go @@ -144,7 +144,6 @@ func ArtifactContexter() func(next http.Handler) http.Handler { var task *actions.ActionTask if err == nil { - task, err = actions.GetTaskByID(req.Context(), tID) if err != nil { log.Error("Error runner api getting task by ID: %v", err) diff --git a/routers/api/packages/alpine/alpine.go b/routers/api/packages/alpine/alpine.go index cf0fe6c07c..481cf70d33 100644 --- a/routers/api/packages/alpine/alpine.go +++ b/routers/api/packages/alpine/alpine.go @@ -144,12 +144,12 @@ func UploadPackageFile(ctx *context.Context) { return } - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/conan/conan.go b/routers/api/packages/conan/conan.go index c45e085a4d..07ea3eda34 100644 --- a/routers/api/packages/conan/conan.go +++ b/routers/api/packages/conan/conan.go @@ -310,12 +310,12 @@ func uploadFile(ctx *context.Context, fileFilter container.Set[string], fileKey return } - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusBadRequest, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/conda/conda.go b/routers/api/packages/conda/conda.go index 30c80fc15e..c7e4544d52 100644 --- a/routers/api/packages/conda/conda.go +++ b/routers/api/packages/conda/conda.go @@ -174,12 +174,12 @@ func EnumeratePackages(ctx *context.Context) { } func UploadPackageFile(ctx *context.Context) { - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go index e519766142..2cb16daebc 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -385,9 +385,9 @@ func EndUploadBlob(ctx *context.Context) { } return } - close := true + doClose := true defer func() { - if close { + if doClose { uploader.Close() } }() @@ -427,7 +427,7 @@ func EndUploadBlob(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - close = false + doClose = false if err := container_service.RemoveBlobUploadByID(ctx, uploader.ID); err != nil { apiError(ctx, http.StatusInternalServerError, err) diff --git a/routers/api/packages/cran/cran.go b/routers/api/packages/cran/cran.go index 2cec75294f..f1d616724a 100644 --- a/routers/api/packages/cran/cran.go +++ b/routers/api/packages/cran/cran.go @@ -151,12 +151,12 @@ func UploadBinaryPackageFile(ctx *context.Context) { } func uploadPackageFile(ctx *context.Context, compositeKey string, properties map[string]string) { - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusBadRequest, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go index 241de3ac5d..8c05476cbc 100644 --- a/routers/api/packages/debian/debian.go +++ b/routers/api/packages/debian/debian.go @@ -127,12 +127,12 @@ func UploadPackageFile(ctx *context.Context) { return } - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go index 8232931134..e66f3ee676 100644 --- a/routers/api/packages/generic/generic.go +++ b/routers/api/packages/generic/generic.go @@ -90,12 +90,12 @@ func UploadPackage(ctx *context.Context) { return } - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/goproxy/goproxy.go b/routers/api/packages/goproxy/goproxy.go index d658066bb4..56a07dbd43 100644 --- a/routers/api/packages/goproxy/goproxy.go +++ b/routers/api/packages/goproxy/goproxy.go @@ -154,12 +154,12 @@ func resolvePackage(ctx *context.Context, ownerID int64, name, version string) ( } func UploadPackage(ctx *context.Context) { - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go index 09156ece6b..26b0ae226e 100644 --- a/routers/api/packages/nuget/nuget.go +++ b/routers/api/packages/nuget/nuget.go @@ -594,13 +594,13 @@ func UploadSymbolPackage(ctx *context.Context) { func processUploadedFile(ctx *context.Context, expectedType nuget_module.PackageType) (*nuget_module.Package, *packages_module.HashedBuffer, []io.Closer) { closables := make([]io.Closer, 0, 2) - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusBadRequest, err) return nil, nil, closables } - if close { + if needToClose { closables = append(closables, upload) } diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go index 4de361c214..c59366992c 100644 --- a/routers/api/packages/rpm/rpm.go +++ b/routers/api/packages/rpm/rpm.go @@ -117,12 +117,12 @@ func GetRepositoryFile(ctx *context.Context) { } func UploadPackageFile(ctx *context.Context) { - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go index d2fbcd01f0..ba5f4de080 100644 --- a/routers/api/packages/rubygems/rubygems.go +++ b/routers/api/packages/rubygems/rubygems.go @@ -197,12 +197,12 @@ func DownloadPackageFile(ctx *context.Context) { // UploadPackageFile adds a file to the package. If the package does not exist, it gets created. func UploadPackageFile(ctx *context.Context) { - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusBadRequest, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 98c9a2697f..9ea210ee4e 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -192,9 +192,17 @@ func EditUser(ctx *context.APIContext) { form := web.GetForm(ctx).(*api.EditUserOption) + // If either LoginSource or LoginName is given, the other must be present too. + if form.SourceID != nil || form.LoginName != nil { + if form.SourceID == nil || form.LoginName == nil { + ctx.Error(http.StatusUnprocessableEntity, "LoginSourceAndLoginName", fmt.Errorf("source_id and login_name must be specified together")) + return + } + } + authOpts := &user_service.UpdateAuthOptions{ - LoginSource: optional.FromNonDefault(form.SourceID), - LoginName: optional.Some(form.LoginName), + LoginSource: optional.FromPtr(form.SourceID), + LoginName: optional.FromPtr(form.LoginName), Password: optional.FromNonDefault(form.Password), MustChangePassword: optional.FromPtr(form.MustChangePassword), ProhibitLogin: optional.FromPtr(form.ProhibitLogin), diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index b202e32e4e..e4c848cd2f 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -93,6 +93,7 @@ import ( "code.gitea.io/gitea/routers/api/v1/repo" "code.gitea.io/gitea/routers/api/v1/settings" "code.gitea.io/gitea/routers/api/v1/user" + "code.gitea.io/gitea/services/actions" "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" @@ -753,6 +754,34 @@ func Routes() *web.Route { m.Use(shared.Middlewares()...) + addActionsRoutes := func( + m *web.Route, + reqChecker func(ctx *context.APIContext), + act actions.API, + ) { + m.Group("/actions", func() { + m.Group("/secrets", func() { + m.Get("", reqToken(), reqChecker, act.ListActionsSecrets) + m.Combo("/{secretname}"). + Put(reqToken(), reqChecker, bind(api.CreateOrUpdateSecretOption{}), act.CreateOrUpdateSecret). + Delete(reqToken(), reqChecker, act.DeleteSecret) + }) + + m.Group("/variables", func() { + m.Get("", reqToken(), reqChecker, act.ListVariables) + m.Combo("/{variablename}"). + Get(reqToken(), reqChecker, act.GetVariable). + Delete(reqToken(), reqChecker, act.DeleteVariable). + Post(reqToken(), reqChecker, bind(api.CreateVariableOption{}), act.CreateVariable). + Put(reqToken(), reqChecker, bind(api.UpdateVariableOption{}), act.UpdateVariable) + }) + + m.Group("/runners", func() { + m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken) + }) + }) + } + m.Group("", func() { // Miscellaneous (no scope required) if setting.API.EnableSwagger { @@ -871,6 +900,15 @@ func Routes() *web.Route { Delete(user.DeleteSecret) }) + m.Group("/variables", func() { + m.Get("", user.ListVariables) + m.Combo("/{variablename}"). + Get(user.GetVariable). + Delete(user.DeleteVariable). + Post(bind(api.CreateVariableOption{}), user.CreateVariable). + Put(bind(api.UpdateVariableOption{}), user.UpdateVariable) + }) + m.Group("/runners", func() { m.Get("/registration-token", reqToken(), user.GetRegistrationToken) }) @@ -974,6 +1012,8 @@ func Routes() *web.Route { m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate) m.Group("/{username}/{reponame}", func() { + m.Get("/compare/*", reqRepoReader(unit.TypeCode), repo.CompareDiff) + m.Combo("").Get(reqAnyRepoReader(), repo.Get). Delete(reqToken(), reqOwner(), repo.Delete). Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit) @@ -983,17 +1023,11 @@ func Routes() *web.Route { m.Post("/accept", repo.AcceptTransfer) m.Post("/reject", repo.RejectTransfer) }, reqToken()) - m.Group("/actions", func() { - m.Group("/secrets", func() { - m.Combo("/{secretname}"). - Put(reqToken(), reqOwner(), bind(api.CreateOrUpdateSecretOption{}), repo.CreateOrUpdateSecret). - Delete(reqToken(), reqOwner(), repo.DeleteSecret) - }) - - m.Group("/runners", func() { - m.Get("/registration-token", reqToken(), reqOwner(), repo.GetRegistrationToken) - }) - }) + addActionsRoutes( + m, + reqOwner(), + repo.NewAction(), + ) m.Group("/hooks/git", func() { m.Combo("").Get(repo.ListGitHooks) m.Group("/{id}", func() { @@ -1385,18 +1419,11 @@ func Routes() *web.Route { m.Combo("/{username}").Get(reqToken(), org.IsMember). Delete(reqToken(), reqOrgOwnership(), org.DeleteMember) }) - m.Group("/actions", func() { - m.Group("/secrets", func() { - m.Get("", reqToken(), reqOrgOwnership(), org.ListActionsSecrets) - m.Combo("/{secretname}"). - Put(reqToken(), reqOrgOwnership(), bind(api.CreateOrUpdateSecretOption{}), org.CreateOrUpdateSecret). - Delete(reqToken(), reqOrgOwnership(), org.DeleteSecret) - }) - - m.Group("/runners", func() { - m.Get("/registration-token", reqToken(), reqOrgOwnership(), org.GetRegistrationToken) - }) - }) + addActionsRoutes( + m, + reqOrgOwnership(), + org.NewAction(), + ) m.Group("/public_members", func() { m.Get("", org.ListPublicMembers) m.Combo("/{username}").Get(org.IsPublicMember). diff --git a/routers/api/v1/org/action.go b/routers/api/v1/org/action.go new file mode 100644 index 0000000000..03a1fa8ccc --- /dev/null +++ b/routers/api/v1/org/action.go @@ -0,0 +1,473 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package org + +import ( + "errors" + "net/http" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + secret_model "code.gitea.io/gitea/models/secret" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers/api/v1/shared" + "code.gitea.io/gitea/routers/api/v1/utils" + actions_service "code.gitea.io/gitea/services/actions" + "code.gitea.io/gitea/services/context" + secret_service "code.gitea.io/gitea/services/secrets" +) + +// ListActionsSecrets list an organization's actions secrets +func (Action) ListActionsSecrets(ctx *context.APIContext) { + // swagger:operation GET /orgs/{org}/actions/secrets organization orgListActionsSecrets + // --- + // summary: List an organization's actions secrets + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/SecretList" + // "404": + // "$ref": "#/responses/notFound" + + opts := &secret_model.FindSecretsOptions{ + OwnerID: ctx.Org.Organization.ID, + ListOptions: utils.GetListOptions(ctx), + } + + secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts) + if err != nil { + ctx.InternalServerError(err) + return + } + + apiSecrets := make([]*api.Secret, len(secrets)) + for k, v := range secrets { + apiSecrets[k] = &api.Secret{ + Name: v.Name, + Created: v.CreatedUnix.AsTime(), + } + } + + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, apiSecrets) +} + +// create or update one secret of the organization +func (Action) CreateOrUpdateSecret(ctx *context.APIContext) { + // swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret + // --- + // summary: Create or Update a secret value in an organization + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of organization + // type: string + // required: true + // - name: secretname + // in: path + // description: name of the secret + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/CreateOrUpdateSecretOption" + // responses: + // "201": + // description: response when creating a secret + // "204": + // description: response when updating a secret + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption) + + _, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"), opt.Data) + if err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err) + } else if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err) + } else { + ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err) + } + return + } + + if created { + ctx.Status(http.StatusCreated) + } else { + ctx.Status(http.StatusNoContent) + } +} + +// DeleteSecret delete one secret of the organization +func (Action) DeleteSecret(ctx *context.APIContext) { + // swagger:operation DELETE /orgs/{org}/actions/secrets/{secretname} organization deleteOrgSecret + // --- + // summary: Delete a secret in an organization + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of organization + // type: string + // required: true + // - name: secretname + // in: path + // description: name of the secret + // type: string + // required: true + // responses: + // "204": + // description: delete one secret of the organization + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname")) + if err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "DeleteSecret", err) + } else if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "DeleteSecret", err) + } else { + ctx.Error(http.StatusInternalServerError, "DeleteSecret", err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization +// GetRegistrationToken returns the token to register org runners +func (Action) GetRegistrationToken(ctx *context.APIContext) { + // swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken + // --- + // summary: Get an organization's actions runner registration token + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/RegistrationToken" + + shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0) +} + +// ListVariables list org-level variables +func (Action) ListVariables(ctx *context.APIContext) { + // swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList + // --- + // summary: Get an org-level variables list + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/VariableList" + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{ + OwnerID: ctx.Org.Organization.ID, + ListOptions: utils.GetListOptions(ctx), + }) + if err != nil { + ctx.Error(http.StatusInternalServerError, "FindVariables", err) + return + } + + variables := make([]*api.ActionVariable, len(vars)) + for i, v := range vars { + variables[i] = &api.ActionVariable{ + OwnerID: v.OwnerID, + RepoID: v.RepoID, + Name: v.Name, + Data: v.Data, + } + } + + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, variables) +} + +// GetVariable get an org-level variable +func (Action) GetVariable(ctx *context.APIContext) { + // swagger:operation GET /orgs/{org}/actions/variables/{variablename} organization getOrgVariable + // --- + // summary: Get an org-level variable + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: variablename + // in: path + // description: name of the variable + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ActionVariable" + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ + OwnerID: ctx.Org.Organization.ID, + Name: ctx.Params("variablename"), + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "GetVariable", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetVariable", err) + } + return + } + + variable := &api.ActionVariable{ + OwnerID: v.OwnerID, + RepoID: v.RepoID, + Name: v.Name, + Data: v.Data, + } + + ctx.JSON(http.StatusOK, variable) +} + +// DeleteVariable delete an org-level variable +func (Action) DeleteVariable(ctx *context.APIContext) { + // swagger:operation DELETE /orgs/{org}/actions/variables/{variablename} organization deleteOrgVariable + // --- + // summary: Delete an org-level variable + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: variablename + // in: path + // description: name of the variable + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ActionVariable" + // "201": + // description: response when deleting a variable + // "204": + // description: response when deleting a variable + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + if err := actions_service.DeleteVariableByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("variablename")); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err) + } else if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "DeleteVariableByName", err) + } else { + ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +// CreateVariable create an org-level variable +func (Action) CreateVariable(ctx *context.APIContext) { + // swagger:operation POST /orgs/{org}/actions/variables/{variablename} organization createOrgVariable + // --- + // summary: Create an org-level variable + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: variablename + // in: path + // description: name of the variable + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/CreateVariableOption" + // responses: + // "201": + // description: response when creating an org-level variable + // "204": + // description: response when creating an org-level variable + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + opt := web.GetForm(ctx).(*api.CreateVariableOption) + + ownerID := ctx.Org.Organization.ID + variableName := ctx.Params("variablename") + + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ + OwnerID: ownerID, + Name: variableName, + }) + if err != nil && !errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusInternalServerError, "GetVariable", err) + return + } + if v != nil && v.ID > 0 { + ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName)) + return + } + + if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "CreateVariable", err) + } else { + ctx.Error(http.StatusInternalServerError, "CreateVariable", err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +// UpdateVariable update an org-level variable +func (Action) UpdateVariable(ctx *context.APIContext) { + // swagger:operation PUT /orgs/{org}/actions/variables/{variablename} organization updateOrgVariable + // --- + // summary: Update an org-level variable + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: org + // in: path + // description: name of the organization + // type: string + // required: true + // - name: variablename + // in: path + // description: name of the variable + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UpdateVariableOption" + // responses: + // "201": + // description: response when updating an org-level variable + // "204": + // description: response when updating an org-level variable + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + opt := web.GetForm(ctx).(*api.UpdateVariableOption) + + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ + OwnerID: ctx.Org.Organization.ID, + Name: ctx.Params("variablename"), + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "GetVariable", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetVariable", err) + } + return + } + + if opt.Name == "" { + opt.Name = ctx.Params("variablename") + } + if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "UpdateVariable", err) + } else { + ctx.Error(http.StatusInternalServerError, "UpdateVariable", err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +var _ actions_service.API = new(Action) + +// Action implements actions_service.API +type Action struct{} + +// NewAction creates a new Action service +func NewAction() actions_service.API { + return Action{} +} diff --git a/routers/api/v1/org/runners.go b/routers/api/v1/org/runners.go deleted file mode 100644 index 2a52bd8778..0000000000 --- a/routers/api/v1/org/runners.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package org - -import ( - "code.gitea.io/gitea/routers/api/v1/shared" - "code.gitea.io/gitea/services/context" -) - -// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization - -// GetRegistrationToken returns the token to register org runners -func GetRegistrationToken(ctx *context.APIContext) { - // swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken - // --- - // summary: Get an organization's actions runner registration token - // produces: - // - application/json - // parameters: - // - name: org - // in: path - // description: name of the organization - // type: string - // required: true - // responses: - // "200": - // "$ref": "#/responses/RegistrationToken" - - shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0) -} diff --git a/routers/api/v1/org/secrets.go b/routers/api/v1/org/secrets.go deleted file mode 100644 index abb6bb26c4..0000000000 --- a/routers/api/v1/org/secrets.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package org - -import ( - "errors" - "net/http" - - "code.gitea.io/gitea/models/db" - secret_model "code.gitea.io/gitea/models/secret" - api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/modules/web" - "code.gitea.io/gitea/routers/api/v1/utils" - "code.gitea.io/gitea/services/context" - secret_service "code.gitea.io/gitea/services/secrets" -) - -// ListActionsSecrets list an organization's actions secrets -func ListActionsSecrets(ctx *context.APIContext) { - // swagger:operation GET /orgs/{org}/actions/secrets organization orgListActionsSecrets - // --- - // summary: List an organization's actions secrets - // produces: - // - application/json - // parameters: - // - name: org - // in: path - // description: name of the organization - // type: string - // required: true - // - name: page - // in: query - // description: page number of results to return (1-based) - // type: integer - // - name: limit - // in: query - // description: page size of results - // type: integer - // responses: - // "200": - // "$ref": "#/responses/SecretList" - // "404": - // "$ref": "#/responses/notFound" - - opts := &secret_model.FindSecretsOptions{ - OwnerID: ctx.Org.Organization.ID, - ListOptions: utils.GetListOptions(ctx), - } - - secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts) - if err != nil { - ctx.InternalServerError(err) - return - } - - apiSecrets := make([]*api.Secret, len(secrets)) - for k, v := range secrets { - apiSecrets[k] = &api.Secret{ - Name: v.Name, - Created: v.CreatedUnix.AsTime(), - } - } - - ctx.SetTotalCountHeader(count) - ctx.JSON(http.StatusOK, apiSecrets) -} - -// create or update one secret of the organization -func CreateOrUpdateSecret(ctx *context.APIContext) { - // swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret - // --- - // summary: Create or Update a secret value in an organization - // consumes: - // - application/json - // produces: - // - application/json - // parameters: - // - name: org - // in: path - // description: name of organization - // type: string - // required: true - // - name: secretname - // in: path - // description: name of the secret - // type: string - // required: true - // - name: body - // in: body - // schema: - // "$ref": "#/definitions/CreateOrUpdateSecretOption" - // responses: - // "201": - // description: response when creating a secret - // "204": - // description: response when updating a secret - // "400": - // "$ref": "#/responses/error" - // "404": - // "$ref": "#/responses/notFound" - - opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption) - - _, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"), opt.Data) - if err != nil { - if errors.Is(err, util.ErrInvalidArgument) { - ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err) - } else if errors.Is(err, util.ErrNotExist) { - ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err) - } else { - ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err) - } - return - } - - if created { - ctx.Status(http.StatusCreated) - } else { - ctx.Status(http.StatusNoContent) - } -} - -// DeleteSecret delete one secret of the organization -func DeleteSecret(ctx *context.APIContext) { - // swagger:operation DELETE /orgs/{org}/actions/secrets/{secretname} organization deleteOrgSecret - // --- - // summary: Delete a secret in an organization - // consumes: - // - application/json - // produces: - // - application/json - // parameters: - // - name: org - // in: path - // description: name of organization - // type: string - // required: true - // - name: secretname - // in: path - // description: name of the secret - // type: string - // required: true - // responses: - // "204": - // description: delete one secret of the organization - // "400": - // "$ref": "#/responses/error" - // "404": - // "$ref": "#/responses/notFound" - - err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname")) - if err != nil { - if errors.Is(err, util.ErrInvalidArgument) { - ctx.Error(http.StatusBadRequest, "DeleteSecret", err) - } else if errors.Is(err, util.ErrNotExist) { - ctx.Error(http.StatusNotFound, "DeleteSecret", err) - } else { - ctx.Error(http.StatusInternalServerError, "DeleteSecret", err) - } - return - } - - ctx.Status(http.StatusNoContent) -} diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index e0af276c71..311cfca6e9 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -7,15 +7,78 @@ import ( "errors" "net/http" + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + secret_model "code.gitea.io/gitea/models/secret" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers/api/v1/shared" + "code.gitea.io/gitea/routers/api/v1/utils" + actions_service "code.gitea.io/gitea/services/actions" "code.gitea.io/gitea/services/context" secret_service "code.gitea.io/gitea/services/secrets" ) +// ListActionsSecrets list an repo's actions secrets +func (Action) ListActionsSecrets(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/actions/secrets repository repoListActionsSecrets + // --- + // summary: List an repo's actions secrets + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repository + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repository + // type: string + // required: true + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/SecretList" + // "404": + // "$ref": "#/responses/notFound" + + repo := ctx.Repo.Repository + + opts := &secret_model.FindSecretsOptions{ + RepoID: repo.ID, + ListOptions: utils.GetListOptions(ctx), + } + + secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts) + if err != nil { + ctx.InternalServerError(err) + return + } + + apiSecrets := make([]*api.Secret, len(secrets)) + for k, v := range secrets { + apiSecrets[k] = &api.Secret{ + Name: v.Name, + Created: v.CreatedUnix.AsTime(), + } + } + + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, apiSecrets) +} + // create or update one secret of the repository -func CreateOrUpdateSecret(ctx *context.APIContext) { +func (Action) CreateOrUpdateSecret(ctx *context.APIContext) { // swagger:operation PUT /repos/{owner}/{repo}/actions/secrets/{secretname} repository updateRepoSecret // --- // summary: Create or Update a secret value in a repository @@ -78,7 +141,7 @@ func CreateOrUpdateSecret(ctx *context.APIContext) { } // DeleteSecret delete one secret of the repository -func DeleteSecret(ctx *context.APIContext) { +func (Action) DeleteSecret(ctx *context.APIContext) { // swagger:operation DELETE /repos/{owner}/{repo}/actions/secrets/{secretname} repository deleteRepoSecret // --- // summary: Delete a secret in a repository @@ -127,3 +190,330 @@ func DeleteSecret(ctx *context.APIContext) { ctx.Status(http.StatusNoContent) } + +// GetVariable get a repo-level variable +func (Action) GetVariable(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/actions/variables/{variablename} repository getRepoVariable + // --- + // summary: Get a repo-level variable + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: name of the owner + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repository + // type: string + // required: true + // - name: variablename + // in: path + // description: name of the variable + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ActionVariable" + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ + RepoID: ctx.Repo.Repository.ID, + Name: ctx.Params("variablename"), + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "GetVariable", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetVariable", err) + } + return + } + + variable := &api.ActionVariable{ + OwnerID: v.OwnerID, + RepoID: v.RepoID, + Name: v.Name, + Data: v.Data, + } + + ctx.JSON(http.StatusOK, variable) +} + +// DeleteVariable delete a repo-level variable +func (Action) DeleteVariable(ctx *context.APIContext) { + // swagger:operation DELETE /repos/{owner}/{repo}/actions/variables/{variablename} repository deleteRepoVariable + // --- + // summary: Delete a repo-level variable + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: name of the owner + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repository + // type: string + // required: true + // - name: variablename + // in: path + // description: name of the variable + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ActionVariable" + // "201": + // description: response when deleting a variable + // "204": + // description: response when deleting a variable + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + if err := actions_service.DeleteVariableByName(ctx, 0, ctx.Repo.Repository.ID, ctx.Params("variablename")); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err) + } else if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "DeleteVariableByName", err) + } else { + ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +// CreateVariable create a repo-level variable +func (Action) CreateVariable(ctx *context.APIContext) { + // swagger:operation POST /repos/{owner}/{repo}/actions/variables/{variablename} repository createRepoVariable + // --- + // summary: Create a repo-level variable + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: name of the owner + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repository + // type: string + // required: true + // - name: variablename + // in: path + // description: name of the variable + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/CreateVariableOption" + // responses: + // "201": + // description: response when creating a repo-level variable + // "204": + // description: response when creating a repo-level variable + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + opt := web.GetForm(ctx).(*api.CreateVariableOption) + + repoID := ctx.Repo.Repository.ID + variableName := ctx.Params("variablename") + + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ + RepoID: repoID, + Name: variableName, + }) + if err != nil && !errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusInternalServerError, "GetVariable", err) + return + } + if v != nil && v.ID > 0 { + ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName)) + return + } + + if _, err := actions_service.CreateVariable(ctx, 0, repoID, variableName, opt.Value); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "CreateVariable", err) + } else { + ctx.Error(http.StatusInternalServerError, "CreateVariable", err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +// UpdateVariable update a repo-level variable +func (Action) UpdateVariable(ctx *context.APIContext) { + // swagger:operation PUT /repos/{owner}/{repo}/actions/variables/{variablename} repository updateRepoVariable + // --- + // summary: Update a repo-level variable + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: name of the owner + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repository + // type: string + // required: true + // - name: variablename + // in: path + // description: name of the variable + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UpdateVariableOption" + // responses: + // "201": + // description: response when updating a repo-level variable + // "204": + // description: response when updating a repo-level variable + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + opt := web.GetForm(ctx).(*api.UpdateVariableOption) + + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ + RepoID: ctx.Repo.Repository.ID, + Name: ctx.Params("variablename"), + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "GetVariable", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetVariable", err) + } + return + } + + if opt.Name == "" { + opt.Name = ctx.Params("variablename") + } + if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "UpdateVariable", err) + } else { + ctx.Error(http.StatusInternalServerError, "UpdateVariable", err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +// ListVariables list repo-level variables +func (Action) ListVariables(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/actions/variables repository getRepoVariablesList + // --- + // summary: Get repo-level variables list + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: name of the owner + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repository + // type: string + // required: true + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/VariableList" + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{ + RepoID: ctx.Repo.Repository.ID, + ListOptions: utils.GetListOptions(ctx), + }) + if err != nil { + ctx.Error(http.StatusInternalServerError, "FindVariables", err) + return + } + + variables := make([]*api.ActionVariable, len(vars)) + for i, v := range vars { + variables[i] = &api.ActionVariable{ + OwnerID: v.OwnerID, + RepoID: v.RepoID, + Name: v.Name, + } + } + + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, variables) +} + +// GetRegistrationToken returns the token to register repo runners +func (Action) GetRegistrationToken(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/runners/registration-token repository repoGetRunnerRegistrationToken + // --- + // summary: Get a repository's actions runner registration token + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/RegistrationToken" + + shared.GetRegistrationToken(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID) +} + +var _ actions_service.API = new(Action) + +// Action implements actions_service.API +type Action struct{} + +// NewAction creates a new Action service +func NewAction() actions_service.API { + return Action{} +} diff --git a/routers/api/v1/repo/compare.go b/routers/api/v1/repo/compare.go new file mode 100644 index 0000000000..cfd61d768c --- /dev/null +++ b/routers/api/v1/repo/compare.go @@ -0,0 +1,99 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "net/http" + "strings" + + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/gitrepo" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/convert" +) + +// CompareDiff compare two branches or commits +func CompareDiff(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/compare/{basehead} repository repoCompareDiff + // --- + // summary: Get commit comparison information + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: basehead + // in: path + // description: compare two branches or commits + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/Compare" + // "404": + // "$ref": "#/responses/notFound" + + if ctx.Repo.GitRepo == nil { + gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository) + if err != nil { + ctx.Error(http.StatusInternalServerError, "OpenRepository", err) + return + } + ctx.Repo.GitRepo = gitRepo + defer gitRepo.Close() + } + + infoPath := ctx.Params("*") + infos := []string{ctx.Repo.Repository.DefaultBranch, ctx.Repo.Repository.DefaultBranch} + if infoPath != "" { + infos = strings.SplitN(infoPath, "...", 2) + if len(infos) != 2 { + if infos = strings.SplitN(infoPath, "..", 2); len(infos) != 2 { + infos = []string{ctx.Repo.Repository.DefaultBranch, infoPath} + } + } + } + + _, _, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{ + Base: infos[0], + Head: infos[1], + }) + if ctx.Written() { + return + } + defer headGitRepo.Close() + + verification := ctx.FormString("verification") == "" || ctx.FormBool("verification") + files := ctx.FormString("files") == "" || ctx.FormBool("files") + + apiCommits := make([]*api.Commit, 0, len(ci.Commits)) + userCache := make(map[string]*user_model.User) + for i := 0; i < len(ci.Commits); i++ { + apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ci.Commits[i], userCache, + convert.ToCommitOptions{ + Stat: true, + Verification: verification, + Files: files, + }) + if err != nil { + ctx.ServerError("toCommit", err) + return + } + apiCommits = append(apiCommits, apiCommit) + } + + ctx.JSON(http.StatusOK, &api.Compare{ + TotalCommits: len(ci.Commits), + Commits: apiCommits, + }) +} diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 712cba7455..34ccc929a5 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -302,7 +302,7 @@ func GetArchive(ctx *context.APIContext) { func archiveDownload(ctx *context.APIContext) { uri := ctx.Params("*") - aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri) + aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri) if err != nil { if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) { ctx.Error(http.StatusBadRequest, "unknown archive format", err) @@ -326,6 +326,12 @@ func archiveDownload(ctx *context.APIContext) { func download(ctx *context.APIContext, archiveName string, archiver *repo_model.RepoArchiver) { downloadName := ctx.Repo.Repository.Name + "-" + archiveName + // Add nix format link header so tarballs lock correctly: + // https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md + ctx.Resp.Header().Add("Link", fmt.Sprintf("<%s/archive/%s.tar.gz?rev=%s>; rel=\"immutable\"", + ctx.Repo.Repository.APIURL(), + archiver.CommitID, archiver.CommitID)) + rPath := archiver.RelativePath() if setting.RepoArchive.Storage.MinioConfig.ServeDirect { // If we have a signed url (S3, object storage), redirect to this directly. diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 0d304dd66d..35c1fdcc0c 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -217,7 +217,6 @@ func SearchIssues(ctx *context.APIContext) { var includedAnyLabels []int64 { - labels := ctx.FormTrim("labels") var includedLabelNames []string if len(labels) > 0 { diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index c3005dee3b..819859b991 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -323,10 +323,6 @@ func ListRepoIssueComments(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "LoadIssues", err) return } - if err := comments.LoadPosters(ctx); err != nil { - ctx.Error(http.StatusInternalServerError, "LoadPosters", err) - return - } if err := comments.LoadAttachments(ctx); err != nil { ctx.Error(http.StatusInternalServerError, "LoadAttachments", err) return diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go index 864644e1ef..2a896de4fe 100644 --- a/routers/api/v1/repo/mirror.go +++ b/routers/api/v1/repo/mirror.go @@ -180,7 +180,6 @@ func ListPushMirrors(ctx *context.APIContext) { if err == nil { responsePushMirrors = append(responsePushMirrors, m) } - } ctx.SetLinkHeader(len(responsePushMirrors), utils.GetListOptions(ctx).PageSize) ctx.SetTotalCountHeader(count) diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index eec3c49bc4..852ec78ade 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -1058,7 +1058,6 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) isSameRepo = true headUser = ctx.Repo.Owner headBranch = headInfos[0] - } else if len(headInfos) == 2 { headUser, err = user_model.GetUserByName(ctx, headInfos[0]) if err != nil { @@ -1072,18 +1071,16 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) headBranch = headInfos[1] // The head repository can also point to the same repo isSameRepo = ctx.Repo.Owner.ID == headUser.ID - } else { ctx.NotFound() return nil, nil, nil, nil, "", "" } ctx.Repo.PullRequest.SameRepo = isSameRepo - log.Info("Base branch: %s", baseBranch) - log.Info("Repo path: %s", ctx.Repo.GitRepo.Path) + log.Trace("Repo path: %q, base branch: %q, head branch: %q", ctx.Repo.GitRepo.Path, baseBranch, headBranch) // Check if base branch is valid. - if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) { - ctx.NotFound("IsBranchExist") + if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) && !ctx.Repo.GitRepo.IsTagExist(baseBranch) { + ctx.NotFound("BaseNotExist") return nil, nil, nil, nil, "", "" } @@ -1146,7 +1143,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) } // Check if head branch is valid. - if !headGitRepo.IsBranchExist(headBranch) { + if !headGitRepo.IsBranchExist(headBranch) && !headGitRepo.IsTagExist(headBranch) { headGitRepo.Close() ctx.NotFound() return nil, nil, nil, nil, "", "" diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 77c0d25e2a..6799e43c73 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -4,6 +4,7 @@ package repo import ( + "errors" "fmt" "net/http" "strings" @@ -519,7 +520,11 @@ func CreatePullReview(ctx *context.APIContext) { // create review and associate all pending review comments review, _, err := pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, opts.CommitID, nil) if err != nil { - ctx.Error(http.StatusInternalServerError, "SubmitReview", err) + if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) { + ctx.Error(http.StatusUnprocessableEntity, "", err) + } else { + ctx.Error(http.StatusInternalServerError, "SubmitReview", err) + } return } @@ -607,7 +612,11 @@ func SubmitPullReview(ctx *context.APIContext) { // create review and associate all pending review comments review, _, err = pull_service.SubmitReview(ctx, ctx.Doer, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, headCommitID, nil) if err != nil { - ctx.Error(http.StatusInternalServerError, "SubmitReview", err) + if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) { + ctx.Error(http.StatusUnprocessableEntity, "", err) + } else { + ctx.Error(http.StatusInternalServerError, "SubmitReview", err) + } return } @@ -875,7 +884,6 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions } if ctx.Repo.Repository.Owner.IsOrganization() && len(opts.TeamReviewers) > 0 { - teamReviewers := make([]*organization.Team, 0, len(opts.TeamReviewers)) for _, t := range opts.TeamReviewers { var teamReviewer *organization.Team diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index c6495c3de8..057282b210 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -231,17 +231,18 @@ func CreateRelease(ctx *context.APIContext) { form.Target = ctx.Repo.Repository.DefaultBranch } rel = &repo_model.Release{ - RepoID: ctx.Repo.Repository.ID, - PublisherID: ctx.Doer.ID, - Publisher: ctx.Doer, - TagName: form.TagName, - Target: form.Target, - Title: form.Title, - Note: form.Note, - IsDraft: form.IsDraft, - IsPrerelease: form.IsPrerelease, - IsTag: false, - Repo: ctx.Repo.Repository, + RepoID: ctx.Repo.Repository.ID, + PublisherID: ctx.Doer.ID, + Publisher: ctx.Doer, + TagName: form.TagName, + Target: form.Target, + Title: form.Title, + Note: form.Note, + IsDraft: form.IsDraft, + IsPrerelease: form.IsPrerelease, + HideArchiveLinks: form.HideArchiveLinks, + IsTag: false, + Repo: ctx.Repo.Repository, } if err := release_service.CreateRelease(ctx.Repo.GitRepo, rel, nil, ""); err != nil { if repo_model.IsErrReleaseAlreadyExist(err) { @@ -261,6 +262,7 @@ func CreateRelease(ctx *context.APIContext) { rel.Note = form.Note rel.IsDraft = form.IsDraft rel.IsPrerelease = form.IsPrerelease + rel.HideArchiveLinks = form.HideArchiveLinks rel.PublisherID = ctx.Doer.ID rel.IsTag = false rel.Repo = ctx.Repo.Repository @@ -341,6 +343,9 @@ func EditRelease(ctx *context.APIContext) { if form.IsPrerelease != nil { rel.IsPrerelease = *form.IsPrerelease } + if form.HideArchiveLinks != nil { + rel.HideArchiveLinks = *form.HideArchiveLinks + } if err := release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, nil, nil, nil, false); err != nil { ctx.Error(http.StatusInternalServerError, "UpdateRelease", err) return diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 562c3eb64c..66eb227c19 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -845,6 +845,15 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { newHasWiki = *opts.HasWiki } if currHasWiki || newHasWiki { + wikiPermissions := repo.MustGetUnit(ctx, unit_model.TypeWiki).DefaultPermissions + if opts.GloballyEditableWiki != nil { + if *opts.GloballyEditableWiki { + wikiPermissions = repo_model.UnitAccessModeWrite + } else { + wikiPermissions = repo_model.UnitAccessModeRead + } + } + if newHasWiki && opts.ExternalWiki != nil && !unit_model.TypeExternalWiki.UnitGlobalDisabled() { // Check that values are valid if !validation.IsValidExternalURL(opts.ExternalWiki.ExternalWikiURL) { @@ -864,9 +873,10 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { } else if newHasWiki && opts.ExternalWiki == nil && !unit_model.TypeWiki.UnitGlobalDisabled() { config := &repo_model.UnitConfig{} units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeWiki, - Config: config, + RepoID: repo.ID, + Type: unit_model.TypeWiki, + Config: config, + DefaultPermissions: wikiPermissions, }) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) } else if !newHasWiki { @@ -876,6 +886,14 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { if !unit_model.TypeWiki.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) } + } else if *opts.GloballyEditableWiki { + config := &repo_model.UnitConfig{} + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypeWiki, + Config: config, + DefaultPermissions: wikiPermissions, + }) } } @@ -1076,7 +1094,6 @@ func updateMirror(ctx *context.APIContext, opts api.EditRepoOption) error { // update MirrorInterval if opts.MirrorInterval != nil { - // MirrorInterval should be a duration interval, err := time.ParseDuration(*opts.MirrorInterval) if err != nil { diff --git a/routers/api/v1/repo/runners.go b/routers/api/v1/repo/runners.go deleted file mode 100644 index fe133b311d..0000000000 --- a/routers/api/v1/repo/runners.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package repo - -import ( - "code.gitea.io/gitea/routers/api/v1/shared" - "code.gitea.io/gitea/services/context" -) - -// GetRegistrationToken returns the token to register repo runners -func GetRegistrationToken(ctx *context.APIContext) { - // swagger:operation GET /repos/{owner}/{repo}/runners/registration-token repository repoGetRunnerRegistrationToken - // --- - // summary: Get a repository's actions runner registration token - // produces: - // - application/json - // parameters: - // - name: owner - // in: path - // description: owner of the repo - // type: string - // required: true - // - name: repo - // in: path - // description: name of the repo - // type: string - // required: true - // responses: - // "200": - // "$ref": "#/responses/RegistrationToken" - - shared.GetRegistrationToken(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID) -} diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go index 84ec3dd91c..b498f0e792 100644 --- a/routers/api/v1/repo/tag.go +++ b/routers/api/v1/repo/tag.go @@ -60,6 +60,12 @@ func ListTags(ctx *context.APIContext) { apiTags := make([]*api.Tag, len(tags)) for i := range tags { + tags[i].ArchiveDownloadCount, err = repo_model.GetArchiveDownloadCountForTagName(ctx, ctx.Repo.Repository.ID, tags[i].Name) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetTagArchiveDownloadCountForName", err) + return + } + apiTags[i] = convert.ToTag(ctx.Repo.Repository, tags[i]) } @@ -111,6 +117,13 @@ func GetAnnotatedTag(ctx *context.APIContext) { if err != nil { ctx.Error(http.StatusBadRequest, "GetAnnotatedTag", err) } + + tag.ArchiveDownloadCount, err = repo_model.GetArchiveDownloadCountForTagName(ctx, ctx.Repo.Repository.ID, tag.Name) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetTagArchiveDownloadCountForName", err) + return + } + ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx, ctx.Repo.Repository, tag, commit)) } } @@ -150,6 +163,13 @@ func GetTag(ctx *context.APIContext) { ctx.NotFound(tagName) return } + + tag.ArchiveDownloadCount, err = repo_model.GetArchiveDownloadCountForTagName(ctx, ctx.Repo.Repository.ID, tag.Name) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetTagArchiveDownloadCountForName", err) + return + } + ctx.JSON(http.StatusOK, convert.ToTag(ctx.Repo.Repository, tag)) } @@ -218,6 +238,13 @@ func CreateTag(ctx *context.APIContext) { ctx.InternalServerError(err) return } + + tag.ArchiveDownloadCount, err = repo_model.GetArchiveDownloadCountForTagName(ctx, ctx.Repo.Repository.ID, tag.Name) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetTagArchiveDownloadCountForName", err) + return + } + ctx.JSON(http.StatusCreated, convert.ToTag(ctx.Repo.Repository, tag)) } diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index f18ea087c4..1b92c7bceb 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -193,7 +193,7 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi } // get commit count - wiki revisions - commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename) + commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.GetWikiBranchName(), pageFilename) // Get last change information. lastCommit, err := wikiRepo.GetCommitByPath(pageFilename) @@ -432,7 +432,7 @@ func ListPageRevisions(ctx *context.APIContext) { } // get commit count - wiki revisions - commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename) + commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.GetWikiBranchName(), pageFilename) page := ctx.FormInt("page") if page <= 1 { @@ -442,7 +442,7 @@ func ListPageRevisions(ctx *context.APIContext) { // get Commit Count commitsHistory, err := wikiRepo.CommitsByFileAndRange( git.CommitsByFileAndRangeOptions{ - Revision: "master", + Revision: ctx.Repo.Repository.GetWikiBranchName(), File: pageFilename, Page: page, }) @@ -478,7 +478,6 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) { wikiRepo, err := gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository) if err != nil { - if git.IsErrNotExist(err) || err.Error() == "no such file or directory" { ctx.NotFound(err) } else { @@ -487,7 +486,7 @@ func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) return nil, nil } - commit, err := wikiRepo.GetBranchCommit("master") + commit, err := wikiRepo.GetBranchCommit(ctx.Repo.Repository.GetWikiBranchName()) if err != nil { if git.IsErrNotExist(err) { ctx.NotFound(err) diff --git a/routers/api/v1/swagger/action.go b/routers/api/v1/swagger/action.go index 3771780718..665f4d0b85 100644 --- a/routers/api/v1/swagger/action.go +++ b/routers/api/v1/swagger/action.go @@ -18,3 +18,17 @@ type swaggerResponseSecret struct { // in:body Body api.Secret `json:"body"` } + +// ActionVariable +// swagger:response ActionVariable +type swaggerResponseActionVariable struct { + // in:body + Body api.ActionVariable `json:"body"` +} + +// VariableList +// swagger:response VariableList +type swaggerResponseVariableList struct { + // in:body + Body []api.ActionVariable `json:"body"` +} diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index 2886b865e8..6a94c21002 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -199,4 +199,10 @@ type swaggerParameterBodies struct { // in:body CreateOrUpdateSecretOption api.CreateOrUpdateSecretOption + + // in:body + CreateVariableOption api.CreateVariableOption + + // in:body + UpdateVariableOption api.UpdateVariableOption } diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go index c7fa98a697..b55ea1d0a9 100644 --- a/routers/api/v1/swagger/repo.go +++ b/routers/api/v1/swagger/repo.go @@ -421,3 +421,9 @@ type swaggerBlockedUserList struct { // in:body Body []api.BlockedUser `json:"body"` } + +// swagger:response Compare +type swaggerCompare struct { + // in:body + Body api.Compare `json:"body"` +} diff --git a/routers/api/v1/user/action.go b/routers/api/v1/user/action.go index babb8c0cf7..bf78c2c864 100644 --- a/routers/api/v1/user/action.go +++ b/routers/api/v1/user/action.go @@ -7,9 +7,13 @@ import ( "errors" "net/http" + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers/api/v1/utils" + actions_service "code.gitea.io/gitea/services/actions" "code.gitea.io/gitea/services/context" secret_service "code.gitea.io/gitea/services/secrets" ) @@ -101,3 +105,249 @@ func DeleteSecret(ctx *context.APIContext) { ctx.Status(http.StatusNoContent) } + +// CreateVariable create a user-level variable +func CreateVariable(ctx *context.APIContext) { + // swagger:operation POST /user/actions/variables/{variablename} user createUserVariable + // --- + // summary: Create a user-level variable + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: variablename + // in: path + // description: name of the variable + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/CreateVariableOption" + // responses: + // "201": + // description: response when creating a variable + // "204": + // description: response when creating a variable + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + opt := web.GetForm(ctx).(*api.CreateVariableOption) + + ownerID := ctx.Doer.ID + variableName := ctx.Params("variablename") + + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ + OwnerID: ownerID, + Name: variableName, + }) + if err != nil && !errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusInternalServerError, "GetVariable", err) + return + } + if v != nil && v.ID > 0 { + ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName)) + return + } + + if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "CreateVariable", err) + } else { + ctx.Error(http.StatusInternalServerError, "CreateVariable", err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +// UpdateVariable update a user-level variable which is created by current doer +func UpdateVariable(ctx *context.APIContext) { + // swagger:operation PUT /user/actions/variables/{variablename} user updateUserVariable + // --- + // summary: Update a user-level variable which is created by current doer + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: variablename + // in: path + // description: name of the variable + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UpdateVariableOption" + // responses: + // "201": + // description: response when updating a variable + // "204": + // description: response when updating a variable + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + opt := web.GetForm(ctx).(*api.UpdateVariableOption) + + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ + OwnerID: ctx.Doer.ID, + Name: ctx.Params("variablename"), + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "GetVariable", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetVariable", err) + } + return + } + + if opt.Name == "" { + opt.Name = ctx.Params("variablename") + } + if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "UpdateVariable", err) + } else { + ctx.Error(http.StatusInternalServerError, "UpdateVariable", err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +// DeleteVariable delete a user-level variable which is created by current doer +func DeleteVariable(ctx *context.APIContext) { + // swagger:operation DELETE /user/actions/variables/{variablename} user deleteUserVariable + // --- + // summary: Delete a user-level variable which is created by current doer + // produces: + // - application/json + // parameters: + // - name: variablename + // in: path + // description: name of the variable + // type: string + // required: true + // responses: + // "201": + // description: response when deleting a variable + // "204": + // description: response when deleting a variable + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + if err := actions_service.DeleteVariableByName(ctx, ctx.Doer.ID, 0, ctx.Params("variablename")); err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err) + } else if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "DeleteVariableByName", err) + } else { + ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err) + } + return + } + + ctx.Status(http.StatusNoContent) +} + +// GetVariable get a user-level variable which is created by current doer +func GetVariable(ctx *context.APIContext) { + // swagger:operation GET /user/actions/variables/{variablename} user getUserVariable + // --- + // summary: Get a user-level variable which is created by current doer + // produces: + // - application/json + // parameters: + // - name: variablename + // in: path + // description: name of the variable + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/ActionVariable" + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{ + OwnerID: ctx.Doer.ID, + Name: ctx.Params("variablename"), + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Error(http.StatusNotFound, "GetVariable", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetVariable", err) + } + return + } + + variable := &api.ActionVariable{ + OwnerID: v.OwnerID, + RepoID: v.RepoID, + Name: v.Name, + Data: v.Data, + } + + ctx.JSON(http.StatusOK, variable) +} + +// ListVariables list user-level variables +func ListVariables(ctx *context.APIContext) { + // swagger:operation GET /user/actions/variables user getUserVariablesList + // --- + // summary: Get the user-level list of variables which is created by current doer + // produces: + // - application/json + // parameters: + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/VariableList" + // "400": + // "$ref": "#/responses/error" + // "404": + // "$ref": "#/responses/notFound" + + vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{ + OwnerID: ctx.Doer.ID, + ListOptions: utils.GetListOptions(ctx), + }) + if err != nil { + ctx.Error(http.StatusInternalServerError, "FindVariables", err) + return + } + + variables := make([]*api.ActionVariable, len(vars)) + for i, v := range vars { + variables[i] = &api.ActionVariable{ + OwnerID: v.OwnerID, + RepoID: v.RepoID, + Name: v.Name, + Data: v.Data, + } + } + + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, variables) +} diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go index dcf5da0b2e..5a2f995e1b 100644 --- a/routers/api/v1/user/gpg_key.go +++ b/routers/api/v1/user/gpg_key.go @@ -10,6 +10,7 @@ import ( asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" @@ -133,7 +134,7 @@ func GetGPGKey(ctx *context.APIContext) { // CreateUserGPGKey creates new GPG key to given user by ID. func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) { - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) return } @@ -274,7 +275,7 @@ func DeleteGPGKey(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) return } diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go index bcbfd93bd3..d9456e7ec6 100644 --- a/routers/api/v1/user/key.go +++ b/routers/api/v1/user/key.go @@ -199,7 +199,7 @@ func GetPublicKey(ctx *context.APIContext) { // CreateUserPublicKey creates new public key to given user by ID. func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) { - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) return } @@ -269,7 +269,7 @@ func DeletePublicKey(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) return } diff --git a/routers/common/compare.go b/routers/common/compare.go new file mode 100644 index 0000000000..4d1cc2f0d8 --- /dev/null +++ b/routers/common/compare.go @@ -0,0 +1,21 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package common + +import ( + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" +) + +// CompareInfo represents the collected results from ParseCompareInfo +type CompareInfo struct { + HeadUser *user_model.User + HeadRepo *repo_model.Repository + HeadGitRepo *git.Repository + CompareInfo *git.CompareInfo + BaseBranch string + HeadBranch string + DirectComparison bool +} diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index cb356a184a..da9fa20082 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -259,7 +259,6 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r UserMsg: fmt.Sprintf("branch %s is protected from force push", branchName), }) return - } } diff --git a/routers/utils/utils.go b/routers/utils/utils.go index 1f4d11fd3c..3035073d5c 100644 --- a/routers/utils/utils.go +++ b/routers/utils/utils.go @@ -5,26 +5,10 @@ package utils import ( "html" - "net/url" "strings" - - "code.gitea.io/gitea/modules/setting" ) // SanitizeFlashErrorString will sanitize a flash error string func SanitizeFlashErrorString(x string) string { return strings.ReplaceAll(html.EscapeString(x), "\n", "
    ") } - -// IsExternalURL checks if rawURL points to an external URL like http://example.com -func IsExternalURL(rawURL string) bool { - parsed, err := url.Parse(rawURL) - if err != nil { - return true - } - appURL, _ := url.Parse(setting.AppURL) - if len(parsed.Host) != 0 && strings.Replace(parsed.Host, "www.", "", 1) != strings.Replace(appURL.Host, "www.", "", 1) { - return true - } - return false -} diff --git a/routers/utils/utils_test.go b/routers/utils/utils_test.go index 440aad87c6..6e7f3c33cd 100644 --- a/routers/utils/utils_test.go +++ b/routers/utils/utils_test.go @@ -5,47 +5,8 @@ package utils import ( "testing" - - "code.gitea.io/gitea/modules/setting" - - "github.com/stretchr/testify/assert" ) -func TestIsExternalURL(t *testing.T) { - setting.AppURL = "https://try.gitea.io/" - type test struct { - Expected bool - RawURL string - } - newTest := func(expected bool, rawURL string) test { - return test{Expected: expected, RawURL: rawURL} - } - for _, test := range []test{ - newTest(false, - "https://try.gitea.io"), - newTest(true, - "https://example.com/"), - newTest(true, - "//example.com"), - newTest(true, - "http://example.com"), - newTest(false, - "a/"), - newTest(false, - "https://try.gitea.io/test?param=false"), - newTest(false, - "test?param=false"), - newTest(false, - "//try.gitea.io/test?param=false"), - newTest(false, - "/hey/hey/hey#3244"), - newTest(true, - "://missing protocol scheme"), - } { - assert.Equal(t, test.Expected, IsExternalURL(test.RawURL)) - } -} - func TestSanitizeFlashErrorString(t *testing.T) { tests := []struct { name string diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index ba487d1045..799b7e8a84 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -129,6 +129,7 @@ func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source { UserDN: form.UserDN, BindPassword: form.BindPassword, UserBase: form.UserBase, + DefaultDomainName: form.DefaultDomainName, AttributeUsername: form.AttributeUsername, AttributeName: form.AttributeName, AttributeSurname: form.AttributeSurname, diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 1c55256db4..50fd760d80 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/auth/password" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/eventsource" + "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/session" @@ -26,7 +27,6 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" - "code.gitea.io/gitea/routers/utils" auth_service "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/context" @@ -372,16 +372,15 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe return setting.AppSubURL + "/" } - if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) { + redirectTo := ctx.GetSiteCookie("redirect_to") + if redirectTo != "" { middleware.DeleteRedirectToCookie(ctx.Resp) - if obeyRedirect { - ctx.RedirectToFirst(redirectTo) - } - return redirectTo } - if obeyRedirect { - ctx.Redirect(setting.AppSubURL + "/") + return ctx.RedirectToFirst(redirectTo) + } + if !httplib.IsRiskyRedirectURL(redirectTo) { + return redirectTo } return setting.AppSubURL + "/" } diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index f5ca0bda5e..3ad7a4738e 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -5,6 +5,7 @@ package auth import ( go_context "context" + "crypto/sha256" "encoding/base64" "errors" "fmt" @@ -35,6 +36,7 @@ import ( "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/externalaccount" "code.gitea.io/gitea/services/forms" + remote_service "code.gitea.io/gitea/services/remote" user_service "code.gitea.io/gitea/services/user" "gitea.com/go-chi/binding" @@ -859,13 +861,19 @@ func SignInOAuth(ctx *context.Context) { return } - if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil { + codeChallenge, err := generateCodeChallenge(ctx) + if err != nil { + ctx.ServerError("SignIn", fmt.Errorf("could not generate code_challenge: %w", err)) + return + } + + if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp, codeChallenge); err != nil { if strings.Contains(err.Error(), "no provider for ") { if err = oauth2.ResetOAuth2(ctx); err != nil { ctx.ServerError("SignIn", err) return } - if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil { + if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp, codeChallenge); err != nil { ctx.ServerError("SignIn", err) } return @@ -1202,9 +1210,42 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model ctx.Redirect(setting.AppSubURL + "/user/two_factor") } +// generateCodeChallenge stores a code verifier in the session and returns a S256 code challenge for PKCE +func generateCodeChallenge(ctx *context.Context) (codeChallenge string, err error) { + codeVerifier, err := util.CryptoRandomString(43) // 256/log2(62) = 256 bits of entropy (each char having log2(62) of randomness) + if err != nil { + return "", err + } + if err = ctx.Session.Set("CodeVerifier", codeVerifier); err != nil { + return "", err + } + return encodeCodeChallenge(codeVerifier) +} + +func encodeCodeChallenge(codeVerifier string) (string, error) { + hasher := sha256.New() + _, err := io.WriteString(hasher, codeVerifier) + codeChallenge := base64.RawURLEncoding.EncodeToString(hasher.Sum(nil)) + return codeChallenge, err +} + // OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful // login the user func oAuth2UserLoginCallback(ctx *context.Context, authSource *auth.Source, request *http.Request, response http.ResponseWriter) (*user_model.User, goth.User, error) { + gothUser, err := oAuth2FetchUser(ctx, authSource, request, response) + if err != nil { + return nil, goth.User{}, err + } + + if _, _, err := remote_service.MaybePromoteRemoteUser(ctx, authSource, gothUser.UserID, gothUser.Email); err != nil { + return nil, goth.User{}, err + } + + u, err := oAuth2GothUserToUser(request.Context(), authSource, gothUser) + return u, gothUser, err +} + +func oAuth2FetchUser(ctx *context.Context, authSource *auth.Source, request *http.Request, response http.ResponseWriter) (goth.User, error) { oauth2Source := authSource.Cfg.(*oauth2.Source) // Make sure that the response is not an error response. @@ -1216,40 +1257,46 @@ func oAuth2UserLoginCallback(ctx *context.Context, authSource *auth.Source, requ // Delete the goth session err := gothic.Logout(response, request) if err != nil { - return nil, goth.User{}, err + return goth.User{}, err } - return nil, goth.User{}, errCallback{ + return goth.User{}, errCallback{ Code: errorName, Description: errorDescription, } } // Proceed to authenticate through goth. - gothUser, err := oauth2Source.Callback(request, response) + codeVerifier, _ := ctx.Session.Get("CodeVerifier").(string) + _ = ctx.Session.Delete("CodeVerifier") + gothUser, err := oauth2Source.Callback(request, response, codeVerifier) if err != nil { if err.Error() == "securecookie: the value is too long" || strings.Contains(err.Error(), "Data too long") { log.Error("OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider", authSource.Name, setting.OAuth2.MaxTokenLength) err = fmt.Errorf("OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider", authSource.Name, setting.OAuth2.MaxTokenLength) } - return nil, goth.User{}, err + return goth.User{}, err } if oauth2Source.RequiredClaimName != "" { claimInterface, has := gothUser.RawData[oauth2Source.RequiredClaimName] if !has { - return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID} + return goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID} } if oauth2Source.RequiredClaimValue != "" { groups := claimValueToStringSet(claimInterface) if !groups.Contains(oauth2Source.RequiredClaimValue) { - return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID} + return goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID} } } } + return gothUser, nil +} + +func oAuth2GothUserToUser(ctx go_context.Context, authSource *auth.Source, gothUser goth.User) (*user_model.User, error) { user := &user_model.User{ LoginName: gothUser.UserID, LoginType: auth.OAuth2, @@ -1258,27 +1305,28 @@ func oAuth2UserLoginCallback(ctx *context.Context, authSource *auth.Source, requ hasUser, err := user_model.GetUser(ctx, user) if err != nil { - return nil, goth.User{}, err + return nil, err } if hasUser { - return user, gothUser, nil + return user, nil } + log.Debug("no user found for LoginName %v, LoginSource %v, LoginType %v", user.LoginName, user.LoginSource, user.LoginType) // search in external linked users externalLoginUser := &user_model.ExternalLoginUser{ ExternalID: gothUser.UserID, LoginSourceID: authSource.ID, } - hasUser, err = user_model.GetExternalLogin(request.Context(), externalLoginUser) + hasUser, err = user_model.GetExternalLogin(ctx, externalLoginUser) if err != nil { - return nil, goth.User{}, err + return nil, err } if hasUser { - user, err = user_model.GetUserByID(request.Context(), externalLoginUser.UserID) - return user, gothUser, err + user, err = user_model.GetUserByID(ctx, externalLoginUser.UserID) + return user, err } // no user found to login - return nil, gothUser, nil + return nil, nil } diff --git a/routers/web/auth/oauth_test.go b/routers/web/auth/oauth_test.go index 4339d9d1eb..3726daee93 100644 --- a/routers/web/auth/oauth_test.go +++ b/routers/web/auth/oauth_test.go @@ -93,3 +93,10 @@ func TestNewAccessTokenResponse_OIDCToken(t *testing.T) { assert.Equal(t, user.Email, oidcToken.Email) assert.Equal(t, user.IsActive, oidcToken.EmailVerified) } + +func TestEncodeCodeChallenge(t *testing.T) { + // test vector from https://datatracker.ietf.org/doc/html/rfc7636#page-18 + codeChallenge, err := encodeCodeChallenge("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk") + assert.NoError(t, err) + assert.Equal(t, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", codeChallenge) +} diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go index 53494df630..d15a8b814c 100644 --- a/routers/web/auth/password.go +++ b/routers/web/auth/password.go @@ -18,7 +18,6 @@ import ( "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" - "code.gitea.io/gitea/routers/utils" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/mailer" @@ -310,11 +309,9 @@ func MustChangePasswordPost(ctx *context.Context) { log.Trace("User updated password: %s", ctx.Doer.Name) - if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) { + redirectTo := ctx.GetSiteCookie("redirect_to") + if redirectTo != "" { middleware.DeleteRedirectToCookie(ctx.Resp) - ctx.RedirectToFirst(redirectTo) - return } - - ctx.Redirect(setting.AppSubURL + "/") + ctx.RedirectToFirst(redirectTo) } diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index 38f9b85ace..821228b347 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -104,7 +104,7 @@ func Projects(ctx *context.Context) { } for _, project := range projects { - project.RenderedContent = templates.SanitizeHTML(project.Description) // FIXME: is it right? why not render? + project.RenderedContent = templates.RenderMarkdownToHtml(ctx, project.Description) } err = shared_user.LoadHeaderCount(ctx) @@ -372,7 +372,7 @@ func ViewProject(ctx *context.Context) { } } - project.RenderedContent = templates.SanitizeHTML(project.Description) // FIXME: is it right? why not render? + project.RenderedContent = templates.RenderMarkdownToHtml(ctx, project.Description) ctx.Data["LinkedPRs"] = linkedPrsMap ctx.Data["PageIsViewProjects"] = true ctx.Data["CanWriteProjects"] = canWriteProjects(ctx) diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 6176ac1278..dc21f1a4ed 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -689,7 +689,6 @@ func ArtifactsDownloadView(ctx *context_module.Context) { writer := zip.NewWriter(ctx.Resp) defer writer.Close() for _, art := range artifacts { - f, err := storage.ActionsArtifacts.Open(art.StoragePath) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 718454e063..33491ec696 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -212,8 +212,6 @@ func SearchCommits(ctx *context.Context) { // FileHistory show a file's reversions func FileHistory(ctx *context.Context) { - ctx.Data["IsRepoToolbarCommits"] = true - fileName := ctx.Repo.TreePath if len(fileName) == 0 { Commits(ctx) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 93c224b7be..a61e23add3 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -35,6 +35,7 @@ import ( api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context/upload" "code.gitea.io/gitea/services/gitdiff" @@ -185,21 +186,10 @@ func setCsvCompareContext(ctx *context.Context) { } } -// CompareInfo represents the collected results from ParseCompareInfo -type CompareInfo struct { - HeadUser *user_model.User - HeadRepo *repo_model.Repository - HeadGitRepo *git.Repository - CompareInfo *git.CompareInfo - BaseBranch string - HeadBranch string - DirectComparison bool -} - // ParseCompareInfo parse compare info between two commit for preparing comparing references -func ParseCompareInfo(ctx *context.Context) *CompareInfo { +func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { baseRepo := ctx.Repo.Repository - ci := &CompareInfo{} + ci := &common.CompareInfo{} fileOnly := ctx.FormBool("file-only") @@ -576,7 +566,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { // PrepareCompareDiff renders compare diff page func PrepareCompareDiff( ctx *context.Context, - ci *CompareInfo, + ci *common.CompareInfo, whitespaceBehavior git.TrustedCmdArgs, ) bool { var ( @@ -810,7 +800,6 @@ func CompareDiff(ctx *context.Context) { } ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + separator + base.ShortSha(afterCommitID) - ctx.Data["IsRepoToolbarCommits"] = true ctx.Data["IsDiffCompare"] = true _, templateErrs := setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index afa6160d42..97f2195116 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -328,15 +328,15 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt return } - // Get posters. - for i := range issues { - // Check read status - if !ctx.IsSigned { - issues[i].IsRead = true - } else if err = issues[i].GetIsRead(ctx, ctx.Doer.ID); err != nil { - ctx.ServerError("GetIsRead", err) + if ctx.IsSigned { + if err := issues.LoadIsRead(ctx, ctx.Doer.ID); err != nil { + ctx.ServerError("LoadIsRead", err) return } + } else { + for i := range issues { + issues[i].IsRead = true + } } commitStatuses, lastStatus, err := pull_service.GetIssuesAllCommitStatus(ctx, issues) @@ -927,7 +927,6 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles } } } - } if template.Ref != "" && !strings.HasPrefix(template.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/ @@ -1602,20 +1601,20 @@ func ViewIssue(ctx *context.Context) { // Render comments and fetch participants. participants[0] = issue.Poster + + if err := issue.Comments.LoadAttachmentsByIssue(ctx); err != nil { + ctx.ServerError("LoadAttachmentsByIssue", err) + return + } + if err := issue.Comments.LoadPosters(ctx); err != nil { + ctx.ServerError("LoadPosters", err) + return + } + for _, comment = range issue.Comments { comment.Issue = issue - if err := comment.LoadPoster(ctx); err != nil { - ctx.ServerError("LoadPoster", err) - return - } - if comment.Type == issues_model.CommentTypeComment || comment.Type == issues_model.CommentTypeReview { - if err := comment.LoadAttachments(ctx); err != nil { - ctx.ServerError("LoadAttachments", err) - return - } - comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ Links: markup.Links{ Base: ctx.Repo.RepoLink, @@ -1663,7 +1662,6 @@ func ViewIssue(ctx *context.Context) { comment.Milestone = ghostMilestone } } else if comment.Type == issues_model.CommentTypeProject { - if err = comment.LoadProject(ctx); err != nil { ctx.ServerError("LoadProject", err) return @@ -1681,7 +1679,6 @@ func ViewIssue(ctx *context.Context) { if comment.ProjectID > 0 && comment.Project == nil { comment.Project = ghostProject } - } else if comment.Type == issues_model.CommentTypeAssignees || comment.Type == issues_model.CommentTypeReviewRequest { if err = comment.LoadAssigneeUserAndTeam(ctx); err != nil { ctx.ServerError("LoadAssigneeUserAndTeam", err) @@ -1729,10 +1726,6 @@ func ViewIssue(ctx *context.Context) { for _, codeComments := range comment.Review.CodeComments { for _, lineComments := range codeComments { for _, c := range lineComments { - if err := c.LoadAttachments(ctx); err != nil { - ctx.ServerError("LoadAttachments", err) - return - } // Check tag. role, ok = marked[c.PosterID] if ok { @@ -2610,7 +2603,6 @@ func SearchIssues(ctx *context.Context) { var includedAnyLabels []int64 { - labels := ctx.FormTrim("labels") var includedLabelNames []string if len(labels) > 0 { @@ -2998,7 +2990,6 @@ func NewComment(ctx *context.Context) { if (ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) || (ctx.IsSigned && issue.IsPoster(ctx.Doer.ID))) && (form.Status == "reopen" || form.Status == "close") && !(issue.IsPull && issue.PullRequest.HasMerged) { - // Duplication and conflict check should apply to reopen pull request. var pr *issues_model.PullRequest @@ -3165,12 +3156,6 @@ func UpdateCommentContent(ctx *context.Context) { oldContent := comment.Content comment.Content = ctx.FormString("content") - if len(comment.Content) == 0 { - ctx.JSON(http.StatusOK, map[string]any{ - "content": "", - }) - return - } if err = issue_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil { ctx.ServerError("UpdateComment", err) return diff --git a/routers/web/repo/issue_dependency.go b/routers/web/repo/issue_dependency.go index e3b85ee638..66b38688ec 100644 --- a/routers/web/repo/issue_dependency.go +++ b/routers/web/repo/issue_dependency.go @@ -109,7 +109,7 @@ func RemoveDependency(ctx *context.Context) { } // Dependency Type - depTypeStr := ctx.Req.PostForm.Get("dependencyType") + depTypeStr := ctx.Req.PostFormValue("dependencyType") var depType issues_model.DependencyType diff --git a/routers/web/repo/issue_watch.go b/routers/web/repo/issue_watch.go index 6799bf8eb2..5cff9f4ddd 100644 --- a/routers/web/repo/issue_watch.go +++ b/routers/web/repo/issue_watch.go @@ -14,7 +14,7 @@ import ( ) const ( - tplWatching base.TplName = "repo/issue/view_content/watching" + tplWatching base.TplName = "repo/issue/view_content/sidebar/watching" ) // IssueWatch sets issue watching diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index c6c6142534..a0dd36927f 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -670,7 +670,6 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C } if pb != nil && pb.EnableStatusCheck { - var missingRequiredChecks []string for _, requiredContext := range pb.StatusCheckContexts { contextFound := false @@ -873,7 +872,6 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi // Validate the given commit sha to show (if any passed) if willShowSpecifiedCommit || willShowSpecifiedCommitRange { - foundStartCommit := len(specifiedStartCommit) == 0 foundEndCommit := len(specifiedEndCommit) == 0 @@ -1185,7 +1183,6 @@ func UpdatePullRequest(ctx *context.Context) { ctx.Flash.Error(flashError) ctx.Redirect(issue.Link()) return - } ctx.Flash.Error(err.Error()) ctx.Redirect(issue.Link()) @@ -1439,7 +1436,6 @@ func CompareAndPullRequestPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes") ctx.Data["PageIsComparePull"] = true ctx.Data["IsDiffCompare"] = true - ctx.Data["IsRepoToolbarCommits"] = true ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled upload.AddUploadContext(ctx, "comment") diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go index afa3b17d31..24763668d0 100644 --- a/routers/web/repo/pull_review.go +++ b/routers/web/repo/pull_review.go @@ -169,11 +169,9 @@ func renderConversation(ctx *context.Context, comment *issues_model.Comment, ori } ctx.Data["PageIsPullFiles"] = (origin == "diff") - for _, c := range comments { - if err := c.LoadAttachments(ctx); err != nil { - ctx.ServerError("LoadAttachments", err) - return - } + if err := comments.LoadAttachments(ctx); err != nil { + ctx.ServerError("LoadAttachments", err) + return } ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled @@ -250,6 +248,8 @@ func SubmitReview(ctx *context.Context) { if issues_model.IsContentEmptyErr(err) { ctx.Flash.Error(ctx.Tr("repo.issues.review.content.empty")) ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d/files", ctx.Repo.RepoLink, issue.Index)) + } else if errors.Is(err, pull_service.ErrSubmitReviewOnClosedPR) { + ctx.Status(http.StatusUnprocessableEntity) } else { ctx.ServerError("SubmitReview", err) } @@ -304,7 +304,6 @@ func UpdateViewedFiles(ctx *context.Context) { updatedFiles := make(map[string]pull_model.ViewedState, len(data.Files)) for file, viewed := range data.Files { - // Only unviewed and viewed are possible, has-changed can not be set from the outside state := pull_model.Unviewed if viewed { diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 908ce8138e..3927e3d2d9 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -127,6 +127,11 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) return nil, err } + err = r.LoadArchiveDownloadCount(ctx) + if err != nil { + return nil, err + } + if !r.IsDraft { if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil { return nil, err @@ -355,6 +360,12 @@ func SingleRelease(ctx *context.Context) { ctx.Data["Title"] = release.Title } + err = release.LoadArchiveDownloadCount(ctx) + if err != nil { + ctx.ServerError("LoadArchiveDownloadCount", err) + return + } + ctx.Data["Releases"] = releases ctx.HTML(http.StatusOK, tplReleasesList) } @@ -430,6 +441,20 @@ func NewRelease(ctx *context.Context) { } ctx.Data["Tags"] = tags + // We set the value of the hide_archive_link textbox depending on the latest release + latestRelease, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID) + if err != nil { + if repo_model.IsErrReleaseNotExist(err) { + ctx.Data["hide_archive_links"] = false + } else { + ctx.ServerError("GetLatestReleaseByRepoID", err) + return + } + } + if latestRelease != nil { + ctx.Data["hide_archive_links"] = latestRelease.HideArchiveLinks + } + ctx.HTML(http.StatusOK, tplReleaseNew) } @@ -514,17 +539,18 @@ func NewReleasePost(ctx *context.Context) { } rel = &repo_model.Release{ - RepoID: ctx.Repo.Repository.ID, - Repo: ctx.Repo.Repository, - PublisherID: ctx.Doer.ID, - Publisher: ctx.Doer, - Title: form.Title, - TagName: form.TagName, - Target: form.Target, - Note: form.Content, - IsDraft: len(form.Draft) > 0, - IsPrerelease: form.Prerelease, - IsTag: false, + RepoID: ctx.Repo.Repository.ID, + Repo: ctx.Repo.Repository, + PublisherID: ctx.Doer.ID, + Publisher: ctx.Doer, + Title: form.Title, + TagName: form.TagName, + Target: form.Target, + Note: form.Content, + IsDraft: len(form.Draft) > 0, + IsPrerelease: form.Prerelease, + HideArchiveLinks: form.HideArchiveLinks, + IsTag: false, } if err = releaseservice.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs, msg); err != nil { @@ -554,6 +580,7 @@ func NewReleasePost(ctx *context.Context) { rel.IsDraft = len(form.Draft) > 0 rel.IsPrerelease = form.Prerelease rel.PublisherID = ctx.Doer.ID + rel.HideArchiveLinks = form.HideArchiveLinks rel.IsTag = false if err = releaseservice.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, attachmentUUIDs, nil, nil, true); err != nil { @@ -591,6 +618,7 @@ func EditRelease(ctx *context.Context) { ctx.Data["title"] = rel.Title ctx.Data["content"] = rel.Note ctx.Data["prerelease"] = rel.IsPrerelease + ctx.Data["hide_archive_links"] = rel.HideArchiveLinks ctx.Data["IsDraft"] = rel.IsDraft rel.Repo = ctx.Repo.Repository @@ -637,6 +665,7 @@ func EditReleasePost(ctx *context.Context) { ctx.Data["title"] = rel.Title ctx.Data["content"] = rel.Note ctx.Data["prerelease"] = rel.IsPrerelease + ctx.Data["hide_archive_links"] = rel.HideArchiveLinks if ctx.HasError() { ctx.HTML(http.StatusOK, tplReleaseNew) @@ -662,6 +691,7 @@ func EditReleasePost(ctx *context.Context) { rel.Note = form.Content rel.IsDraft = len(form.Draft) > 0 rel.IsPrerelease = form.Prerelease + rel.HideArchiveLinks = form.HideArchiveLinks if err = releaseservice.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, addAttachmentUUIDs, delAttachmentUUIDs, editAttachments, false); err != nil { ctx.ServerError("UpdateRelease", err) diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index b2458a561a..abd68630a4 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -456,7 +456,7 @@ func RedirectDownload(ctx *context.Context) { // Download an archive of a repository func Download(ctx *context.Context) { uri := ctx.Params("*") - aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri) + aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri) if err != nil { if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) { ctx.Error(http.StatusBadRequest, err.Error()) @@ -480,11 +480,25 @@ func Download(ctx *context.Context) { func download(ctx *context.Context, archiveName string, archiver *repo_model.RepoArchiver) { downloadName := ctx.Repo.Repository.Name + "-" + archiveName + // Add nix format link header so tarballs lock correctly: + // https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md + ctx.Resp.Header().Add("Link", fmt.Sprintf("<%s/archive/%s.tar.gz?rev=%s>; rel=\"immutable\"", + ctx.Repo.Repository.APIURL(), + archiver.CommitID, archiver.CommitID)) + rPath := archiver.RelativePath() if setting.RepoArchive.Storage.MinioConfig.ServeDirect { // If we have a signed url (S3, object storage), redirect to this directly. u, err := storage.RepoArchives.URL(rPath, downloadName) if u != nil && err == nil { + if archiver.ReleaseID != 0 { + err = repo_model.CountArchiveDownload(ctx, ctx.Repo.Repository.ID, archiver.ReleaseID, archiver.Type) + if err != nil { + ctx.ServerError("CountArchiveDownload", err) + return + } + } + ctx.Redirect(u.String()) return } @@ -498,6 +512,14 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep } defer fr.Close() + if archiver.ReleaseID != 0 { + err = repo_model.CountArchiveDownload(ctx, ctx.Repo.Repository.ID, archiver.ReleaseID, archiver.Type) + if err != nil { + ctx.ServerError("CountArchiveDownload", err) + return + } + } + ctx.ServeContent(fr, &context.ServeHeaderOptions{ Filename: downloadName, LastModified: archiver.CreatedUnix.AsLocalTime(), @@ -509,7 +531,7 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep // kind of drop it on the floor if this is the case. func InitiateDownload(ctx *context.Context) { uri := ctx.Params("*") - aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri) + aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri) if err != nil { ctx.ServerError("archiver_service.NewRequest", err) return diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index e98cd46c4e..2644b27229 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -352,7 +352,6 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool { // or of directory if not in root directory. ctx.Data["LatestCommit"] = latestCommit if latestCommit != nil { - verification := asymkey_model.ParseCommitWithSignature(ctx, latestCommit) if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) { diff --git a/routers/web/shared/actions/variables.go b/routers/web/shared/actions/variables.go index 0f705399c9..79c03e4e8c 100644 --- a/routers/web/shared/actions/variables.go +++ b/routers/web/shared/actions/variables.go @@ -4,17 +4,13 @@ package actions import ( - "errors" - "regexp" - "strings" - actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/web" + actions_service "code.gitea.io/gitea/services/actions" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" - secret_service "code.gitea.io/gitea/services/secrets" ) func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) { @@ -29,41 +25,16 @@ func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) { ctx.Data["Variables"] = variables } -// some regular expression of `variables` and `secrets` -// reference to: -// https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables -// https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets -var ( - forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI") -) - -func envNameCIRegexMatch(name string) error { - if forbiddenEnvNameCIRx.MatchString(name) { - log.Error("Env Name cannot be ci") - return errors.New("env name cannot be ci") - } - return nil -} - func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) { form := web.GetForm(ctx).(*forms.EditVariableForm) - if err := secret_service.ValidateName(form.Name); err != nil { - ctx.JSONError(err.Error()) - return - } - - if err := envNameCIRegexMatch(form.Name); err != nil { - ctx.JSONError(err.Error()) - return - } - - v, err := actions_model.InsertVariable(ctx, ownerID, repoID, form.Name, ReserveLineBreakForTextarea(form.Data)) + v, err := actions_service.CreateVariable(ctx, ownerID, repoID, form.Name, form.Data) if err != nil { - log.Error("InsertVariable error: %v", err) + log.Error("CreateVariable: %v", err) ctx.JSONError(ctx.Tr("actions.variables.creation.failed")) return } + ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name)) ctx.JSONRedirect(redirectURL) } @@ -72,23 +43,8 @@ func UpdateVariable(ctx *context.Context, redirectURL string) { id := ctx.ParamsInt64(":variable_id") form := web.GetForm(ctx).(*forms.EditVariableForm) - if err := secret_service.ValidateName(form.Name); err != nil { - ctx.JSONError(err.Error()) - return - } - - if err := envNameCIRegexMatch(form.Name); err != nil { - ctx.JSONError(err.Error()) - return - } - - ok, err := actions_model.UpdateVariable(ctx, &actions_model.ActionVariable{ - ID: id, - Name: strings.ToUpper(form.Name), - Data: ReserveLineBreakForTextarea(form.Data), - }) - if err != nil || !ok { - log.Error("UpdateVariable error: %v", err) + if ok, err := actions_service.UpdateVariable(ctx, id, form.Name, form.Data); err != nil || !ok { + log.Error("UpdateVariable: %v", err) ctx.JSONError(ctx.Tr("actions.variables.update.failed")) return } @@ -99,7 +55,7 @@ func UpdateVariable(ctx *context.Context, redirectURL string) { func DeleteVariable(ctx *context.Context, redirectURL string) { id := ctx.ParamsInt64(":variable_id") - if _, err := db.DeleteByBean(ctx, &actions_model.ActionVariable{ID: id}); err != nil { + if err := actions_service.DeleteVariableByID(ctx, id); err != nil { log.Error("Delete variable [%d] failed: %v", id, err) ctx.JSONError(ctx.Tr("actions.variables.deletion.failed")) return @@ -107,12 +63,3 @@ func DeleteVariable(ctx *context.Context, redirectURL string) { ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success")) ctx.JSONRedirect(redirectURL) } - -func ReserveLineBreakForTextarea(input string) string { - // Since the content is from a form which is a textarea, the line endings are \r\n. - // It's a standard behavior of HTML. - // But we want to store them as \n like what GitHub does. - // And users are unlikely to really need to keep the \r. - // Other than this, we should respect the original content, even leading or trailing spaces. - return strings.ReplaceAll(input, "\r\n", "\n") -} diff --git a/routers/web/shared/secrets/secrets.go b/routers/web/shared/secrets/secrets.go index 73505ec372..3bd421f86a 100644 --- a/routers/web/shared/secrets/secrets.go +++ b/routers/web/shared/secrets/secrets.go @@ -7,8 +7,8 @@ import ( "code.gitea.io/gitea/models/db" secret_model "code.gitea.io/gitea/models/secret" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" - "code.gitea.io/gitea/routers/web/shared/actions" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" secret_service "code.gitea.io/gitea/services/secrets" @@ -27,7 +27,7 @@ func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) { func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) { form := web.GetForm(ctx).(*forms.AddSecretForm) - s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, actions.ReserveLineBreakForTextarea(form.Data)) + s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.ReserveLineBreakForTextarea(form.Data)) if err != nil { log.Error("CreateOrUpdateSecret failed: %v", err) ctx.JSONError(ctx.Tr("secrets.creation.failed")) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 2e4b9dae65..1893e91221 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -376,7 +376,7 @@ func Action(ctx *context.Context) { } if redirectViaJSON { - ctx.JSON(http.StatusOK, map[string]interface{}{ + ctx.JSON(http.StatusOK, map[string]any{ "redirect": ctx.ContextUser.HomeLink(), }) return diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 6f412aed7f..795ee59d58 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -243,7 +243,7 @@ func DeleteEmail(ctx *context.Context) { // DeleteAccount render user suicide page and response for delete user himself func DeleteAccount(ctx *context.Context) { - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureDeletion) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureDeletion) { ctx.Error(http.StatusNotFound) return } @@ -327,7 +327,7 @@ func loadAccountData(ctx *context.Context) { ctx.Data["EmailNotificationsPreference"] = ctx.Doer.EmailNotificationsPreference ctx.Data["ActivationsPending"] = pendingActivation ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm - ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) if setting.Service.UserDeleteWithCommentsMaxTime != 0 { ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String() diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index d2b60fc809..9462be71c2 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -10,6 +10,7 @@ import ( asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" @@ -78,7 +79,7 @@ func KeysPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "gpg": - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) return } @@ -159,7 +160,7 @@ func KeysPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.verify_gpg_key_success", keyID)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "ssh": - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) return } @@ -203,7 +204,7 @@ func KeysPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "verify_ssh": - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) return } @@ -240,7 +241,7 @@ func KeysPost(ctx *context.Context) { func DeleteKey(ctx *context.Context) { switch ctx.FormString("type") { case "gpg": - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageGPGKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) return } @@ -250,7 +251,7 @@ func DeleteKey(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success")) } case "ssh": - if setting.Admin.UserDisabledFeatures.Contains(setting.UserFeatureManageSSHKeys) { + if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { ctx.NotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) return } @@ -333,5 +334,5 @@ func loadKeysData(ctx *context.Context) { ctx.Data["VerifyingID"] = ctx.FormString("verify_gpg") ctx.Data["VerifyingFingerprint"] = ctx.FormString("verify_ssh") - ctx.Data["UserDisabledFeatures"] = &setting.Admin.UserDisabledFeatures + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) } diff --git a/routers/web/web.go b/routers/web/web.go index 23695ee4db..8faedca178 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -654,7 +654,7 @@ func registerRoutes(m *web.Route) { m.Get("/system_status", admin.SystemStatus) m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost) - if setting.Database.Type.IsMySQL() || setting.Database.Type.IsMSSQL() { + if setting.Database.Type.IsMySQL() { m.Get("/self_check", admin.SelfCheck) } diff --git a/routers/web/webfinger.go b/routers/web/webfinger.go index e4b2aacce8..1f3de70db0 100644 --- a/routers/web/webfinger.go +++ b/routers/web/webfinger.go @@ -64,6 +64,51 @@ func WebfingerQuery(ctx *context.Context) { if u != nil && u.KeepEmailPrivate { err = user_model.ErrUserNotExist{} } + case "https", "http": + if resource.Host != appURL.Host { + ctx.Error(http.StatusBadRequest) + return + } + + p := strings.Trim(resource.Path, "/") + if len(p) == 0 { + ctx.Error(http.StatusNotFound) + return + } + + parts := strings.Split(p, "/") + + switch len(parts) { + case 1: // user + u, err = user_model.GetUserByName(ctx, parts[0]) + case 2: // repository + ctx.Error(http.StatusNotFound) + return + + case 3: + switch parts[2] { + case "issues": + ctx.Error(http.StatusNotFound) + return + + case "pulls": + ctx.Error(http.StatusNotFound) + return + + case "projects": + ctx.Error(http.StatusNotFound) + return + + default: + ctx.Error(http.StatusNotFound) + return + } + + default: + ctx.Error(http.StatusNotFound) + return + } + default: ctx.Error(http.StatusBadRequest) return diff --git a/services/actions/auth_test.go b/services/actions/auth_test.go index f73ae8ae4c..12db2bae56 100644 --- a/services/actions/auth_test.go +++ b/services/actions/auth_test.go @@ -20,7 +20,7 @@ func TestCreateAuthorizationToken(t *testing.T) { assert.Nil(t, err) assert.NotEqual(t, "", token) claims := jwt.MapClaims{} - _, err = jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) { + _, err = jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) { return setting.GetGeneralTokenSigningSecret(), nil }) assert.Nil(t, err) diff --git a/services/actions/interface.go b/services/actions/interface.go new file mode 100644 index 0000000000..d4fa782fec --- /dev/null +++ b/services/actions/interface.go @@ -0,0 +1,28 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import "code.gitea.io/gitea/services/context" + +// API for actions of a repository or organization +type API interface { + // ListActionsSecrets list secrets + ListActionsSecrets(*context.APIContext) + // CreateOrUpdateSecret create or update a secret + CreateOrUpdateSecret(*context.APIContext) + // DeleteSecret delete a secret + DeleteSecret(*context.APIContext) + // ListVariables list variables + ListVariables(*context.APIContext) + // GetVariable get a variable + GetVariable(*context.APIContext) + // DeleteVariable delete a variable + DeleteVariable(*context.APIContext) + // CreateVariable create a variable + CreateVariable(*context.APIContext) + // UpdateVariable update a variable + UpdateVariable(*context.APIContext) + // GetRegistrationToken get registration token + GetRegistrationToken(*context.APIContext) +} diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index 365212d9c2..78f413c214 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -329,13 +329,15 @@ func handleWorkflows( TriggerEvent: dwf.TriggerEvent.Name, Status: actions_model.StatusWaiting, } - if need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer); err != nil { + + need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer) + if err != nil { log.Error("check if need approval for repo %d with user %d: %v", input.Repo.ID, input.Doer.ID, err) continue - } else { - run.NeedApproval = need } + run.NeedApproval = need + if err := run.LoadAttributes(ctx); err != nil { log.Error("LoadAttributes: %v", err) continue diff --git a/services/actions/schedule_tasks.go b/services/actions/schedule_tasks.go index e4e56e5122..18f3324fd2 100644 --- a/services/actions/schedule_tasks.go +++ b/services/actions/schedule_tasks.go @@ -132,8 +132,14 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule) Status: actions_model.StatusWaiting, } + vars, err := actions_model.GetVariablesOfRun(ctx, run) + if err != nil { + log.Error("GetVariablesOfRun: %v", err) + return err + } + // Parse the workflow specification from the cron schedule - workflows, err := jobparser.Parse(cron.Content) + workflows, err := jobparser.Parse(cron.Content, jobparser.WithVars(vars)) if err != nil { return err } diff --git a/services/actions/variables.go b/services/actions/variables.go new file mode 100644 index 0000000000..8dde9c4af5 --- /dev/null +++ b/services/actions/variables.go @@ -0,0 +1,100 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "context" + "regexp" + "strings" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" + secret_service "code.gitea.io/gitea/services/secrets" +) + +func CreateVariable(ctx context.Context, ownerID, repoID int64, name, data string) (*actions_model.ActionVariable, error) { + if err := secret_service.ValidateName(name); err != nil { + return nil, err + } + + if err := envNameCIRegexMatch(name); err != nil { + return nil, err + } + + v, err := actions_model.InsertVariable(ctx, ownerID, repoID, name, util.ReserveLineBreakForTextarea(data)) + if err != nil { + return nil, err + } + + return v, nil +} + +func UpdateVariable(ctx context.Context, variableID int64, name, data string) (bool, error) { + if err := secret_service.ValidateName(name); err != nil { + return false, err + } + + if err := envNameCIRegexMatch(name); err != nil { + return false, err + } + + return actions_model.UpdateVariable(ctx, &actions_model.ActionVariable{ + ID: variableID, + Name: strings.ToUpper(name), + Data: util.ReserveLineBreakForTextarea(data), + }) +} + +func DeleteVariableByID(ctx context.Context, variableID int64) error { + return actions_model.DeleteVariable(ctx, variableID) +} + +func DeleteVariableByName(ctx context.Context, ownerID, repoID int64, name string) error { + if err := secret_service.ValidateName(name); err != nil { + return err + } + + if err := envNameCIRegexMatch(name); err != nil { + return err + } + + v, err := GetVariable(ctx, actions_model.FindVariablesOpts{ + OwnerID: ownerID, + RepoID: repoID, + Name: name, + }) + if err != nil { + return err + } + + return actions_model.DeleteVariable(ctx, v.ID) +} + +func GetVariable(ctx context.Context, opts actions_model.FindVariablesOpts) (*actions_model.ActionVariable, error) { + vars, err := actions_model.FindVariables(ctx, opts) + if err != nil { + return nil, err + } + if len(vars) != 1 { + return nil, util.NewNotExistErrorf("variable not found") + } + return vars[0], nil +} + +// some regular expression of `variables` and `secrets` +// reference to: +// https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables +// https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets +var ( + forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI") +) + +func envNameCIRegexMatch(name string) error { + if forbiddenEnvNameCIRx.MatchString(name) { + log.Error("Env Name cannot be ci") + return util.NewInvalidArgumentErrorf("env name cannot be ci") + } + return nil +} diff --git a/services/auth/source/ldap/source.go b/services/auth/source/ldap/source.go index dc4cb2c940..ba407b351a 100644 --- a/services/auth/source/ldap/source.go +++ b/services/auth/source/ldap/source.go @@ -34,6 +34,7 @@ type Source struct { BindPassword string // Bind DN password UserBase string // Base search path for users UserDN string // Template for the DN of the user for simple auth + DefaultDomainName string // DomainName used if none are in the field, default "localhost.local" AttributeUsername string // Username attribute AttributeName string // First name attribute AttributeSurname string // Surname attribute diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index 62f052d68c..1f70edaa82 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -105,7 +105,11 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } if len(su.Mail) == 0 { - su.Mail = fmt.Sprintf("%s@localhost.local", su.Username) + domainName := source.DefaultDomainName + if len(domainName) == 0 { + domainName = "localhost.local" + } + su.Mail = fmt.Sprintf("%s@%s", su.Username, domainName) } fullName := composeFullName(su.Name, su.Surname, su.Username) @@ -155,7 +159,6 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { !strings.EqualFold(usr.Email, su.Mail) || usr.FullName != fullName || !usr.IsActive { - log.Trace("SyncExternalUsers[%s]: Updating user %s", source.authSource.Name, usr.Name) opts := &user_service.UpdateOptions{ diff --git a/services/auth/source/oauth2/source_callout.go b/services/auth/source/oauth2/source_callout.go index 8d70bee248..f95a80fc19 100644 --- a/services/auth/source/oauth2/source_callout.go +++ b/services/auth/source/oauth2/source_callout.go @@ -5,16 +5,25 @@ package oauth2 import ( "net/http" + "net/url" "github.com/markbates/goth" "github.com/markbates/goth/gothic" ) // Callout redirects request/response pair to authenticate against the provider -func (source *Source) Callout(request *http.Request, response http.ResponseWriter) error { +func (source *Source) Callout(request *http.Request, response http.ResponseWriter, codeChallengeS256 string) error { // not sure if goth is thread safe (?) when using multiple providers request.Header.Set(ProviderHeaderKey, source.authSource.Name) + var querySuffix string + if codeChallengeS256 != "" { + querySuffix = "&" + url.Values{ + "code_challenge_method": []string{"S256"}, + "code_challenge": []string{codeChallengeS256}, + }.Encode() + } + // don't use the default gothic begin handler to prevent issues when some error occurs // normally the gothic library will write some custom stuff to the response instead of our own nice error page // gothic.BeginAuthHandler(response, request) @@ -24,17 +33,29 @@ func (source *Source) Callout(request *http.Request, response http.ResponseWrite url, err := gothic.GetAuthURL(response, request) if err == nil { - http.Redirect(response, request, url, http.StatusTemporaryRedirect) + // hacky way to set the code_challenge, but no better way until + // https://github.com/markbates/goth/issues/516 is resolved + http.Redirect(response, request, url+querySuffix, http.StatusTemporaryRedirect) } return err } // Callback handles OAuth callback, resolve to a goth user and send back to original url // this will trigger a new authentication request, but because we save it in the session we can use that -func (source *Source) Callback(request *http.Request, response http.ResponseWriter) (goth.User, error) { +func (source *Source) Callback(request *http.Request, response http.ResponseWriter, codeVerifier string) (goth.User, error) { // not sure if goth is thread safe (?) when using multiple providers request.Header.Set(ProviderHeaderKey, source.authSource.Name) + if codeVerifier != "" { + // hacky way to set the code_verifier... + // Will be picked up inside CompleteUserAuth: params := req.URL.Query() + // https://github.com/markbates/goth/pull/474/files + request = request.Clone(request.Context()) + q := request.URL.Query() + q.Add("code_verifier", codeVerifier) + request.URL.RawQuery = q.Encode() + } + gothRWMutex.RLock() defer gothRWMutex.RUnlock() diff --git a/services/auth/source/remote/source.go b/services/auth/source/remote/source.go new file mode 100644 index 0000000000..4165858a56 --- /dev/null +++ b/services/auth/source/remote/source.go @@ -0,0 +1,33 @@ +// Copyright Earl Warren +// SPDX-License-Identifier: MIT + +package remote + +import ( + "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/modules/json" +) + +type Source struct { + URL string + MatchingSource string + + // reference to the authSource + authSource *auth.Source +} + +func (source *Source) FromDB(bs []byte) error { + return json.UnmarshalHandleDoubleEncode(bs, &source) +} + +func (source *Source) ToDB() ([]byte, error) { + return json.Marshal(source) +} + +func (source *Source) SetAuthSource(authSource *auth.Source) { + source.authSource = authSource +} + +func init() { + auth.RegisterTypeConfig(auth.Remote, &Source{}) +} diff --git a/services/context/context_response.go b/services/context/context_response.go index 372b4cb38b..2f2d7b0e1b 100644 --- a/services/context/context_response.go +++ b/services/context/context_response.go @@ -44,8 +44,10 @@ func RedirectToUser(ctx *Base, userName string, redirectUserID int64) { ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect) } -// RedirectToFirst redirects to first not empty URL -func (ctx *Context) RedirectToFirst(location ...string) { +// RedirectToFirst redirects to first not empty URL which likely belongs to current site. +// If no suitable redirection is found, it redirects to the home. +// It returns the location it redirected to. +func (ctx *Context) RedirectToFirst(location ...string) string { for _, loc := range location { if len(loc) == 0 { continue @@ -56,10 +58,11 @@ func (ctx *Context) RedirectToFirst(location ...string) { } ctx.Redirect(loc) - return + return loc } ctx.Redirect(setting.AppSubURL + "/") + return setting.AppSubURL + "/" } const tplStatus500 base.TplName = "status/500" diff --git a/services/context/repo.go b/services/context/repo.go index 43eeab8098..3e30f2ba97 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -848,7 +848,6 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string { case RepoRefBranch: ref := getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsBranchExist) if len(ref) == 0 { - // check if ref is HEAD parts := strings.Split(path, "/") if parts[0] == headRefName { @@ -991,7 +990,6 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context return cancel } ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() - } else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) { ctx.Repo.IsViewTag = true ctx.Repo.TagName = refName diff --git a/services/convert/convert.go b/services/convert/convert.go index 70ca5da2ec..55996d8fe3 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -185,12 +185,13 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo // ToTag convert a git.Tag to an api.Tag func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag { return &api.Tag{ - Name: t.Name, - Message: strings.TrimSpace(t.Message), - ID: t.ID.String(), - Commit: ToCommitMeta(repo, t), - ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"), - TarballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz"), + Name: t.Name, + Message: strings.TrimSpace(t.Message), + ID: t.ID.String(), + Commit: ToCommitMeta(repo, t), + ZipballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".zip"), + TarballURL: util.URLJoin(repo.HTMLURL(), "archive", t.Name+".tar.gz"), + ArchiveDownloadCount: t.ArchiveDownloadCount, } } @@ -363,13 +364,14 @@ func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([] // ToAnnotatedTag convert git.Tag to api.AnnotatedTag func ToAnnotatedTag(ctx context.Context, repo *repo_model.Repository, t *git.Tag, c *git.Commit) *api.AnnotatedTag { return &api.AnnotatedTag{ - Tag: t.Name, - SHA: t.ID.String(), - Object: ToAnnotatedTagObject(repo, c), - Message: t.Message, - URL: util.URLJoin(repo.APIURL(), "git/tags", t.ID.String()), - Tagger: ToCommitUser(t.Tagger), - Verification: ToVerification(ctx, c), + Tag: t.Name, + SHA: t.ID.String(), + Object: ToAnnotatedTagObject(repo, c), + Message: t.Message, + URL: util.URLJoin(repo.APIURL(), "git/tags", t.ID.String()), + Tagger: ToCommitUser(t.Tagger), + Verification: ToVerification(ctx, c), + ArchiveDownloadCount: t.ArchiveDownloadCount, } } diff --git a/services/convert/release.go b/services/convert/release.go index bfff53e62f..8c0f61b56c 100644 --- a/services/convert/release.go +++ b/services/convert/release.go @@ -13,21 +13,23 @@ import ( // ToAPIRelease convert a repo_model.Release to api.Release func ToAPIRelease(ctx context.Context, repo *repo_model.Repository, r *repo_model.Release) *api.Release { return &api.Release{ - ID: r.ID, - TagName: r.TagName, - Target: r.Target, - Title: r.Title, - Note: r.Note, - URL: r.APIURL(), - HTMLURL: r.HTMLURL(), - TarURL: r.TarURL(), - ZipURL: r.ZipURL(), - UploadURL: r.APIUploadURL(), - IsDraft: r.IsDraft, - IsPrerelease: r.IsPrerelease, - CreatedAt: r.CreatedUnix.AsTime(), - PublishedAt: r.CreatedUnix.AsTime(), - Publisher: ToUser(ctx, r.Publisher, nil), - Attachments: ToAPIAttachments(repo, r.Attachments), + ID: r.ID, + TagName: r.TagName, + Target: r.Target, + Title: r.Title, + Note: r.Note, + URL: r.APIURL(), + HTMLURL: r.HTMLURL(), + TarURL: r.TarURL(), + ZipURL: r.ZipURL(), + HideArchiveLinks: r.HideArchiveLinks, + UploadURL: r.APIUploadURL(), + IsDraft: r.IsDraft, + IsPrerelease: r.IsPrerelease, + CreatedAt: r.CreatedUnix.AsTime(), + PublishedAt: r.CreatedUnix.AsTime(), + Publisher: ToUser(ctx, r.Publisher, nil), + Attachments: ToAPIAttachments(repo, r.Attachments), + ArchiveDownloadCount: r.ArchiveDownloadCount, } } diff --git a/services/convert/repository.go b/services/convert/repository.go index 7b7f944d79..35becd96d0 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -77,9 +77,13 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR } } hasWiki := false + globallyEditableWiki := false var externalWiki *api.ExternalWiki - if _, err := repo.GetUnit(ctx, unit_model.TypeWiki); err == nil { + if wikiUnit, err := repo.GetUnit(ctx, unit_model.TypeWiki); err == nil { hasWiki = true + if wikiUnit.DefaultPermissions == repo_model.UnitAccessModeWrite { + globallyEditableWiki = true + } } else if unit, err := repo.GetUnit(ctx, unit_model.TypeExternalWiki); err == nil { hasWiki = true config := unit.ExternalWikiConfig() @@ -211,6 +215,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR InternalTracker: internalTracker, HasWiki: hasWiki, WikiBranch: repo.WikiBranch, + GloballyEditableWiki: globallyEditableWiki, HasProjects: hasProjects, HasReleases: hasReleases, HasPackages: hasPackages, diff --git a/services/convert/user.go b/services/convert/user.go index 98db53705b..789bc51097 100644 --- a/services/convert/user.go +++ b/services/convert/user.go @@ -76,6 +76,7 @@ func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *ap if authed { result.IsAdmin = user.IsAdmin result.LoginName = user.LoginName + result.SourceID = user.LoginSource result.LastLogin = user.LastLoginUnix.AsTime() result.Language = user.Language result.IsActive = user.IsActive diff --git a/services/doctor/dbconsistency.go b/services/doctor/dbconsistency.go index 0903ecc2a6..6b931b7036 100644 --- a/services/doctor/dbconsistency.go +++ b/services/doctor/dbconsistency.go @@ -159,6 +159,12 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er Fixer: actions_model.FixRunnersWithoutBelongingOwner, FixedMessage: "Removed", }, + { + Name: "Action Runners without existing repository", + Counter: actions_model.CountRunnersWithoutBelongingRepo, + Fixer: actions_model.FixRunnersWithoutBelongingRepo, + FixedMessage: "Removed", + }, { Name: "Topics with empty repository count", Counter: repo_model.CountOrphanedTopics, @@ -227,6 +233,9 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er // find redirects without existing user. genericOrphanCheck("Orphaned Redirects without existing redirect user", "user_redirect", "user", "user_redirect.redirect_user_id=`user`.id"), + // find archive download count without existing release + genericOrphanCheck("Archive download count without existing Release", + "repo_archive_download_count", "release", "repo_archive_download_count.release_id=release.id"), ) for _, c := range consistencyChecks { diff --git a/services/doctor/packages_nuget.go b/services/doctor/packages_nuget.go index 8c0a2d856d..47fdb3ac12 100644 --- a/services/doctor/packages_nuget.go +++ b/services/doctor/packages_nuget.go @@ -51,7 +51,6 @@ func PackagesNugetNuspecCheck(ctx context.Context, logger log.Logger, autofix bo logger.Info("Found %d versions for package %s", len(pvs), pkg.Name) for _, pv := range pvs { - pfs, err := packages.GetFilesByVersionID(ctx, pv.ID) if err != nil { logger.Error("Failed to get files for package version %s %s: %v", pkg.Name, pv.Version, err) diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index c9f3182b3a..a3eca9473b 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -26,6 +26,7 @@ type AuthenticationForm struct { AttributeUsername string AttributeName string AttributeSurname string + DefaultDomainName string AttributeMail string AttributeSSHPublicKey string AttributeAvatar string diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index e0540852af..e4fcf8e0c0 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -559,15 +559,16 @@ type UpdateAllowEditsForm struct { // NewReleaseForm form for creating release type NewReleaseForm struct { - TagName string `binding:"Required;GitRefName;MaxSize(255)"` - Target string `form:"tag_target" binding:"Required;MaxSize(255)"` - Title string `binding:"MaxSize(255)"` - Content string - Draft string - TagOnly string - Prerelease bool - AddTagMsg bool - Files []string + TagName string `binding:"Required;GitRefName;MaxSize(255)"` + Target string `form:"tag_target" binding:"Required;MaxSize(255)"` + Title string `binding:"MaxSize(255)"` + Content string + Draft string + TagOnly string + Prerelease bool + AddTagMsg bool + HideArchiveLinks bool + Files []string } // Validate validates the fields @@ -578,11 +579,12 @@ func (f *NewReleaseForm) Validate(req *http.Request, errs binding.Errors) bindin // EditReleaseForm form for changing release type EditReleaseForm struct { - Title string `form:"title" binding:"Required;MaxSize(255)"` - Content string `form:"content"` - Draft string `form:"draft"` - Prerelease bool `form:"prerelease"` - Files []string + Title string `form:"title" binding:"Required;MaxSize(255)"` + Content string `form:"content"` + Draft string `form:"draft"` + Prerelease bool `form:"prerelease"` + HideArchiveLinks bool + Files []string } // Validate validates the fields diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 0c89839edf..d9dbeedee5 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1040,8 +1040,8 @@ func createDiffFile(diff *Diff, line string) *DiffFile { // diff --git a/b b/b b/b b/b b/b b/b // midpoint := (len(line) + len(cmdDiffHead) - 1) / 2 - new, old := line[len(cmdDiffHead):midpoint], line[midpoint+1:] - if len(new) > 2 && len(old) > 2 && new[2:] == old[2:] { + newl, old := line[len(cmdDiffHead):midpoint], line[midpoint+1:] + if len(newl) > 2 && len(old) > 2 && newl[2:] == old[2:] { curFile.OldName = old[2:] curFile.Name = old[2:] } diff --git a/services/issue/commit.go b/services/issue/commit.go index e493a03211..8b927d52b6 100644 --- a/services/issue/commit.go +++ b/services/issue/commit.go @@ -117,7 +117,6 @@ func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_m var refIssue *issues_model.Issue var err error for _, ref := range references.FindAllIssueReferences(c.Message) { - // issue is from another repo if len(ref.Owner) > 0 && len(ref.Name) > 0 { refRepo, err = repo_model.GetRepositoryByOwnerAndName(ctx, ref.Owner, ref.Name) @@ -185,15 +184,15 @@ func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_m continue } } - close := ref.Action == references.XRefActionCloses - if close && len(ref.TimeLog) > 0 { + isClosed := ref.Action == references.XRefActionCloses + if isClosed && len(ref.TimeLog) > 0 { if err := issueAddTime(ctx, refIssue, doer, c.Timestamp, ref.TimeLog); err != nil { return err } } - if close != refIssue.IsClosed { + if isClosed != refIssue.IsClosed { refIssue.Repo = refRepo - if err := ChangeStatus(ctx, refIssue, doer, c.Sha1, close); err != nil { + if err := ChangeStatus(ctx, refIssue, doer, c.Sha1, isClosed); err != nil { return err } } diff --git a/services/migrations/gitea_downloader.go b/services/migrations/gitea_downloader.go index d402a238f2..272bf02e11 100644 --- a/services/migrations/gitea_downloader.go +++ b/services/migrations/gitea_downloader.go @@ -410,7 +410,6 @@ func (g *GiteaDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, err return nil, false, fmt.Errorf("error while listing issues: %w", err) } for _, issue := range issues { - labels := make([]*base.Label, 0, len(issue.Labels)) for i := range issue.Labels { labels = append(labels, g.convertGiteaLabel(issue.Labels[i])) diff --git a/services/migrations/gitlab.go b/services/migrations/gitlab.go index bbc44e958a..065b687fa6 100644 --- a/services/migrations/gitlab.go +++ b/services/migrations/gitlab.go @@ -421,7 +421,6 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er return nil, false, fmt.Errorf("error while listing issues: %w", err) } for _, issue := range issues { - labels := make([]*base.Label, 0, len(issue.Labels)) for _, l := range issue.Labels { labels = append(labels, &base.Label{ diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go index 72e545581a..0270f87039 100644 --- a/services/mirror/mirror.go +++ b/services/mirror/mirror.go @@ -40,7 +40,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { } log.Trace("Doing: Update") - handler := func(idx int, bean any) error { + handler := func(bean any) error { var repo *repo_model.Repository var mirrorType SyncType var referenceID int64 @@ -91,7 +91,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { pullMirrorsRequested := 0 if pullLimit != 0 { if err := repo_model.MirrorsIterate(ctx, pullLimit, func(idx int, bean any) error { - if err := handler(idx, bean); err != nil { + if err := handler(bean); err != nil { return err } pullMirrorsRequested++ @@ -105,7 +105,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { pushMirrorsRequested := 0 if pushLimit != 0 { if err := repo_model.PushMirrorsIterate(ctx, pushLimit, func(idx int, bean any) error { - if err := handler(idx, bean); err != nil { + if err := handler(bean); err != nil { return err } pushMirrorsRequested++ diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index fa23986c54..9f7ffb29c9 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -466,7 +466,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { log.Trace("SyncMirrors [repo: %-v]: %d branches updated", m.Repo, len(results)) if len(results) > 0 { - if ok := checkAndUpdateEmptyRepository(ctx, m, gitRepo, results); !ok { + if ok := checkAndUpdateEmptyRepository(ctx, m, results); !ok { log.Error("SyncMirrors [repo: %-v]: checkAndUpdateEmptyRepository: %v", m.Repo, err) return false } @@ -523,13 +523,13 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { theCommits.Commits = theCommits.Commits[:setting.UI.FeedMaxCommitNum] } - if newCommit, err := gitRepo.GetCommit(newCommitID); err != nil { + newCommit, err := gitRepo.GetCommit(newCommitID) + if err != nil { log.Error("SyncMirrors [repo: %-v]: unable to get commit %s: %v", m.Repo, newCommitID, err) continue - } else { - theCommits.HeadCommit = repo_module.CommitToPushCommit(newCommit) } + theCommits.HeadCommit = repo_module.CommitToPushCommit(newCommit) theCommits.CompareURL = m.Repo.ComposeCompareURL(oldCommitID, newCommitID) notify_service.SyncPushCommits(ctx, m.Repo.MustOwner(ctx), m.Repo, &repo_module.PushUpdateOptions{ @@ -557,7 +557,6 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { log.Error("SyncMirrors [repo: %-v]: unable to update repository 'updated_unix': %v", m.Repo, err) return false } - } log.Trace("SyncMirrors [repo: %-v]: Successfully updated", m.Repo) @@ -565,7 +564,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { return true } -func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, gitRepo *git.Repository, results []*mirrorSyncResult) bool { +func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, results []*mirrorSyncResult) bool { if !m.Repo.IsEmpty { return true } diff --git a/services/pull/merge.go b/services/pull/merge.go index 2989d77c6a..1d6431ab66 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -237,9 +237,9 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U if err = ref.Issue.LoadRepo(ctx); err != nil { return err } - close := ref.RefAction == references.XRefActionCloses - if close != ref.Issue.IsClosed { - if err = issue_service.ChangeStatus(ctx, ref.Issue, doer, pr.MergedCommitID, close); err != nil { + isClosed := ref.RefAction == references.XRefActionCloses + if isClosed != ref.Issue.IsClosed { + if err = issue_service.ChangeStatus(ctx, ref.Issue, doer, pr.MergedCommitID, isClosed); err != nil { // Allow ErrDependenciesLeft if !issues_model.IsErrDependenciesLeft(err) { return err diff --git a/services/pull/pull.go b/services/pull/pull.go index 720efdb0cb..5fa426483f 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -803,7 +803,6 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ if err != nil { log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err) return "" - } if len(commits) == 0 { break diff --git a/services/pull/review.go b/services/pull/review.go index 6ad931b679..cff6f346ae 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -6,6 +6,7 @@ package pull import ( "context" + "errors" "fmt" "io" "regexp" @@ -43,6 +44,9 @@ func (err ErrDismissRequestOnClosedPR) Unwrap() error { return util.ErrPermissionDenied } +// ErrSubmitReviewOnClosedPR represents an error when an user tries to submit an approve or reject review associated to a closed or merged PR. +var ErrSubmitReviewOnClosedPR = errors.New("can't submit review for a closed or merged PR") + // checkInvalidation checks if the line of code comment got changed by another commit. // If the line got changed the comment is going to be invalidated. func checkInvalidation(ctx context.Context, c *issues_model.Comment, doer *user_model.User, repo *git.Repository, branch string) error { @@ -293,6 +297,10 @@ func SubmitReview(ctx context.Context, doer *user_model.User, gitRepo *git.Repos if reviewType != issues_model.ReviewTypeApprove && reviewType != issues_model.ReviewTypeReject { stale = false } else { + if issue.IsClosed { + return nil, nil, ErrSubmitReviewOnClosedPR + } + headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName()) if err != nil { return nil, nil, err diff --git a/services/release/release.go b/services/release/release.go index c349ab5143..5062af1436 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -318,6 +318,11 @@ func DeleteReleaseByID(ctx context.Context, repo *repo_model.Repository, rel *re } } + err = repo_model.DeleteArchiveDownloadCountForRelease(ctx, rel.ID) + if err != nil { + return err + } + if stdout, _, err := git.NewCommand(ctx, "tag", "-d").AddDashesAndList(rel.TagName). SetDescription(fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID)). RunStdString(&git.RunOpts{Dir: repo.RepoPath()}); err != nil && !strings.Contains(err.Error(), "not found") { diff --git a/services/remote/promote.go b/services/remote/promote.go new file mode 100644 index 0000000000..6709b4cc1d --- /dev/null +++ b/services/remote/promote.go @@ -0,0 +1,133 @@ +// Copyright Earl Warren +// SPDX-License-Identifier: MIT + +package remote + +import ( + "context" + + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/services/auth/source/oauth2" + remote_source "code.gitea.io/gitea/services/auth/source/remote" +) + +type Reason int + +const ( + ReasonNoMatch Reason = iota + ReasonNotAuth2 + ReasonBadAuth2 + ReasonLoginNameNotExists + ReasonNotRemote + ReasonEmailIsSet + ReasonNoSource + ReasonSourceWrongType + ReasonCanPromote + ReasonPromoted + ReasonUpdateFail + ReasonErrorLoginName + ReasonErrorGetSource +) + +func NewReason(level log.Level, reason Reason, message string, args ...any) Reason { + log.Log(1, level, message, args...) + return reason +} + +func getUsersByLoginName(ctx context.Context, name string) ([]*user_model.User, error) { + if len(name) == 0 { + return nil, user_model.ErrUserNotExist{Name: name} + } + + users := make([]*user_model.User, 0, 5) + + return users, db.GetEngine(ctx). + Table("user"). + Where("login_name = ? AND login_type = ? AND type = ?", name, auth_model.Remote, user_model.UserTypeRemoteUser). + Find(&users) +} + +// The remote user has: +// +// Type UserTypeRemoteUser +// LogingType Remote +// LoginName set to the unique identifier of the originating authentication source +// LoginSource set to the Remote source that can be matched against an OAuth2 source +// +// If the source from which an authentification happens is OAuth2, an existing +// remote user will be promoted to an OAuth2 user provided: +// +// user.LoginName is the same as goth.UserID (argument loginName) +// user.LoginSource has a MatchingSource equals to the name of the OAuth2 provider +// +// Once promoted, the user will be logged in without further interaction from the +// user and will own all repositories, issues, etc. associated with it. +func MaybePromoteRemoteUser(ctx context.Context, source *auth_model.Source, loginName, email string) (promoted bool, reason Reason, err error) { + user, reason, err := getRemoteUserToPromote(ctx, source, loginName, email) + if err != nil || user == nil { + return false, reason, err + } + promote := &user_model.User{ + ID: user.ID, + Type: user_model.UserTypeIndividual, + Email: email, + LoginSource: source.ID, + LoginType: source.Type, + } + reason = NewReason(log.DEBUG, ReasonPromoted, "promote user %v: LoginName %v => %v, LoginSource %v => %v, LoginType %v => %v, Email %v => %v", user.ID, user.LoginName, promote.LoginName, user.LoginSource, promote.LoginSource, user.LoginType, promote.LoginType, user.Email, promote.Email) + if err := user_model.UpdateUserCols(ctx, promote, "type", "email", "login_source", "login_type"); err != nil { + return false, ReasonUpdateFail, err + } + return true, reason, nil +} + +func getRemoteUserToPromote(ctx context.Context, source *auth_model.Source, loginName, email string) (*user_model.User, Reason, error) { + if !source.IsOAuth2() { + return nil, NewReason(log.DEBUG, ReasonNotAuth2, "source %v is not OAuth2", source), nil + } + oauth2Source, ok := source.Cfg.(*oauth2.Source) + if !ok { + return nil, NewReason(log.ERROR, ReasonBadAuth2, "source claims to be OAuth2 but is not"), nil + } + + users, err := getUsersByLoginName(ctx, loginName) + if err != nil { + return nil, NewReason(log.ERROR, ReasonErrorLoginName, "getUserByLoginName('%s') %v", loginName, err), err + } + if len(users) == 0 { + return nil, NewReason(log.ERROR, ReasonLoginNameNotExists, "no user with LoginType UserTypeRemoteUser and LoginName '%s'", loginName), nil + } + + reason := ReasonNoSource + for _, u := range users { + userSource, err := auth_model.GetSourceByID(ctx, u.LoginSource) + if err != nil { + if auth_model.IsErrSourceNotExist(err) { + reason = NewReason(log.DEBUG, ReasonNoSource, "source id = %v for user %v not found %v", u.LoginSource, u.ID, err) + continue + } + return nil, NewReason(log.ERROR, ReasonErrorGetSource, "GetSourceByID('%s') %v", u.LoginSource, err), err + } + if u.Email != "" { + reason = NewReason(log.DEBUG, ReasonEmailIsSet, "the user email is already set to '%s'", u.Email) + continue + } + remoteSource, ok := userSource.Cfg.(*remote_source.Source) + if !ok { + reason = NewReason(log.DEBUG, ReasonSourceWrongType, "expected a remote source but got %T %v", userSource, userSource) + continue + } + + if oauth2Source.Provider != remoteSource.MatchingSource { + reason = NewReason(log.DEBUG, ReasonNoMatch, "skip OAuth2 source %s because it is different from %s which is the expected match for the remote source %s", oauth2Source.Provider, remoteSource.MatchingSource, remoteSource.URL) + continue + } + + return u, ReasonCanPromote, nil + } + + return nil, reason, nil +} diff --git a/services/repository/adopt.go b/services/repository/adopt.go index b337eac38a..31e3e581b3 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -357,7 +357,6 @@ func ListUnadoptedRepositories(ctx context.Context, query string, opts *db.ListO return err } repoNamesToCheck = repoNamesToCheck[:0] - } return filepath.SkipDir }); err != nil { diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go index 01c58f0ce4..c74712b4ba 100644 --- a/services/repository/archiver/archiver.go +++ b/services/repository/archiver/archiver.go @@ -30,10 +30,11 @@ import ( // This is entirely opaque to external entities, though, and mostly used as a // handle elsewhere. type ArchiveRequest struct { - RepoID int64 - refName string - Type git.ArchiveType - CommitID string + RepoID int64 + refName string + Type git.ArchiveType + CommitID string + ReleaseID int64 } // ErrUnknownArchiveFormat request archive format is not supported @@ -70,7 +71,7 @@ func (e RepoRefNotFoundError) Is(err error) bool { // NewRequest creates an archival request, based on the URI. The // resulting ArchiveRequest is suitable for being passed to ArchiveRepository() // if it's determined that the request still needs to be satisfied. -func NewRequest(repoID int64, repo *git.Repository, uri string) (*ArchiveRequest, error) { +func NewRequest(ctx context.Context, repoID int64, repo *git.Repository, uri string) (*ArchiveRequest, error) { r := &ArchiveRequest{ RepoID: repoID, } @@ -99,6 +100,17 @@ func NewRequest(repoID int64, repo *git.Repository, uri string) (*ArchiveRequest } r.CommitID = commitID.String() + + release, err := repo_model.GetRelease(ctx, repoID, r.refName) + if err != nil { + if !repo_model.IsErrReleaseNotExist(err) { + return nil, err + } + } + if release != nil { + r.ReleaseID = release.ID + } + return r, nil } @@ -120,6 +132,10 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver return nil, fmt.Errorf("models.GetRepoArchiver: %w", err) } + if archiver != nil { + archiver.ReleaseID = aReq.ReleaseID + } + if archiver != nil && archiver.Status == repo_model.ArchiverReady { // Archive already generated, we're done. return archiver, nil @@ -145,6 +161,7 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver return nil, fmt.Errorf("repo_model.GetRepoArchiver: %w", err) } if archiver != nil && archiver.Status == repo_model.ArchiverReady { + archiver.ReleaseID = aReq.ReleaseID return archiver, nil } } diff --git a/services/repository/archiver/archiver_test.go b/services/repository/archiver/archiver_test.go index ec6e9dfac3..dbd4d9b3c7 100644 --- a/services/repository/archiver/archiver_test.go +++ b/services/repository/archiver/archiver_test.go @@ -31,47 +31,47 @@ func TestArchive_Basic(t *testing.T) { contexttest.LoadGitRepo(t, ctx) defer ctx.Repo.GitRepo.Close() - bogusReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") + bogusReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") assert.NoError(t, err) assert.NotNil(t, bogusReq) assert.EqualValues(t, firstCommit+".zip", bogusReq.GetArchiveName()) // Check a series of bogus requests. // Step 1, valid commit with a bad extension. - bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".dilbert") + bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".dilbert") assert.Error(t, err) assert.Nil(t, bogusReq) // Step 2, missing commit. - bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff.zip") + bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff.zip") assert.Error(t, err) assert.Nil(t, bogusReq) // Step 3, doesn't look like branch/tag/commit. - bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "db.zip") + bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "db.zip") assert.Error(t, err) assert.Nil(t, bogusReq) - bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master.zip") + bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master.zip") assert.NoError(t, err) assert.NotNil(t, bogusReq) assert.EqualValues(t, "master.zip", bogusReq.GetArchiveName()) - bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive.zip") + bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive.zip") assert.NoError(t, err) assert.NotNil(t, bogusReq) assert.EqualValues(t, "test-archive.zip", bogusReq.GetArchiveName()) // Now two valid requests, firstCommit with valid extensions. - zipReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") + zipReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") assert.NoError(t, err) assert.NotNil(t, zipReq) - tgzReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".tar.gz") + tgzReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".tar.gz") assert.NoError(t, err) assert.NotNil(t, tgzReq) - secondReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".zip") + secondReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".zip") assert.NoError(t, err) assert.NotNil(t, secondReq) @@ -91,7 +91,7 @@ func TestArchive_Basic(t *testing.T) { // Sleep two seconds to make sure the queue doesn't change. time.Sleep(2 * time.Second) - zipReq2, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") + zipReq2, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") assert.NoError(t, err) // This zipReq should match what's sitting in the queue, as we haven't // let it release yet. From the consumer's point of view, this looks like @@ -106,12 +106,12 @@ func TestArchive_Basic(t *testing.T) { // Now we'll submit a request and TimedWaitForCompletion twice, before and // after we release it. We should trigger both the timeout and non-timeout // cases. - timedReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".tar.gz") + timedReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".tar.gz") assert.NoError(t, err) assert.NotNil(t, timedReq) ArchiveRepository(db.DefaultContext, timedReq) - zipReq2, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") + zipReq2, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") assert.NoError(t, err) // Now, we're guaranteed to have released the original zipReq from the queue. // Ensure that we don't get handed back the released entry somehow, but they diff --git a/services/repository/commitstatus/commitstatus.go b/services/repository/commitstatus/commitstatus.go index 04a0ea0b38..1944f11f03 100644 --- a/services/repository/commitstatus/commitstatus.go +++ b/services/repository/commitstatus/commitstatus.go @@ -7,6 +7,7 @@ import ( "context" "crypto/sha256" "fmt" + "slices" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" @@ -89,13 +90,19 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato sha = commit.ID.String() } - if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{ - Repo: repo, - Creator: creator, - SHA: commit.ID, - CommitStatus: status, + if err := db.WithTx(ctx, func(ctx context.Context) error { + if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{ + Repo: repo, + Creator: creator, + SHA: commit.ID, + CommitStatus: status, + }); err != nil { + return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err) + } + + return git_model.UpdateCommitStatusSummary(ctx, repo.ID, commit.ID.String()) }); err != nil { - return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err) + return err } defaultBranchCommit, err := gitRepo.GetBranchCommit(repo.DefaultBranch) @@ -147,8 +154,35 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep return nil, fmt.Errorf("FindBranchesByRepoAndBranchName: %v", err) } + var repoSHAs []git_model.RepoSHA + for id, sha := range repoIDsToLatestCommitSHAs { + repoSHAs = append(repoSHAs, git_model.RepoSHA{RepoID: id, SHA: sha}) + } + + summaryResults, err := git_model.GetLatestCommitStatusForRepoAndSHAs(ctx, repoSHAs) + if err != nil { + return nil, fmt.Errorf("GetLatestCommitStatusForRepoAndSHAs: %v", err) + } + + for _, summary := range summaryResults { + for i, repo := range repos { + if repo.ID == summary.RepoID { + results[i] = summary + _ = slices.DeleteFunc(repoSHAs, func(repoSHA git_model.RepoSHA) bool { + return repoSHA.RepoID == repo.ID + }) + if results[i].State != "" { + if err := updateCommitStatusCache(repo.ID, repo.DefaultBranch, results[i].State, results[i].TargetURL); err != nil { + log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err) + } + } + break + } + } + } + // call the database O(1) times to get the commit statuses for all repos - repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoIDsToLatestCommitSHAs, db.ListOptionsAll) + repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoSHAs) if err != nil { return nil, fmt.Errorf("GetLatestCommitStatusForPairs: %v", err) } diff --git a/services/repository/contributors_graph.go b/services/repository/contributors_graph.go index 516e7f1475..7241d3d655 100644 --- a/services/repository/contributors_graph.go +++ b/services/repository/contributors_graph.go @@ -194,7 +194,6 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int Stats: &commitStats, } extendedCommitStats = append(extendedCommitStats, res) - } _ = stdoutReader.Close() return nil diff --git a/services/repository/delete.go b/services/repository/delete.go index d5a8849033..21d9ebcf0b 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -162,6 +162,7 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID &actions_model.ActionScheduleSpec{RepoID: repoID}, &actions_model.ActionSchedule{RepoID: repoID}, &actions_model.ActionArtifact{RepoID: repoID}, + &repo_model.RepoArchiveDownloadCount{RepoID: repoID}, &actions_model.ActionRunnerToken{RepoID: repoID}, ); err != nil { return fmt.Errorf("deleteBeans: %w", err) diff --git a/services/repository/files/update.go b/services/repository/files/update.go index e677949e86..81a61da5ed 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -208,7 +208,6 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use return nil, fmt.Errorf("ConvertToSHA1: Invalid last commit ID: %w", err) } opts.LastCommitID = lastCommitID.String() - } for _, file := range opts.Files { @@ -360,7 +359,6 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep Path: file.Options.treePath, } } - } } diff --git a/services/user/delete.go b/services/user/delete.go index e890990994..74dbc09b82 100644 --- a/services/user/delete.go +++ b/services/user/delete.go @@ -106,7 +106,6 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error) if purge || (setting.Service.UserDeleteWithCommentsMaxTime != 0 && u.CreatedUnix.AsTime().Add(setting.Service.UserDeleteWithCommentsMaxTime).After(time.Now())) { - // Delete Comments const batchSize = 50 for { diff --git a/services/user/update_test.go b/services/user/update_test.go index 7ed764b539..c2ff26a140 100644 --- a/services/user/update_test.go +++ b/services/user/update_test.go @@ -94,7 +94,7 @@ func TestUpdateAuth(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) - copy := *user + userCopy := *user assert.NoError(t, UpdateAuth(db.DefaultContext, user, &UpdateAuthOptions{ LoginName: optional.Some("new-login"), @@ -106,8 +106,8 @@ func TestUpdateAuth(t *testing.T) { MustChangePassword: optional.Some(true), })) assert.True(t, user.MustChangePassword) - assert.NotEqual(t, copy.Passwd, user.Passwd) - assert.NotEqual(t, copy.Salt, user.Salt) + assert.NotEqual(t, userCopy.Passwd, user.Passwd) + assert.NotEqual(t, userCopy.Salt, user.Salt) assert.NoError(t, UpdateAuth(db.DefaultContext, user, &UpdateAuthOptions{ ProhibitLogin: optional.Some(true), diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index d488b1725a..8db1c72572 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -78,7 +78,13 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error { } if authorization != "" { req.Header.Set("Authorization", authorization) - t.RequestInfo.Headers["Authorization"] = "******" + redacted := "******" + if strings.HasPrefix(authorization, "Bearer ") { + redacted = "Bearer " + redacted + } else if strings.HasPrefix(authorization, "Basic ") { + redacted = "Basic " + redacted + } + t.RequestInfo.Headers["Authorization"] = redacted } t.ResponseInfo = &webhook_model.HookResponse{ diff --git a/services/webhook/deliver_test.go b/services/webhook/deliver_test.go index c689332666..0311d810e6 100644 --- a/services/webhook/deliver_test.go +++ b/services/webhook/deliver_test.go @@ -132,7 +132,7 @@ func TestWebhookDeliverAuthorizationHeader(t *testing.T) { } assert.True(t, hookTask.IsSucceed) - assert.Equal(t, "******", hookTask.RequestInfo.Headers["Authorization"]) + assert.Equal(t, "Bearer ******", hookTask.RequestInfo.Headers["Authorization"]) } func TestWebhookDeliverHookTask(t *testing.T) { @@ -152,7 +152,6 @@ func TestWebhookDeliverHookTask(t *testing.T) { case "/webhook/6db5dc1e282529a8c162c7fe93dd2667494eeb51": // Version 2 - assert.Equal(t, "push", r.Header.Get("X-GitHub-Event")) assert.Equal(t, "application/json", r.Header.Get("Content-Type")) body, err := io.ReadAll(r.Body) assert.NoError(t, err) diff --git a/services/webhook/discord.go b/services/webhook/discord.go index cb756688c8..80f6cfb79b 100644 --- a/services/webhook/discord.go +++ b/services/webhook/discord.go @@ -304,14 +304,12 @@ func (discordHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, error) { switch event { - case webhook_module.HookEventPullRequestReviewApproved: return "approved", nil case webhook_module.HookEventPullRequestReviewRejected: return "rejected", nil case webhook_module.HookEventPullRequestReviewComment: return "comment", nil - default: return "", errors.New("unknown event type") } diff --git a/services/webhook/matrix.go b/services/webhook/matrix.go index 697e33e94c..06176e8dd3 100644 --- a/services/webhook/matrix.go +++ b/services/webhook/matrix.go @@ -42,16 +42,15 @@ func (matrixHandler) UnmarshalForm(bind func(any)) forms.WebhookForm { HomeserverURL string `binding:"Required;ValidUrl"` RoomID string `binding:"Required"` MessageType int - - // enforce requirement of authorization_header - // (value will still be set in the embedded WebhookCoreForm) - AuthorizationHeader string `binding:"Required"` + AccessToken string `binding:"Required"` } bind(&form) + form.AuthorizationHeader = "Bearer " + strings.TrimSpace(form.AccessToken) + // https://spec.matrix.org/v1.10/client-server-api/#sending-events-to-a-room return forms.WebhookForm{ WebhookCoreForm: form.WebhookCoreForm, - URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), + URL: fmt.Sprintf("%s/_matrix/client/v3/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), ContentType: webhook_model.ContentTypeJSON, Secret: "", HTTPMethod: http.MethodPut, @@ -91,7 +90,7 @@ func (matrixHandler) NewRequest(ctx context.Context, w *webhook_model.Webhook, t } req.Header.Set("Content-Type", "application/json") - return req, body, shared.AddDefaultHeaders(req, []byte(w.Secret), t, body) // likely useless, but has always been sent historially + return req, body, nil } const matrixPayloadSizeLimit = 1024 * 64 @@ -218,7 +217,6 @@ func (m matrixConvertor) Push(p *api.PushPayload) (MatrixPayload, error) { if i < len(p.Commits)-1 { text += "
    " } - } return m.newPayload(text, p.Commits...) diff --git a/services/webhook/matrix_test.go b/services/webhook/matrix_test.go index 0269ccccea..7031a45bec 100644 --- a/services/webhook/matrix_test.go +++ b/services/webhook/matrix_test.go @@ -201,7 +201,7 @@ func TestMatrixJSONPayload(t *testing.T) { RepoID: 3, IsActive: true, Type: webhook_module.MATRIX, - URL: "https://matrix.example.com/_matrix/client/r0/rooms/ROOM_ID/send/m.room.message", + URL: "https://matrix.example.com/_matrix/client/v3/rooms/ROOM_ID/send/m.room.message", Meta: `{"message_type":0}`, // text } task := &webhook_model.HookTask{ @@ -217,8 +217,7 @@ func TestMatrixJSONPayload(t *testing.T) { require.NoError(t, err) assert.Equal(t, "PUT", req.Method) - assert.Equal(t, "/_matrix/client/r0/rooms/ROOM_ID/send/m.room.message/6db5dc1e282529a8c162c7fe93dd2667494eeb51", req.URL.Path) - assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) + assert.Equal(t, "/_matrix/client/v3/rooms/ROOM_ID/send/m.room.message/6db5dc1e282529a8c162c7fe93dd2667494eeb51", req.URL.Path) assert.Equal(t, "application/json", req.Header.Get("Content-Type")) var body MatrixPayload err = json.NewDecoder(req.Body).Decode(&body) diff --git a/services/webhook/sourcehut/builds.go b/services/webhook/sourcehut/builds.go index 1561b9e6e6..e7501110a2 100644 --- a/services/webhook/sourcehut/builds.go +++ b/services/webhook/sourcehut/builds.go @@ -49,6 +49,7 @@ type buildsForm struct { ManifestPath string `binding:"Required"` Visibility string `binding:"Required;In(PUBLIC,UNLISTED,PRIVATE)"` Secrets bool + AccessToken string `binding:"Required"` } var _ binding.Validator = &buildsForm{} @@ -63,13 +64,7 @@ func (f *buildsForm) Validate(req *http.Request, errs binding.Errors) binding.Er Message: ctx.Locale.TrString("repo.settings.add_webhook.invalid_path"), }) } - if !strings.HasPrefix(f.AuthorizationHeader, "Bearer ") { - errs = append(errs, binding.Error{ - FieldNames: []string{"AuthorizationHeader"}, - Classification: "", - Message: ctx.Locale.TrString("form.required_prefix", "Bearer "), - }) - } + f.AuthorizationHeader = "Bearer " + strings.TrimSpace(f.AccessToken) return errs } diff --git a/stylelint.config.js b/stylelint.config.js new file mode 100644 index 0000000000..523b18841e --- /dev/null +++ b/stylelint.config.js @@ -0,0 +1,246 @@ +import {fileURLToPath} from 'node:url'; + +const cssVarFiles = [ + fileURLToPath(new URL('web_src/css/base.css', import.meta.url)), + fileURLToPath(new URL('web_src/css/themes/theme-gitea-light.css', import.meta.url)), + fileURLToPath(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url)), +]; + +/** @type {import('stylelint').Config} */ +export default { + plugins: [ + 'stylelint-declaration-strict-value', + 'stylelint-declaration-block-no-ignored-properties', + 'stylelint-value-no-unknown-custom-properties', + '@stylistic/stylelint-plugin', + ], + ignoreFiles: [ + '**/*.go', + '/web_src/fomantic', + ], + overrides: [ + { + files: ['**/chroma/*', '**/codemirror/*', '**/standalone/*', '**/console.css', 'font_i18n.css'], + rules: { + 'scale-unlimited/declaration-strict-value': null, + }, + }, + { + files: ['**/chroma/*', '**/codemirror/*'], + rules: { + 'block-no-empty': null, + }, + }, + { + files: ['**/*.vue'], + customSyntax: 'postcss-html', + }, + ], + rules: { + '@stylistic/at-rule-name-case': null, + '@stylistic/at-rule-name-newline-after': null, + '@stylistic/at-rule-name-space-after': null, + '@stylistic/at-rule-semicolon-newline-after': null, + '@stylistic/at-rule-semicolon-space-before': null, + '@stylistic/block-closing-brace-empty-line-before': null, + '@stylistic/block-closing-brace-newline-after': null, + '@stylistic/block-closing-brace-newline-before': null, + '@stylistic/block-closing-brace-space-after': null, + '@stylistic/block-closing-brace-space-before': null, + '@stylistic/block-opening-brace-newline-after': null, + '@stylistic/block-opening-brace-newline-before': null, + '@stylistic/block-opening-brace-space-after': null, + '@stylistic/block-opening-brace-space-before': 'always', + '@stylistic/color-hex-case': 'lower', + '@stylistic/declaration-bang-space-after': 'never', + '@stylistic/declaration-bang-space-before': null, + '@stylistic/declaration-block-semicolon-newline-after': null, + '@stylistic/declaration-block-semicolon-newline-before': null, + '@stylistic/declaration-block-semicolon-space-after': null, + '@stylistic/declaration-block-semicolon-space-before': 'never', + '@stylistic/declaration-block-trailing-semicolon': null, + '@stylistic/declaration-colon-newline-after': null, + '@stylistic/declaration-colon-space-after': null, + '@stylistic/declaration-colon-space-before': 'never', + '@stylistic/function-comma-newline-after': null, + '@stylistic/function-comma-newline-before': null, + '@stylistic/function-comma-space-after': null, + '@stylistic/function-comma-space-before': null, + '@stylistic/function-max-empty-lines': 0, + '@stylistic/function-parentheses-newline-inside': 'never-multi-line', + '@stylistic/function-parentheses-space-inside': null, + '@stylistic/function-whitespace-after': null, + '@stylistic/indentation': 2, + '@stylistic/linebreaks': null, + '@stylistic/max-empty-lines': 1, + '@stylistic/max-line-length': null, + '@stylistic/media-feature-colon-space-after': null, + '@stylistic/media-feature-colon-space-before': 'never', + '@stylistic/media-feature-name-case': null, + '@stylistic/media-feature-parentheses-space-inside': null, + '@stylistic/media-feature-range-operator-space-after': 'always', + '@stylistic/media-feature-range-operator-space-before': 'always', + '@stylistic/media-query-list-comma-newline-after': null, + '@stylistic/media-query-list-comma-newline-before': null, + '@stylistic/media-query-list-comma-space-after': null, + '@stylistic/media-query-list-comma-space-before': null, + '@stylistic/named-grid-areas-alignment': null, + '@stylistic/no-empty-first-line': null, + '@stylistic/no-eol-whitespace': true, + '@stylistic/no-extra-semicolons': true, + '@stylistic/no-missing-end-of-source-newline': null, + '@stylistic/number-leading-zero': null, + '@stylistic/number-no-trailing-zeros': null, + '@stylistic/property-case': 'lower', + '@stylistic/selector-attribute-brackets-space-inside': null, + '@stylistic/selector-attribute-operator-space-after': null, + '@stylistic/selector-attribute-operator-space-before': null, + '@stylistic/selector-combinator-space-after': null, + '@stylistic/selector-combinator-space-before': null, + '@stylistic/selector-descendant-combinator-no-non-space': null, + '@stylistic/selector-list-comma-newline-after': null, + '@stylistic/selector-list-comma-newline-before': null, + '@stylistic/selector-list-comma-space-after': 'always-single-line', + '@stylistic/selector-list-comma-space-before': 'never-single-line', + '@stylistic/selector-max-empty-lines': 0, + '@stylistic/selector-pseudo-class-case': 'lower', + '@stylistic/selector-pseudo-class-parentheses-space-inside': 'never', + '@stylistic/selector-pseudo-element-case': 'lower', + '@stylistic/string-quotes': 'double', + '@stylistic/unicode-bom': null, + '@stylistic/unit-case': 'lower', + '@stylistic/value-list-comma-newline-after': null, + '@stylistic/value-list-comma-newline-before': null, + '@stylistic/value-list-comma-space-after': null, + '@stylistic/value-list-comma-space-before': null, + '@stylistic/value-list-max-empty-lines': 0, + 'alpha-value-notation': null, + 'annotation-no-unknown': true, + 'at-rule-allowed-list': null, + 'at-rule-disallowed-list': null, + 'at-rule-empty-line-before': null, + 'at-rule-no-unknown': [true, {ignoreAtRules: ['tailwind']}], + 'at-rule-no-vendor-prefix': true, + 'at-rule-property-required-list': null, + 'block-no-empty': true, + 'color-function-notation': null, + 'color-hex-alpha': null, + 'color-hex-length': null, + 'color-named': null, + 'color-no-hex': null, + 'color-no-invalid-hex': true, + 'comment-empty-line-before': null, + 'comment-no-empty': true, + 'comment-pattern': null, + 'comment-whitespace-inside': null, + 'comment-word-disallowed-list': null, + 'csstools/value-no-unknown-custom-properties': [true, {importFrom: cssVarFiles}], + 'custom-media-pattern': null, + 'custom-property-empty-line-before': null, + 'custom-property-no-missing-var-function': true, + 'custom-property-pattern': null, + 'declaration-block-no-duplicate-custom-properties': true, + 'declaration-block-no-duplicate-properties': [true, {ignore: ['consecutive-duplicates-with-different-values']}], + 'declaration-block-no-redundant-longhand-properties': null, + 'declaration-block-no-shorthand-property-overrides': null, + 'declaration-block-single-line-max-declarations': null, + 'declaration-empty-line-before': null, + 'declaration-no-important': null, + 'declaration-property-max-values': null, + 'declaration-property-unit-allowed-list': null, + 'declaration-property-unit-disallowed-list': {'line-height': ['em']}, + 'declaration-property-value-allowed-list': null, + 'declaration-property-value-disallowed-list': null, + 'declaration-property-value-no-unknown': true, + 'font-family-name-quotes': 'always-where-recommended', + 'font-family-no-duplicate-names': true, + 'font-family-no-missing-generic-family-keyword': true, + 'font-weight-notation': null, + 'function-allowed-list': null, + 'function-calc-no-unspaced-operator': true, + 'function-disallowed-list': null, + 'function-linear-gradient-no-nonstandard-direction': true, + 'function-name-case': 'lower', + 'function-no-unknown': true, + 'function-url-no-scheme-relative': null, + 'function-url-quotes': 'always', + 'function-url-scheme-allowed-list': null, + 'function-url-scheme-disallowed-list': null, + 'hue-degree-notation': null, + 'import-notation': 'string', + 'keyframe-block-no-duplicate-selectors': true, + 'keyframe-declaration-no-important': true, + 'keyframe-selector-notation': null, + 'keyframes-name-pattern': null, + 'length-zero-no-unit': [true, {ignore: ['custom-properties']}, {ignoreFunctions: ['var']}], + 'max-nesting-depth': null, + 'media-feature-name-allowed-list': null, + 'media-feature-name-disallowed-list': null, + 'media-feature-name-no-unknown': true, + 'media-feature-name-no-vendor-prefix': true, + 'media-feature-name-unit-allowed-list': null, + 'media-feature-name-value-allowed-list': null, + 'media-feature-name-value-no-unknown': true, + 'media-feature-range-notation': null, + 'media-query-no-invalid': true, + 'named-grid-areas-no-invalid': true, + 'no-descending-specificity': null, + 'no-duplicate-at-import-rules': true, + 'no-duplicate-selectors': true, + 'no-empty-source': true, + 'no-invalid-double-slash-comments': true, + 'no-invalid-position-at-import-rule': [true, {ignoreAtRules: ['tailwind']}], + 'no-irregular-whitespace': true, + 'no-unknown-animations': null, + 'no-unknown-custom-properties': null, + 'number-max-precision': null, + 'plugin/declaration-block-no-ignored-properties': true, + 'property-allowed-list': null, + 'property-disallowed-list': null, + 'property-no-unknown': true, + 'property-no-vendor-prefix': null, + 'rule-empty-line-before': null, + 'rule-selector-property-disallowed-list': null, + 'scale-unlimited/declaration-strict-value': [['/color$/', 'font-weight'], {ignoreValues: '/^(inherit|transparent|unset|initial|currentcolor|none)$/', ignoreFunctions: false, disableFix: true, expandShorthand: true}], + 'selector-anb-no-unmatchable': true, + 'selector-attribute-name-disallowed-list': null, + 'selector-attribute-operator-allowed-list': null, + 'selector-attribute-operator-disallowed-list': null, + 'selector-attribute-quotes': 'always', + 'selector-class-pattern': null, + 'selector-combinator-allowed-list': null, + 'selector-combinator-disallowed-list': null, + 'selector-disallowed-list': null, + 'selector-id-pattern': null, + 'selector-max-attribute': null, + 'selector-max-class': null, + 'selector-max-combinators': null, + 'selector-max-compound-selectors': null, + 'selector-max-id': null, + 'selector-max-pseudo-class': null, + 'selector-max-specificity': null, + 'selector-max-type': null, + 'selector-max-universal': null, + 'selector-nested-pattern': null, + 'selector-no-qualifying-type': null, + 'selector-no-vendor-prefix': true, + 'selector-not-notation': null, + 'selector-pseudo-class-allowed-list': null, + 'selector-pseudo-class-disallowed-list': null, + 'selector-pseudo-class-no-unknown': true, + 'selector-pseudo-element-allowed-list': null, + 'selector-pseudo-element-colon-notation': 'double', + 'selector-pseudo-element-disallowed-list': null, + 'selector-pseudo-element-no-unknown': true, + 'selector-type-case': 'lower', + 'selector-type-no-unknown': [true, {ignore: ['custom-elements']}], + 'shorthand-property-no-redundant-values': true, + 'string-no-newline': true, + 'time-min-milliseconds': null, + 'unit-allowed-list': null, + 'unit-disallowed-list': null, + 'unit-no-unknown': true, + 'value-keyword-case': null, + 'value-no-vendor-prefix': [true, {ignoreValues: ['box', 'inline-box']}], + }, +}; diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index 687a277a84..8a8bd61148 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -97,6 +97,10 @@ +
    + + +
    diff --git a/templates/admin/auth/source/ldap.tmpl b/templates/admin/auth/source/ldap.tmpl index 680849c8ea..6cb6643f26 100644 --- a/templates/admin/auth/source/ldap.tmpl +++ b/templates/admin/auth/source/ldap.tmpl @@ -71,6 +71,10 @@
    +
    + + +
    diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl index bfd2ee6670..9b89b8335f 100644 --- a/templates/admin/dashboard.tmpl +++ b/templates/admin/dashboard.tmpl @@ -76,7 +76,8 @@ {{ctx.Locale.Tr "admin.dashboard.system_status"}} {{/* TODO: make these stats work in multi-server deployments, likely needs per-server stats in DB */}} -
    +
    +
    {{template "admin/system_status" .}}
    diff --git a/templates/admin/layout_head.tmpl b/templates/admin/layout_head.tmpl index 8ba47f2f14..7cc6624d50 100644 --- a/templates/admin/layout_head.tmpl +++ b/templates/admin/layout_head.tmpl @@ -1,6 +1,6 @@ {{template "base/head" .ctxData}}
    -
    +
    {{template "admin/navbar" .ctxData}}
    {{template "base/alert" .ctxData}} diff --git a/templates/admin/navbar.tmpl b/templates/admin/navbar.tmpl index 16ec1b4b5b..1ec703b296 100644 --- a/templates/admin/navbar.tmpl +++ b/templates/admin/navbar.tmpl @@ -4,7 +4,7 @@ {{ctx.Locale.Tr "admin.dashboard"}} - {{if or .DatabaseType.IsMySQL .DatabaseType.IsMSSQL}} + {{if .DatabaseType.IsMySQL}} {{ctx.Locale.Tr "admin.self_check"}} diff --git a/templates/admin/self_check.tmpl b/templates/admin/self_check.tmpl index 94c4673a49..5c154ac0d5 100644 --- a/templates/admin/self_check.tmpl +++ b/templates/admin/self_check.tmpl @@ -8,8 +8,6 @@ {{if .DatabaseCheckHasProblems}} {{if .DatabaseType.IsMySQL}}
    {{ctx.Locale.Tr "admin.self_check.database_fix_mysql"}}
    - {{else if .DatabaseType.IsMSSQL}} -
    {{ctx.Locale.Tr "admin.self_check.database_fix_mssql"}}
    {{end}} {{if .DatabaseCheckCollationMismatch}}
    {{ctx.Locale.Tr "admin.self_check.database_collation_mismatch" .DatabaseCheckResult.ExpectedCollation}}
    diff --git a/templates/base/footer_content.tmpl b/templates/base/footer_content.tmpl index 75b7845310..5db7464480 100644 --- a/templates/base/footer_content.tmpl +++ b/templates/base/footer_content.tmpl @@ -1,6 +1,8 @@
    {{else}}
    - {{ctx.Locale.Tr "org.teams.repos.none"}} + {{ctx.Locale.Tr "org.teams.repos.none"}}
    {{end}}
    diff --git a/templates/org/team/sidebar.tmpl b/templates/org/team/sidebar.tmpl index 9311a46e38..c9f80259e2 100644 --- a/templates/org/team/sidebar.tmpl +++ b/templates/org/team/sidebar.tmpl @@ -22,7 +22,7 @@ {{if .Team.Description}} {{.Team.Description}} {{else}} - {{ctx.Locale.Tr "org.teams.no_desc"}} + {{ctx.Locale.Tr "org.teams.no_desc"}} {{end}}
    {{if eq .Team.LowerName "owners"}} diff --git a/templates/package/shared/cleanup_rules/list.tmpl b/templates/package/shared/cleanup_rules/list.tmpl index 6505c8f9a5..ba1683b932 100644 --- a/templates/package/shared/cleanup_rules/list.tmpl +++ b/templates/package/shared/cleanup_rules/list.tmpl @@ -16,26 +16,26 @@ {{.Type.Name}}
    - {{if .Enabled}}{{ctx.Locale.Tr "enabled"}}{{else}}{{ctx.Locale.Tr "disabled"}}{{end}} +

    {{if .Enabled}}{{ctx.Locale.Tr "enabled"}}{{else}}{{ctx.Locale.Tr "disabled"}}{{end}}

    {{if .KeepCount}}
    - {{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.count"}}: {{if eq .KeepCount 1}}{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.count.1"}}{{else}}{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.count.n" .KeepCount}}{{end}} +

    {{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.count"}}:

    {{if eq .KeepCount 1}}{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.count.1"}}{{else}}{{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.count.n" .KeepCount}}{{end}}
    {{end}} {{if .KeepPattern}}
    - {{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.pattern"}}: {{StringUtils.EllipsisString .KeepPattern 100}} +

    {{ctx.Locale.Tr "packages.owner.settings.cleanuprules.keep.pattern"}}:

    {{StringUtils.EllipsisString .KeepPattern 100}}
    {{end}} {{if .RemoveDays}}
    - {{ctx.Locale.Tr "packages.owner.settings.cleanuprules.remove.days"}}: {{ctx.Locale.Tr "tool.days" .RemoveDays}} +

    {{ctx.Locale.Tr "packages.owner.settings.cleanuprules.remove.days"}}:

    {{ctx.Locale.Tr "tool.days" .RemoveDays}}
    {{end}} {{if .RemovePattern}}
    - {{ctx.Locale.Tr "packages.owner.settings.cleanuprules.remove.pattern"}}: {{StringUtils.EllipsisString .RemovePattern 100}} +

    {{ctx.Locale.Tr "packages.owner.settings.cleanuprules.remove.pattern"}}:

    {{StringUtils.EllipsisString .RemovePattern 100}}
    {{end}}
    diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl index 0908f5648c..b892cff996 100644 --- a/templates/projects/list.tmpl +++ b/templates/projects/list.tmpl @@ -41,9 +41,9 @@
    {{range .Projects}}
  • -

    +

    {{svg .IconName 16}} - {{.Title}} + {{.Title}}

    diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index b45174b086..3e000660e2 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -1,8 +1,8 @@ {{$canWriteProject := and .CanWriteProjects (or (not .Repository) (not .Repository.IsArchived))}} -
    -
    -

    {{.Project.Title}}

    +
    +
    +

    {{.Project.Title}}

    {{if $canWriteProject}}
  • diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl index 81104a70d2..e1881b1d94 100644 --- a/templates/repo/release/new.tmpl +++ b/templates/repo/release/new.tmpl @@ -98,6 +98,15 @@
    {{ctx.Locale.Tr "repo.release.prerelease_helper"}} + {{if not .DisableDownloadSourceArchives}} +
    +
    + + +
    +
    + {{ctx.Locale.Tr "repo.release.hide_archive_links_helper"}} + {{end}}
    {{if .PageIsEditRelease}} diff --git a/templates/repo/settings/collaboration.tmpl b/templates/repo/settings/collaboration.tmpl index 2a4ec577e7..ed4d5e7eb3 100644 --- a/templates/repo/settings/collaboration.tmpl +++ b/templates/repo/settings/collaboration.tmpl @@ -29,7 +29,7 @@
    - @@ -75,7 +75,7 @@ {{if $allowedToChangeTeams}}
    -
    diff --git a/templates/repo/settings/deploy_keys.tmpl b/templates/repo/settings/deploy_keys.tmpl index da1a321785..3410c103a2 100644 --- a/templates/repo/settings/deploy_keys.tmpl +++ b/templates/repo/settings/deploy_keys.tmpl @@ -55,7 +55,7 @@ {{.Fingerprint}}
    - {{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}} — {{svg "octicon-info"}} {{if .HasUsed}}{{ctx.Locale.Tr "settings.last_used"}} {{DateTime "short" .UpdatedUnix}}{{else}}{{ctx.Locale.Tr "settings.no_activity"}}{{end}} - {{ctx.Locale.Tr "settings.can_read_info"}}{{if not .IsReadOnly}} / {{ctx.Locale.Tr "settings.can_write_info"}} {{end}} +

    {{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix)}} — {{svg "octicon-info"}} {{if .HasUsed}}{{ctx.Locale.Tr "settings.last_used"}} {{DateTime "short" .UpdatedUnix}}{{else}}{{ctx.Locale.Tr "settings.no_activity"}}{{end}} - {{ctx.Locale.Tr "settings.can_read_info"}}{{if not .IsReadOnly}} / {{ctx.Locale.Tr "settings.can_write_info"}} {{end}}

    diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 31d5ae77f2..979df1f5cf 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -135,7 +135,7 @@
    {{.CsrfTokenHtml}} - +
    diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl index fb257bd474..7ec9acc84e 100644 --- a/templates/repo/view_list.tmpl +++ b/templates/repo/view_list.tmpl @@ -68,6 +68,6 @@ {{end}} -{{if .ReadmeExist}} +{{if and .ReadmeExist (or .IsMarkup .IsPlainText)}} {{template "repo/view_file" .}} {{end}} diff --git a/templates/shared/user/org_profile_avatar.tmpl b/templates/shared/user/org_profile_avatar.tmpl index 2ff1e40ca8..d67f133abf 100644 --- a/templates/shared/user/org_profile_avatar.tmpl +++ b/templates/shared/user/org_profile_avatar.tmpl @@ -4,7 +4,7 @@
    {{ctx.AvatarUtils.Avatar . 100}} - {{.DisplayName}} + {{.DisplayName}} {{if .Visibility.IsLimited}}
    {{ctx.Locale.Tr "org.settings.visibility.limited_shortname"}}
    {{end}} {{if .Visibility.IsPrivate}}
    {{ctx.Locale.Tr "org.settings.visibility.private_shortname"}}
    {{end}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index bcf370b3fb..dbf9eb89e2 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -1741,6 +1741,232 @@ } } }, + "/orgs/{org}/actions/variables": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "organization" + ], + "summary": "Get an org-level variables list", + "operationId": "getOrgVariablesList", + "parameters": [ + { + "type": "string", + "description": "name of the organization", + "name": "org", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/VariableList" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/orgs/{org}/actions/variables/{variablename}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "organization" + ], + "summary": "Get an org-level variable", + "operationId": "getOrgVariable", + "parameters": [ + { + "type": "string", + "description": "name of the organization", + "name": "org", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the variable", + "name": "variablename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/ActionVariable" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "put": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "organization" + ], + "summary": "Update an org-level variable", + "operationId": "updateOrgVariable", + "parameters": [ + { + "type": "string", + "description": "name of the organization", + "name": "org", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the variable", + "name": "variablename", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/UpdateVariableOption" + } + } + ], + "responses": { + "201": { + "description": "response when updating an org-level variable" + }, + "204": { + "description": "response when updating an org-level variable" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "organization" + ], + "summary": "Create an org-level variable", + "operationId": "createOrgVariable", + "parameters": [ + { + "type": "string", + "description": "name of the organization", + "name": "org", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the variable", + "name": "variablename", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/CreateVariableOption" + } + } + ], + "responses": { + "201": { + "description": "response when creating an org-level variable" + }, + "204": { + "description": "response when creating an org-level variable" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "organization" + ], + "summary": "Delete an org-level variable", + "operationId": "deleteOrgVariable", + "parameters": [ + { + "type": "string", + "description": "name of the organization", + "name": "org", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the variable", + "name": "variablename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/ActionVariable" + }, + "201": { + "description": "response when deleting a variable" + }, + "204": { + "description": "response when deleting a variable" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/orgs/{org}/activities/feeds": { "get": { "produces": [ @@ -3485,6 +3711,54 @@ } } }, + "/repos/{owner}/{repo}/actions/secrets": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "List an repo's actions secrets", + "operationId": "repoListActionsSecrets", + "parameters": [ + { + "type": "string", + "description": "owner of the repository", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repository", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/SecretList" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/repos/{owner}/{repo}/actions/secrets/{secretname}": { "put": { "consumes": [ @@ -3591,6 +3865,261 @@ } } }, + "/repos/{owner}/{repo}/actions/variables": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Get repo-level variables list", + "operationId": "getRepoVariablesList", + "parameters": [ + { + "type": "string", + "description": "name of the owner", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repository", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/VariableList" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/repos/{owner}/{repo}/actions/variables/{variablename}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Get a repo-level variable", + "operationId": "getRepoVariable", + "parameters": [ + { + "type": "string", + "description": "name of the owner", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repository", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the variable", + "name": "variablename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/ActionVariable" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "put": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Update a repo-level variable", + "operationId": "updateRepoVariable", + "parameters": [ + { + "type": "string", + "description": "name of the owner", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repository", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the variable", + "name": "variablename", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/UpdateVariableOption" + } + } + ], + "responses": { + "201": { + "description": "response when updating a repo-level variable" + }, + "204": { + "description": "response when updating a repo-level variable" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Create a repo-level variable", + "operationId": "createRepoVariable", + "parameters": [ + { + "type": "string", + "description": "name of the owner", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repository", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the variable", + "name": "variablename", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/CreateVariableOption" + } + } + ], + "responses": { + "201": { + "description": "response when creating a repo-level variable" + }, + "204": { + "description": "response when creating a repo-level variable" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Delete a repo-level variable", + "operationId": "deleteRepoVariable", + "parameters": [ + { + "type": "string", + "description": "name of the owner", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repository", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the variable", + "name": "variablename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/ActionVariable" + }, + "201": { + "description": "response when deleting a variable" + }, + "204": { + "description": "response when deleting a variable" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/repos/{owner}/{repo}/activities/feeds": { "get": { "produces": [ @@ -4729,6 +5258,49 @@ } } }, + "/repos/{owner}/{repo}/compare/{basehead}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Get commit comparison information", + "operationId": "repoCompareDiff", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "compare two branches or commits", + "name": "basehead", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/Compare" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/repos/{owner}/{repo}/contents": { "get": { "produces": [ @@ -15375,6 +15947,194 @@ } } }, + "/user/actions/variables": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get the user-level list of variables which is created by current doer", + "operationId": "getUserVariablesList", + "parameters": [ + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/VariableList" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/user/actions/variables/{variablename}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Get a user-level variable which is created by current doer", + "operationId": "getUserVariable", + "parameters": [ + { + "type": "string", + "description": "name of the variable", + "name": "variablename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/ActionVariable" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "put": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Update a user-level variable which is created by current doer", + "operationId": "updateUserVariable", + "parameters": [ + { + "type": "string", + "description": "name of the variable", + "name": "variablename", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/UpdateVariableOption" + } + } + ], + "responses": { + "201": { + "description": "response when updating a variable" + }, + "204": { + "description": "response when updating a variable" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Create a user-level variable", + "operationId": "createUserVariable", + "parameters": [ + { + "type": "string", + "description": "name of the variable", + "name": "variablename", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/CreateVariableOption" + } + } + ], + "responses": { + "201": { + "description": "response when creating a variable" + }, + "204": { + "description": "response when creating a variable" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "Delete a user-level variable which is created by current doer", + "operationId": "deleteUserVariable", + "parameters": [ + { + "type": "string", + "description": "name of the variable", + "name": "variablename", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "description": "response when deleting a variable" + }, + "204": { + "description": "response when deleting a variable" + }, + "400": { + "$ref": "#/responses/error" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/user/applications/oauth2": { "get": { "produces": [ @@ -17493,6 +18253,35 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "ActionVariable": { + "description": "ActionVariable return value of the query API", + "type": "object", + "properties": { + "data": { + "description": "the value of the variable", + "type": "string", + "x-go-name": "Data" + }, + "name": { + "description": "the name of the variable", + "type": "string", + "x-go-name": "Name" + }, + "owner_id": { + "description": "the owner to which the variable belongs", + "type": "integer", + "format": "int64", + "x-go-name": "OwnerID" + }, + "repo_id": { + "description": "the repository to which the variable belongs", + "type": "integer", + "format": "int64", + "x-go-name": "RepoID" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "Activity": { "type": "object", "properties": { @@ -17606,6 +18395,9 @@ "description": "AnnotatedTag represents an annotated tag", "type": "object", "properties": { + "archive_download_count": { + "$ref": "#/definitions/TagArchiveDownloadCount" + }, "message": { "type": "string", "x-go-name": "Message" @@ -18316,6 +19108,25 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "Compare": { + "type": "object", + "title": "Compare represents a comparison between two commits.", + "properties": { + "commits": { + "type": "array", + "items": { + "$ref": "#/definitions/Commit" + }, + "x-go-name": "Commits" + }, + "total_commits": { + "type": "integer", + "format": "int64", + "x-go-name": "TotalCommits" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "ContentsResponse": { "description": "ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content", "type": "object", @@ -19125,6 +19936,10 @@ "type": "boolean", "x-go-name": "IsDraft" }, + "hide_archive_links": { + "type": "boolean", + "x-go-name": "HideArchiveLinks" + }, "name": { "type": "string", "x-go-name": "Title" @@ -19390,6 +20205,21 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "CreateVariableOption": { + "description": "CreateVariableOption the option when creating variable", + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "description": "Value of the variable to create", + "type": "string", + "x-go-name": "Value" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "CreateWikiPageOptions": { "description": "CreateWikiPageOptions form for creating wiki", "type": "object", @@ -20015,6 +20845,10 @@ "type": "boolean", "x-go-name": "IsDraft" }, + "hide_archive_links": { + "type": "boolean", + "x-go-name": "HideArchiveLinks" + }, "name": { "type": "string", "x-go-name": "Title" @@ -20119,6 +20953,11 @@ "external_wiki": { "$ref": "#/definitions/ExternalWiki" }, + "globally_editable_wiki": { + "description": "set the globally editable state of the wiki", + "type": "boolean", + "x-go-name": "GloballyEditableWiki" + }, "has_actions": { "description": "either `true` to enable actions unit, or `false` to disable them.", "type": "boolean", @@ -20268,10 +21107,6 @@ "EditUserOption": { "description": "EditUserOption edit user options", "type": "object", - "required": [ - "source_id", - "login_name" - ], "properties": { "active": { "type": "boolean", @@ -22755,6 +23590,9 @@ "description": "Release represents a repository release", "type": "object", "properties": { + "archive_download_count": { + "$ref": "#/definitions/TagArchiveDownloadCount" + }, "assets": { "type": "array", "items": { @@ -22778,6 +23616,10 @@ "type": "boolean", "x-go-name": "IsDraft" }, + "hide_archive_links": { + "type": "boolean", + "x-go-name": "HideArchiveLinks" + }, "html_url": { "type": "string", "x-go-name": "HTMLURL" @@ -23030,6 +23872,10 @@ "type": "string", "x-go-name": "FullName" }, + "globally_editable_wiki": { + "type": "boolean", + "x-go-name": "GloballyEditableWiki" + }, "has_actions": { "type": "boolean", "x-go-name": "HasActions" @@ -23330,6 +24176,9 @@ "description": "Tag represents a repository tag", "type": "object", "properties": { + "archive_download_count": { + "$ref": "#/definitions/TagArchiveDownloadCount" + }, "commit": { "$ref": "#/definitions/CommitMeta" }, @@ -23356,6 +24205,23 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "TagArchiveDownloadCount": { + "description": "TagArchiveDownloadCount counts how many times a archive was downloaded", + "type": "object", + "properties": { + "tar_gz": { + "type": "integer", + "format": "int64", + "x-go-name": "TarGz" + }, + "zip": { + "type": "integer", + "format": "int64", + "x-go-name": "Zip" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "Team": { "description": "Team represents a team in an organization", "type": "object", @@ -23747,6 +24613,26 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "UpdateVariableOption": { + "description": "UpdateVariableOption the option when updating variable", + "type": "object", + "required": [ + "value" + ], + "properties": { + "name": { + "description": "New name for the variable. If the field is empty, the variable name won't be updated.", + "type": "string", + "x-go-name": "Name" + }, + "value": { + "description": "Value of the variable to update", + "type": "string", + "x-go-name": "Value" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "User": { "description": "User represents a user", "type": "object", @@ -23844,6 +24730,12 @@ "type": "boolean", "x-go-name": "Restricted" }, + "source_id": { + "description": "The ID of the user's Authentication Source", + "type": "integer", + "format": "int64", + "x-go-name": "SourceID" + }, "starred_repos_count": { "type": "integer", "format": "int64", @@ -24131,6 +25023,12 @@ } } }, + "ActionVariable": { + "description": "ActionVariable", + "schema": { + "$ref": "#/definitions/ActionVariable" + } + }, "ActivityFeedsList": { "description": "ActivityFeedsList", "schema": { @@ -24318,6 +25216,12 @@ } } }, + "Compare": { + "description": "", + "schema": { + "$ref": "#/definitions/Compare" + } + }, "ContentsListResponse": { "description": "ContentsListResponse", "schema": { @@ -25014,6 +25918,15 @@ } } }, + "VariableList": { + "description": "VariableList", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/ActionVariable" + } + } + }, "WatchInfo": { "description": "WatchInfo", "schema": { @@ -25089,7 +26002,7 @@ "parameterBodies": { "description": "parameterBodies", "schema": { - "$ref": "#/definitions/CreateOrUpdateSecretOption" + "$ref": "#/definitions/UpdateVariableOption" } }, "redirect": { diff --git a/templates/user/dashboard/dashboard.tmpl b/templates/user/dashboard/dashboard.tmpl index d4553ea61b..5dc46dc0a5 100644 --- a/templates/user/dashboard/dashboard.tmpl +++ b/templates/user/dashboard/dashboard.tmpl @@ -1,15 +1,13 @@ {{template "base/head" .}}
    {{template "user/dashboard/navbar" .}} -
    - {{template "base/alert" .}} -
    -
    - {{template "user/heatmap" .}} - {{template "user/dashboard/feeds" .}} -
    - {{template "user/dashboard/repolist" .}} +
    +
    + {{template "base/alert" .}} + {{template "user/heatmap" .}} + {{template "user/dashboard/feeds" .}}
    + {{template "user/dashboard/repolist" .}}
    {{template "base/footer" .}} diff --git a/templates/user/dashboard/repolist.tmpl b/templates/user/dashboard/repolist.tmpl index 34f9b67f8e..2781f710ed 100644 --- a/templates/user/dashboard/repolist.tmpl +++ b/templates/user/dashboard/repolist.tmpl @@ -52,4 +52,4 @@ data.organizationId = {{.ContextUser.ID}}; window.config.pageData.dashboardRepoList = data; -
    +
    diff --git a/templates/user/notification/notification_div.tmpl b/templates/user/notification/notification_div.tmpl index bf3b51ee3b..99da119901 100644 --- a/templates/user/notification/notification_div.tmpl +++ b/templates/user/notification/notification_div.tmpl @@ -1,8 +1,8 @@
    {{$notificationUnreadCount := call .NotificationUnreadCount}} -
    - - {{template "repo/settings/webhook/settings" .}} + {{template "webhook/shared-settings" .}} diff --git a/templates/repo/settings/webhook/settings.tmpl b/templates/webhook/shared-settings.tmpl similarity index 94% rename from templates/repo/settings/webhook/settings.tmpl rename to templates/webhook/shared-settings.tmpl index 0a39643260..80caad7279 100644 --- a/templates/repo/settings/webhook/settings.tmpl +++ b/templates/webhook/shared-settings.tmpl @@ -258,14 +258,15 @@ {{ctx.Locale.Tr "repo.settings.branch_filter_desc"}}
    - -
    - - - {{if ne .HookType "matrix"}}{{/* Matrix doesn't make the authorization optional but it is implied by the help string, should be changed.*/}} +{{$skipAuthorizationHeader := or (eq .HookType "sourcehut_builds") (eq .HookType "matrix")}} +{{if not $skipAuthorizationHeader}} + +
    + + {{ctx.Locale.Tr "repo.settings.authorization_header_desc" ("Bearer token123456, Basic YWxhZGRpbjpvcGVuc2VzYW1l" | SafeHTML)}} - {{end}} -
    +
    +{{end}}
    diff --git a/tests/e2e/README.md b/tests/e2e/README.md index e5fd1ca6c0..a0a9fc9ec9 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -7,7 +7,6 @@ They can be run with make commands for the appropriate backends, namely: make test-sqlite make test-pgsql make test-mysql -make test-mssql ``` Make sure to perform a clean front-end build before running tests: @@ -53,16 +52,6 @@ Start tests based on the database container TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=test TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-e2e-pgsql ``` -## Run mssql e2e tests -Setup a mssql database inside docker -``` -docker run -e "ACCEPT_EULA=Y" -e "MSSQL_PID=Standard" -e "SA_PASSWORD=MwantsaSecurePassword1" -p 1433:1433 --rm --name mssql microsoft/mssql-server-linux:latest #(just ctrl-c to stop db and clean the container) -``` -Start tests based on the database container -``` -TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=gitea_test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-e2e-mssql -``` - ## Running individual tests Example command to run `example.test.e2e.js` test file: @@ -75,10 +64,10 @@ For SQLite: make test-e2e-sqlite#example ``` -For other databases(replace `mssql` to `mysql` or `pgsql`): +For PostgreSQL databases(replace `mysql` to `pgsql`): ``` -TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-e2e-mssql#example +TEST_MYSQL_HOST=localhost:1433 TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=sa TEST_MYSQL_PASSWORD=MwantsaSecurePassword1 make test-e2e-mysql#example ``` ## Visual testing diff --git a/tests/e2e/debugserver_test.go b/tests/e2e/debugserver_test.go index f0f54665e1..b1496b22a1 100644 --- a/tests/e2e/debugserver_test.go +++ b/tests/e2e/debugserver_test.go @@ -10,6 +10,7 @@ package e2e import ( + "fmt" "net/url" "os" "os/signal" @@ -24,7 +25,7 @@ func TestDebugserver(t *testing.T) { signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) onGiteaRun(t, func(*testing.T, *url.URL) { - println(setting.AppURL) + fmt.Println(setting.AppURL) <-done }) } diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 92c90f52e6..07f0bf52a3 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -91,6 +91,9 @@ func TestE2e(t *testing.T) { if _, set := os.LookupEnv("ACCEPT_VISUAL"); set { runArgs = append(runArgs, "--update-snapshots") } + if project := os.Getenv("PLAYWRIGHT_PROJECT"); project != "" { + runArgs = append(runArgs, "--project="+project) + } // Create new test for each input file for _, path := range paths { @@ -103,18 +106,20 @@ func TestE2e(t *testing.T) { cmd := exec.Command(runArgs[0], runArgs...) cmd.Env = os.Environ() cmd.Env = append(cmd.Env, fmt.Sprintf("GITEA_URL=%s", setting.AppURL)) + var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr + err := cmd.Run() if err != nil { // Currently colored output is conflicting. Using Printf until that is resolved. fmt.Printf("%v", stdout.String()) fmt.Printf("%v", stderr.String()) log.Fatal("Playwright Failed: %s", err) - } else { - fmt.Printf("%v", stdout.String()) } + + fmt.Printf("%v", stdout.String()) }) }) } diff --git a/tests/integration/README.md b/tests/integration/README.md index f6f74ca21f..d659d51b20 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -6,7 +6,6 @@ appropriate backends, namely: make test-sqlite make test-pgsql make test-mysql -make test-mssql ``` Make sure to perform a clean build before running tests: @@ -63,16 +62,6 @@ Start tests based on the database container TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=test TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-pgsql ``` -## Run mssql integration tests -Setup a mssql database inside docker -``` -docker run -e "ACCEPT_EULA=Y" -e "MSSQL_PID=Standard" -e "SA_PASSWORD=MwantsaSecurePassword1" -p 1433:1433 --rm --name mssql microsoft/mssql-server-linux:latest #(just ctrl-c to stop db and clean the container) -``` -Start tests based on the database container -``` -TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=gitea_test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-mssql -``` - ## Running individual tests Example command to run GPG test: @@ -83,10 +72,10 @@ For SQLite: make test-sqlite#GPG ``` -For other databases(replace `mssql` to `mysql`, or `pgsql`): +For other databases (replace `mysql` to `pgsql`): ``` -TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-mssql#GPG +TEST_MYSQL_HOST=localhost:1433 TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=sa TEST_MYSQL_PASSWORD=MwantsaSecurePassword1 make test-mysql#GPG ``` ## Setting timeouts for declaring long-tests and long-flushes diff --git a/tests/integration/README_ZH.md b/tests/integration/README_ZH.md index 6aea4ab212..d0debf2fd6 100644 --- a/tests/integration/README_ZH.md +++ b/tests/integration/README_ZH.md @@ -59,16 +59,6 @@ docker run -e "POSTGRES_DB=test" -p 5432:5432 --rm --name pgsql postgres:14 #(ju TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=test TEST_PGSQL_USERNAME=postgres TEST_PGSQL_PASSWORD=postgres make test-pgsql ``` -## Run mssql integration tests -同上,首先在 docker 容器里部署一个 mssql 数据库 -``` -docker run -e "ACCEPT_EULA=Y" -e "MSSQL_PID=Standard" -e "SA_PASSWORD=MwantsaSecurePassword1" -p 1433:1433 --rm --name mssql microsoft/mssql-server-linux:latest #(just ctrl-c to stop db and clean the container) -``` -之后便可以基于这个数据库进行集成测试 -``` -TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=gitea_test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-mssql -``` - ## 如何进行自定义的集成测试 下面的示例展示了怎样在集成测试中只进行 GPG 测试: @@ -79,9 +69,9 @@ sqlite 数据库: make test-sqlite#GPG ``` -其它数据库(把 MSSQL 替换为 MYSQL, PGSQL): +其它数据库 (用 PGSQL 取代 MYSQL): ``` -TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=test TEST_MSSQL_USERNAME=sa TEST_MSSQL_PASSWORD=MwantsaSecurePassword1 make test-mssql#GPG +TEST_MYSQL_HOST=localhost:1433 TEST_MYSQL_DBNAME=test TEST_MYSQL_USERNAME=sa TEST_MYSQL_PASSWORD=MwantsaSecurePassword1 make test-mysql#GPG ``` diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index e8954f5b20..0e97e606f9 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -196,19 +196,13 @@ func TestAPIEditUser(t *testing.T) { urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2") req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ - // required - "login_name": "user2", - "source_id": "0", - // to change "full_name": "Full Name User 2", }).AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) empty := "" req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ - LoginName: "user2", - SourceID: 0, - Email: &empty, + Email: &empty, }).AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusBadRequest) @@ -220,10 +214,6 @@ func TestAPIEditUser(t *testing.T) { assert.False(t, user2.IsRestricted) bTrue := true req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ - // required - LoginName: "user2", - SourceID: 0, - // to change Restricted: &bTrue, }).AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) @@ -231,6 +221,45 @@ func TestAPIEditUser(t *testing.T) { assert.True(t, user2.IsRestricted) } +func TestAPIEditUserWithLoginName(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + adminUsername := "user1" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) + urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2") + + loginName := "user2" + loginSource := int64(0) + + t.Run("login_name only", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ + LoginName: &loginName, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnprocessableEntity) + }) + + t.Run("source_id only", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ + SourceID: &loginSource, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnprocessableEntity) + }) + + t.Run("login_name & source_id", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ + LoginName: &loginName, + SourceID: &loginSource, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + }) +} + func TestAPICreateRepoForUser(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" @@ -375,18 +404,14 @@ func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) { newEmail := "user2@example1.com" req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ - LoginName: "user2", - SourceID: 0, - Email: &newEmail, + Email: &newEmail, }).AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "the domain of user email user2@example1.com conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", resp.Header().Get("X-Gitea-Warning")) originalEmail := "user2@example.com" req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ - LoginName: "user2", - SourceID: 0, - Email: &originalEmail, + Email: &originalEmail, }).AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) } diff --git a/tests/integration/api_notification_test.go b/tests/integration/api_notification_test.go index 528890ca22..abb9852eef 100644 --- a/tests/integration/api_notification_test.go +++ b/tests/integration/api_notification_test.go @@ -111,7 +111,7 @@ func TestAPINotification(t *testing.T) { MakeRequest(t, NewRequest(t, "GET", "/api/v1/notifications/new"), http.StatusUnauthorized) - new := struct { + newStruct := struct { New int64 `json:"new"` }{} @@ -119,8 +119,8 @@ func TestAPINotification(t *testing.T) { req = NewRequest(t, "GET", "/api/v1/notifications/new"). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &new) - assert.True(t, new.New > 0) + DecodeJSON(t, resp, &newStruct) + assert.True(t, newStruct.New > 0) // -- mark notifications as read -- req = NewRequest(t, "GET", "/api/v1/notifications?status-types=unread"). @@ -153,8 +153,8 @@ func TestAPINotification(t *testing.T) { req = NewRequest(t, "GET", "/api/v1/notifications/new"). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &new) - assert.True(t, new.New == 0) + DecodeJSON(t, resp, &newStruct) + assert.True(t, newStruct.New == 0) } func TestAPINotificationPUT(t *testing.T) { diff --git a/tests/integration/api_packages_alpine_test.go b/tests/integration/api_packages_alpine_test.go index ea9735236b..f70d3c23af 100644 --- a/tests/integration/api_packages_alpine_test.go +++ b/tests/integration/api_packages_alpine_test.go @@ -480,7 +480,6 @@ aiIK5QoSDwAAAAAAAAAAAAAAAP/IK49O1e8AKAAA` req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/x86_64/%s-%s.apk", rootURL, branch, repository, pkg, packageVersion)). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNoContent) - } // Deleting the last file of an architecture should remove that index req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/x86_64/APKINDEX.tar.gz", rootURL, branch, repository)) diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index 49aa4c4e1b..0a392d0a15 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -133,14 +133,18 @@ func TestAPICreateAndUpdateRelease(t *testing.T) { assert.Equal(t, newRelease.TagName, release.TagName) assert.Equal(t, newRelease.Title, release.Title) assert.Equal(t, newRelease.Note, release.Note) + assert.False(t, newRelease.HideArchiveLinks) + + hideArchiveLinks := true req = NewRequestWithJSON(t, "PATCH", urlStr, &api.EditReleaseOption{ - TagName: release.TagName, - Title: release.Title, - Note: "updated", - IsDraft: &release.IsDraft, - IsPrerelease: &release.IsPrerelease, - Target: release.Target, + TagName: release.TagName, + Title: release.Title, + Note: "updated", + IsDraft: &release.IsDraft, + IsPrerelease: &release.IsPrerelease, + Target: release.Target, + HideArchiveLinks: &hideArchiveLinks, }).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) @@ -152,6 +156,7 @@ func TestAPICreateAndUpdateRelease(t *testing.T) { } unittest.AssertExistsAndLoadBean(t, rel) assert.EqualValues(t, rel.Note, newRelease.Note) + assert.True(t, newRelease.HideArchiveLinks) } func TestAPICreateReleaseToDefaultBranch(t *testing.T) { @@ -319,3 +324,39 @@ func TestAPIUploadAssetRelease(t *testing.T) { assert.EqualValues(t, 104, attachment.Size) }) } + +func TestAPIGetReleaseArchiveDownloadCount(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, owner.LowerName) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + name := "ReleaseDownloadCount" + + createNewReleaseUsingAPI(t, session, token, owner, repo, name, "", name, "test") + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, name) + + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + var release *api.Release + DecodeJSON(t, resp, &release) + + // Check if everything defaults to 0 + assert.Equal(t, int64(0), release.ArchiveDownloadCount.TarGz) + assert.Equal(t, int64(0), release.ArchiveDownloadCount.Zip) + + // Download the tarball to increase the count + MakeRequest(t, NewRequest(t, "GET", release.TarURL), http.StatusOK) + + // Check if the count has increased + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &release) + + assert.Equal(t, int64(1), release.ArchiveDownloadCount.TarGz) + assert.Equal(t, int64(0), release.ArchiveDownloadCount.Zip) +} diff --git a/tests/integration/api_repo_archive_test.go b/tests/integration/api_repo_archive_test.go index c574d49450..a4ae599615 100644 --- a/tests/integration/api_repo_archive_test.go +++ b/tests/integration/api_repo_archive_test.go @@ -8,6 +8,7 @@ import ( "io" "net/http" "net/url" + "regexp" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -41,6 +42,16 @@ func TestAPIDownloadArchive(t *testing.T) { assert.Len(t, bs, 266) assert.EqualValues(t, "application/gzip", resp.Header().Get("Content-Type")) + // Must return a link to a commit ID as the "immutable" archive link + linkHeaderRe := regexp.MustCompile(`<(?Phttps?://.*/api/v1/repos/user2/repo1/archive/[a-f0-9]+\.tar\.gz.*)>; rel="immutable"`) + m := linkHeaderRe.FindStringSubmatch(resp.Header().Get("Link")) + assert.NotEmpty(t, m[1]) + resp = MakeRequest(t, NewRequest(t, "GET", m[1]).AddTokenAuth(token), http.StatusOK) + bs2, err := io.ReadAll(resp.Body) + assert.NoError(t, err) + // The locked URL should give the same bytes as the non-locked one + assert.EqualValues(t, bs, bs2) + link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master.bundle", user2.Name, repo.Name)) resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) bs, err = io.ReadAll(resp.Body) diff --git a/tests/integration/api_repo_compare_test.go b/tests/integration/api_repo_compare_test.go new file mode 100644 index 0000000000..f3188eb49f --- /dev/null +++ b/tests/integration/api_repo_compare_test.go @@ -0,0 +1,38 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "net/http" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPICompareBranches(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + repoName := "repo20" + + req := NewRequestf(t, "GET", "/api/v1/repos/user2/%s/compare/add-csv...remove-files-b", repoName). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiResp *api.Compare + DecodeJSON(t, resp, &apiResp) + + assert.Equal(t, 2, apiResp.TotalCommits) + assert.Len(t, apiResp.Commits, 2) +} diff --git a/tests/integration/api_repo_secrets_test.go b/tests/integration/api_repo_secrets_test.go index feb9bae2b2..c3074d9ece 100644 --- a/tests/integration/api_repo_secrets_test.go +++ b/tests/integration/api_repo_secrets_test.go @@ -24,6 +24,12 @@ func TestAPIRepoSecrets(t *testing.T) { session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + t.Run("List", func(t *testing.T) { + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/secrets", repo.FullName())). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + }) + t.Run("Create", func(t *testing.T) { cases := []struct { Name string @@ -31,7 +37,7 @@ func TestAPIRepoSecrets(t *testing.T) { }{ { Name: "", - ExpectedStatus: http.StatusNotFound, + ExpectedStatus: http.StatusMethodNotAllowed, }, { Name: "-", diff --git a/tests/integration/api_repo_tags_test.go b/tests/integration/api_repo_tags_test.go index c6eeb404c0..10a82e11a8 100644 --- a/tests/integration/api_repo_tags_test.go +++ b/tests/integration/api_repo_tags_test.go @@ -85,3 +85,39 @@ func createNewTagUsingAPI(t *testing.T, session *TestSession, token, ownerName, DecodeJSON(t, resp, &respObj) return &respObj } + +func TestAPIGetTagArchiveDownloadCount(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + repoName := "repo1" + tagName := "TagDownloadCount" + + createNewTagUsingAPI(t, session, token, user.Name, repoName, tagName, "", "") + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags/%s?token=%s", user.Name, repoName, tagName, token) + + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + var tagInfo *api.Tag + DecodeJSON(t, resp, &tagInfo) + + // Check if everything defaults to 0 + assert.Equal(t, int64(0), tagInfo.ArchiveDownloadCount.TarGz) + assert.Equal(t, int64(0), tagInfo.ArchiveDownloadCount.Zip) + + // Download the tarball to increase the count + MakeRequest(t, NewRequest(t, "GET", tagInfo.TarballURL), http.StatusOK) + + // Check if the count has increased + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &tagInfo) + + assert.Equal(t, int64(1), tagInfo.ArchiveDownloadCount.TarGz) + assert.Equal(t, int64(0), tagInfo.ArchiveDownloadCount.Zip) +} diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index 96f38edc86..2fb89cfa6e 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -684,9 +684,7 @@ func TestAPIRepoGetReviewers(t *testing.T) { resp := MakeRequest(t, req, http.StatusOK) var reviewers []*api.User DecodeJSON(t, resp, &reviewers) - if assert.Len(t, reviewers, 3) { - assert.ElementsMatch(t, []int64{1, 4, 11}, []int64{reviewers[0].ID, reviewers[1].ID, reviewers[2].ID}) - } + assert.Len(t, reviewers, 4) } func TestAPIRepoGetAssignees(t *testing.T) { diff --git a/tests/integration/api_repo_variables_test.go b/tests/integration/api_repo_variables_test.go new file mode 100644 index 0000000000..7847962b07 --- /dev/null +++ b/tests/integration/api_repo_variables_test.go @@ -0,0 +1,149 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" +) + +func TestAPIRepoVariables(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + t.Run("CreateRepoVariable", func(t *testing.T) { + cases := []struct { + Name string + ExpectedStatus int + }{ + { + Name: "-", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: "_", + ExpectedStatus: http.StatusNoContent, + }, + { + Name: "TEST_VAR", + ExpectedStatus: http.StatusNoContent, + }, + { + Name: "test_var", + ExpectedStatus: http.StatusConflict, + }, + { + Name: "ci", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: "123var", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: "var@test", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: "github_var", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: "gitea_var", + ExpectedStatus: http.StatusBadRequest, + }, + } + + for _, c := range cases { + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), c.Name), api.CreateVariableOption{ + Value: "value", + }).AddTokenAuth(token) + MakeRequest(t, req, c.ExpectedStatus) + } + }) + + t.Run("UpdateRepoVariable", func(t *testing.T) { + variableName := "test_update_var" + url := fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), variableName) + req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ + Value: "initial_val", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + cases := []struct { + Name string + UpdateName string + ExpectedStatus int + }{ + { + Name: "not_found_var", + ExpectedStatus: http.StatusNotFound, + }, + { + Name: variableName, + UpdateName: "1invalid", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: variableName, + UpdateName: "invalid@name", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: variableName, + UpdateName: "ci", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: variableName, + UpdateName: "updated_var_name", + ExpectedStatus: http.StatusNoContent, + }, + { + Name: variableName, + ExpectedStatus: http.StatusNotFound, + }, + { + Name: "updated_var_name", + ExpectedStatus: http.StatusNoContent, + }, + } + + for _, c := range cases { + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), c.Name), api.UpdateVariableOption{ + Name: c.UpdateName, + Value: "updated_val", + }).AddTokenAuth(token) + MakeRequest(t, req, c.ExpectedStatus) + } + }) + + t.Run("DeleteRepoVariable", func(t *testing.T) { + variableName := "test_delete_var" + url := fmt.Sprintf("/api/v1/repos/%s/actions/variables/%s", repo.FullName(), variableName) + + req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ + Value: "initial_val", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) +} diff --git a/tests/integration/api_user_search_test.go b/tests/integration/api_user_search_test.go index f776b35325..0e01b504cc 100644 --- a/tests/integration/api_user_search_test.go +++ b/tests/integration/api_user_search_test.go @@ -10,7 +10,9 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -57,6 +59,25 @@ func TestAPIUserSearchNotLoggedIn(t *testing.T) { } } +func TestAPIUserSearchPaged(t *testing.T) { + defer tests.PrepareTestEnv(t)() + defer test.MockVariableValue(&setting.API.DefaultPagingNum, 5)() + + req := NewRequest(t, "GET", "/api/v1/users/search?limit=1") + resp := MakeRequest(t, req, http.StatusOK) + + var limitedResults SearchResults + DecodeJSON(t, resp, &limitedResults) + assert.Len(t, limitedResults.Data, 1) + + req = NewRequest(t, "GET", "/api/v1/users/search") + resp = MakeRequest(t, req, http.StatusOK) + + var results SearchResults + DecodeJSON(t, resp, &results) + assert.Len(t, results.Data, 5) +} + func TestAPIUserSearchSystemUsers(t *testing.T) { defer tests.PrepareTestEnv(t)() for _, systemUser := range []*user_model.User{ diff --git a/tests/integration/api_user_variables_test.go b/tests/integration/api_user_variables_test.go new file mode 100644 index 0000000000..dd5501f0b9 --- /dev/null +++ b/tests/integration/api_user_variables_test.go @@ -0,0 +1,144 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" +) + +func TestAPIUserVariables(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) + + t.Run("CreateRepoVariable", func(t *testing.T) { + cases := []struct { + Name string + ExpectedStatus int + }{ + { + Name: "-", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: "_", + ExpectedStatus: http.StatusNoContent, + }, + { + Name: "TEST_VAR", + ExpectedStatus: http.StatusNoContent, + }, + { + Name: "test_var", + ExpectedStatus: http.StatusConflict, + }, + { + Name: "ci", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: "123var", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: "var@test", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: "github_var", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: "gitea_var", + ExpectedStatus: http.StatusBadRequest, + }, + } + + for _, c := range cases { + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.CreateVariableOption{ + Value: "value", + }).AddTokenAuth(token) + MakeRequest(t, req, c.ExpectedStatus) + } + }) + + t.Run("UpdateRepoVariable", func(t *testing.T) { + variableName := "test_update_var" + url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName) + req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ + Value: "initial_val", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + cases := []struct { + Name string + UpdateName string + ExpectedStatus int + }{ + { + Name: "not_found_var", + ExpectedStatus: http.StatusNotFound, + }, + { + Name: variableName, + UpdateName: "1invalid", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: variableName, + UpdateName: "invalid@name", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: variableName, + UpdateName: "ci", + ExpectedStatus: http.StatusBadRequest, + }, + { + Name: variableName, + UpdateName: "updated_var_name", + ExpectedStatus: http.StatusNoContent, + }, + { + Name: variableName, + ExpectedStatus: http.StatusNotFound, + }, + { + Name: "updated_var_name", + ExpectedStatus: http.StatusNoContent, + }, + } + + for _, c := range cases { + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.UpdateVariableOption{ + Name: c.UpdateName, + Value: "updated_val", + }).AddTokenAuth(token) + MakeRequest(t, req, c.ExpectedStatus) + } + }) + + t.Run("DeleteRepoVariable", func(t *testing.T) { + variableName := "test_delete_var" + url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName) + + req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ + Value: "initial_val", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) +} diff --git a/tests/integration/api_wiki_test.go b/tests/integration/api_wiki_test.go index c61b4a061b..400cf068b4 100644 --- a/tests/integration/api_wiki_test.go +++ b/tests/integration/api_wiki_test.go @@ -15,6 +15,8 @@ import ( repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/optional" api "code.gitea.io/gitea/modules/structs" repo_service "code.gitea.io/gitea/services/repository" "code.gitea.io/gitea/tests" @@ -286,6 +288,63 @@ func TestAPIEditOtherWikiPage(t *testing.T) { testCreateWiki(http.StatusCreated) } +func TestAPISetWikiGlobalEditability(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // Create a new repository for testing purposes + repo, _, f := CreateDeclarativeRepo(t, user, "", []unit_model.Type{ + unit_model.TypeCode, + unit_model.TypeWiki, + }, nil, nil) + defer f() + urlStr := fmt.Sprintf("/api/v1/repos/%s", repo.FullName()) + + assertGlobalEditability := func(t *testing.T, editability bool) { + t.Helper() + + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + var opts api.Repository + DecodeJSON(t, resp, &opts) + + assert.Equal(t, opts.GloballyEditableWiki, editability) + } + + t.Run("api includes GloballyEditableWiki", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + assertGlobalEditability(t, false) + }) + + t.Run("api can turn on GloballyEditableWiki", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + globallyEditable := true + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditRepoOption{ + GloballyEditableWiki: &globallyEditable, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + assertGlobalEditability(t, true) + }) + + t.Run("disabling the wiki disables GloballyEditableWiki", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + hasWiki := false + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditRepoOption{ + HasWiki: &hasWiki, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + assertGlobalEditability(t, false) + }) +} + func TestAPIListPageRevisions(t *testing.T) { defer tests.PrepareTestEnv(t)() username := "user2" @@ -324,3 +383,28 @@ func TestAPIListPageRevisions(t *testing.T) { assert.Equal(t, dummyrevisions, revisions) } + +func TestAPIWikiNonMasterBranch(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + repo, _, f := CreateDeclarativeRepoWithOptions(t, user, DeclarativeRepoOptions{ + WikiBranch: optional.Some("main"), + }) + defer f() + + uris := []string{ + "revisions/Home", + "pages", + "page/Home", + } + baseURL := fmt.Sprintf("/api/v1/repos/%s/wiki", repo.FullName()) + for _, uri := range uris { + t.Run(uri, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestf(t, "GET", "%s/%s", baseURL, uri) + MakeRequest(t, req, http.StatusOK) + }) + } +} diff --git a/tests/integration/auth_ldap_test.go b/tests/integration/auth_ldap_test.go index 3a5fdb97a6..06677287c0 100644 --- a/tests/integration/auth_ldap_test.go +++ b/tests/integration/auth_ldap_test.go @@ -112,13 +112,17 @@ func getLDAPServerPort() string { return port } -func buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, groupFilter, groupTeamMap, groupTeamMapRemoval string) map[string]string { +func buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, mailKeyAttribute, defaultDomainName, groupFilter, groupTeamMap, groupTeamMapRemoval string) map[string]string { // Modify user filter to test group filter explicitly userFilter := "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))" if groupFilter != "" { userFilter = "(&(objectClass=inetOrgPerson)(uid=%s))" } + if len(mailKeyAttribute) == 0 { + mailKeyAttribute = "mail" + } + return map[string]string{ "_csrf": csrf, "type": "2", @@ -134,8 +138,9 @@ func buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, groupFilter, groupTeamMap "attribute_username": "uid", "attribute_name": "givenName", "attribute_surname": "sn", - "attribute_mail": "mail", + "attribute_mail": mailKeyAttribute, "attribute_ssh_public_key": sshKeyAttribute, + "default_domain_name": defaultDomainName, "is_sync_enabled": "on", "is_active": "on", "groups_enabled": "on", @@ -148,7 +153,7 @@ func buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, groupFilter, groupTeamMap } } -func addAuthSourceLDAP(t *testing.T, sshKeyAttribute, groupFilter string, groupMapParams ...string) { +func addAuthSourceLDAP(t *testing.T, sshKeyAttribute, mailKeyAttribute, defaultDomainName, groupFilter string, groupMapParams ...string) { groupTeamMapRemoval := "off" groupTeamMap := "" if len(groupMapParams) == 2 { @@ -157,7 +162,7 @@ func addAuthSourceLDAP(t *testing.T, sshKeyAttribute, groupFilter string, groupM } session := loginUser(t, "user1") csrf := GetCSRF(t, session, "/admin/auths/new") - req := NewRequestWithValues(t, "POST", "/admin/auths/new", buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, groupFilter, groupTeamMap, groupTeamMapRemoval)) + req := NewRequestWithValues(t, "POST", "/admin/auths/new", buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, mailKeyAttribute, defaultDomainName, groupFilter, groupTeamMap, groupTeamMapRemoval)) session.MakeRequest(t, req, http.StatusSeeOther) } @@ -167,7 +172,7 @@ func TestLDAPUserSignin(t *testing.T) { return } defer tests.PrepareTestEnv(t)() - addAuthSourceLDAP(t, "", "") + addAuthSourceLDAP(t, "", "", "", "") u := gitLDAPUsers[0] @@ -184,7 +189,7 @@ func TestLDAPUserSignin(t *testing.T) { func TestLDAPAuthChange(t *testing.T) { defer tests.PrepareTestEnv(t)() - addAuthSourceLDAP(t, "", "") + addAuthSourceLDAP(t, "", "", "", "") session := loginUser(t, "user1") req := NewRequest(t, "GET", "/admin/auths") @@ -205,7 +210,7 @@ func TestLDAPAuthChange(t *testing.T) { binddn, _ := doc.Find(`input[name="bind_dn"]`).Attr("value") assert.Equal(t, "uid=gitea,ou=service,dc=planetexpress,dc=com", binddn) - req = NewRequestWithValues(t, "POST", href, buildAuthSourceLDAPPayload(csrf, "", "", "", "off")) + req = NewRequestWithValues(t, "POST", href, buildAuthSourceLDAPPayload(csrf, "", "", "", "", "", "off")) session.MakeRequest(t, req, http.StatusSeeOther) req = NewRequest(t, "GET", href) @@ -215,6 +220,21 @@ func TestLDAPAuthChange(t *testing.T) { assert.Equal(t, host, getLDAPServerHost()) binddn, _ = doc.Find(`input[name="bind_dn"]`).Attr("value") assert.Equal(t, "uid=gitea,ou=service,dc=planetexpress,dc=com", binddn) + domainname, _ := doc.Find(`input[name="default_domain_name"]`).Attr("value") + assert.Equal(t, "", domainname) + + req = NewRequestWithValues(t, "POST", href, buildAuthSourceLDAPPayload(csrf, "", "", "test.org", "", "", "off")) + session.MakeRequest(t, req, http.StatusSeeOther) + + req = NewRequest(t, "GET", href) + resp = session.MakeRequest(t, req, http.StatusOK) + doc = NewHTMLParser(t, resp.Body) + host, _ = doc.Find(`input[name="host"]`).Attr("value") + assert.Equal(t, host, getLDAPServerHost()) + binddn, _ = doc.Find(`input[name="bind_dn"]`).Attr("value") + assert.Equal(t, "uid=gitea,ou=service,dc=planetexpress,dc=com", binddn) + domainname, _ = doc.Find(`input[name="default_domain_name"]`).Attr("value") + assert.Equal(t, "test.org", domainname) } func TestLDAPUserSync(t *testing.T) { @@ -223,7 +243,7 @@ func TestLDAPUserSync(t *testing.T) { return } defer tests.PrepareTestEnv(t)() - addAuthSourceLDAP(t, "", "") + addAuthSourceLDAP(t, "", "", "", "") auth.SyncExternalUsers(context.Background(), true) // Check if users exists @@ -252,7 +272,7 @@ func TestLDAPUserSyncWithEmptyUsernameAttribute(t *testing.T) { session := loginUser(t, "user1") csrf := GetCSRF(t, session, "/admin/auths/new") - payload := buildAuthSourceLDAPPayload(csrf, "", "", "", "") + payload := buildAuthSourceLDAPPayload(csrf, "", "", "", "", "", "") payload["attribute_username"] = "" req := NewRequestWithValues(t, "POST", "/admin/auths/new", payload) session.MakeRequest(t, req, http.StatusSeeOther) @@ -300,7 +320,7 @@ func TestLDAPUserSyncWithGroupFilter(t *testing.T) { return } defer tests.PrepareTestEnv(t)() - addAuthSourceLDAP(t, "", "(cn=git)") + addAuthSourceLDAP(t, "", "", "", "(cn=git)") // Assert a user not a member of the LDAP group "cn=git" cannot login // This test may look like TestLDAPUserSigninFailed but it is not. @@ -359,7 +379,7 @@ func TestLDAPUserSigninFailed(t *testing.T) { return } defer tests.PrepareTestEnv(t)() - addAuthSourceLDAP(t, "", "") + addAuthSourceLDAP(t, "", "", "", "") u := otherLDAPUsers[0] testLoginFailed(t, u.UserName, u.Password, translation.NewLocale("en-US").TrString("form.username_password_incorrect")) @@ -371,7 +391,7 @@ func TestLDAPUserSSHKeySync(t *testing.T) { return } defer tests.PrepareTestEnv(t)() - addAuthSourceLDAP(t, "sshPublicKey", "") + addAuthSourceLDAP(t, "sshPublicKey", "", "", "") auth.SyncExternalUsers(context.Background(), true) @@ -404,7 +424,7 @@ func TestLDAPGroupTeamSyncAddMember(t *testing.T) { return } defer tests.PrepareTestEnv(t)() - addAuthSourceLDAP(t, "", "", "on", `{"cn=ship_crew,ou=people,dc=planetexpress,dc=com":{"org26": ["team11"]},"cn=admin_staff,ou=people,dc=planetexpress,dc=com": {"non-existent": ["non-existent"]}}`) + addAuthSourceLDAP(t, "", "", "", "", "on", `{"cn=ship_crew,ou=people,dc=planetexpress,dc=com":{"org26": ["team11"]},"cn=admin_staff,ou=people,dc=planetexpress,dc=com": {"non-existent": ["non-existent"]}}`) org, err := organization.GetOrgByName(db.DefaultContext, "org26") assert.NoError(t, err) team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11") @@ -449,7 +469,7 @@ func TestLDAPGroupTeamSyncRemoveMember(t *testing.T) { return } defer tests.PrepareTestEnv(t)() - addAuthSourceLDAP(t, "", "", "on", `{"cn=dispatch,ou=people,dc=planetexpress,dc=com": {"org26": ["team11"]}}`) + addAuthSourceLDAP(t, "", "", "", "", "on", `{"cn=dispatch,ou=people,dc=planetexpress,dc=com": {"org26": ["team11"]}}`) org, err := organization.GetOrgByName(db.DefaultContext, "org26") assert.NoError(t, err) team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11") @@ -487,6 +507,58 @@ func TestLDAPPreventInvalidGroupTeamMap(t *testing.T) { session := loginUser(t, "user1") csrf := GetCSRF(t, session, "/admin/auths/new") - req := NewRequestWithValues(t, "POST", "/admin/auths/new", buildAuthSourceLDAPPayload(csrf, "", "", `{"NOT_A_VALID_JSON"["MISSING_DOUBLE_POINT"]}`, "off")) + req := NewRequestWithValues(t, "POST", "/admin/auths/new", buildAuthSourceLDAPPayload(csrf, "", "", "", "", `{"NOT_A_VALID_JSON"["MISSING_DOUBLE_POINT"]}`, "off")) session.MakeRequest(t, req, http.StatusOK) // StatusOK = failed, StatusSeeOther = ok } + +func TestLDAPUserSyncInvalidMail(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "nonexisting", "", "") + auth.SyncExternalUsers(context.Background(), true) + + // Check if users exists + for _, gitLDAPUser := range gitLDAPUsers { + dbUser, err := user_model.GetUserByName(db.DefaultContext, gitLDAPUser.UserName) + assert.NoError(t, err) + assert.Equal(t, gitLDAPUser.UserName, dbUser.Name) + assert.Equal(t, gitLDAPUser.UserName+"@localhost.local", dbUser.Email) + assert.Equal(t, gitLDAPUser.IsAdmin, dbUser.IsAdmin) + assert.Equal(t, gitLDAPUser.IsRestricted, dbUser.IsRestricted) + } + + // Check if no users exist + for _, otherLDAPUser := range otherLDAPUsers { + _, err := user_model.GetUserByName(db.DefaultContext, otherLDAPUser.UserName) + assert.True(t, user_model.IsErrUserNotExist(err)) + } +} + +func TestLDAPUserSyncInvalidMailDefaultDomain(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "nonexisting", "test.org", "") + auth.SyncExternalUsers(context.Background(), true) + + // Check if users exists + for _, gitLDAPUser := range gitLDAPUsers { + dbUser, err := user_model.GetUserByName(db.DefaultContext, gitLDAPUser.UserName) + assert.NoError(t, err) + assert.Equal(t, gitLDAPUser.UserName, dbUser.Name) + assert.Equal(t, gitLDAPUser.UserName+"@test.org", dbUser.Email) + assert.Equal(t, gitLDAPUser.IsAdmin, dbUser.IsAdmin) + assert.Equal(t, gitLDAPUser.IsRestricted, dbUser.IsRestricted) + } + + // Check if no users exist + for _, otherLDAPUser := range otherLDAPUsers { + _, err := user_model.GetUserByName(db.DefaultContext, otherLDAPUser.UserName) + assert.True(t, user_model.IsErrUserNotExist(err)) + } +} diff --git a/tests/integration/benchmarks_test.go b/tests/integration/benchmarks_test.go index 7a882fe836..62da761d2d 100644 --- a/tests/integration/benchmarks_test.go +++ b/tests/integration/benchmarks_test.go @@ -4,7 +4,7 @@ package integration import ( - "math/rand" + "math/rand/v2" "net/http" "net/url" "testing" @@ -18,7 +18,7 @@ import ( func StringWithCharset(length int, charset string) string { b := make([]byte, length) for i := range b { - b[i] = charset[rand.Intn(len(charset))] + b[i] = charset[rand.IntN(len(charset))] } return string(b) } @@ -37,7 +37,7 @@ func BenchmarkRepoBranchCommit(b *testing.B) { b.ResetTimer() b.Run("CreateBranch", func(b *testing.B) { b.StopTimer() - branchName := StringWithCharset(5+rand.Intn(10), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + branchName := StringWithCharset(5+rand.IntN(10), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") b.StartTimer() for i := 0; i < b.N; i++ { b.Run("new_"+branchName, func(b *testing.B) { diff --git a/tests/integration/db_collation_test.go b/tests/integration/db_collation_test.go index eee26d1ed1..4d822c45a6 100644 --- a/tests/integration/db_collation_test.go +++ b/tests/integration/db_collation_test.go @@ -39,7 +39,7 @@ func TestDatabaseCollationSelfCheckUI(t *testing.T) { htmlDoc.AssertElement(t, "a.item[href*='/admin/self_check']", exists) } - if setting.Database.Type.IsMySQL() || setting.Database.Type.IsMSSQL() { + if setting.Database.Type.IsMySQL() { assertSelfCheckExists(true) } else { assertSelfCheckExists(false) @@ -61,10 +61,9 @@ func TestDatabaseCollation(t *testing.T) { assert.EqualValues(t, 2, cnt) _, _ = x.Exec("DROP TABLE IF EXISTS test_collation_tbl") - // by default, SQLite3 and PostgreSQL are using case-sensitive collations, but MySQL and MSSQL are not - // the following tests are only for MySQL and MSSQL - if !setting.Database.Type.IsMySQL() && !setting.Database.Type.IsMSSQL() { - t.Skip("only MySQL and MSSQL requires the case-sensitive collation check at the moment") + // by default, SQLite3 and PostgreSQL are using case-sensitive collations, but MySQL is not. + if !setting.Database.Type.IsMySQL() { + t.Skip("only MySQL requires the case-sensitive collation check at the moment") return } @@ -86,20 +85,11 @@ func TestDatabaseCollation(t *testing.T) { assert.True(t, r.CollationEquals("abc", "abc")) assert.True(t, r.CollationEquals("abc", "utf8mb4_abc")) assert.False(t, r.CollationEquals("utf8mb4_general_ci", "utf8mb4_unicode_ci")) - } else if setting.Database.Type.IsMSSQL() { - assert.True(t, r.IsCollationCaseSensitive("Latin1_General_CS_AS")) - assert.False(t, r.IsCollationCaseSensitive("Latin1_General_CI_AS")) - assert.True(t, r.CollationEquals("abc", "abc")) - assert.False(t, r.CollationEquals("Latin1_General_CS_AS", "SQL_Latin1_General_CP1_CS_AS")) } else { assert.Fail(t, "unexpected database type") } }) - if setting.Database.Type.IsMSSQL() { - return // skip table converting tests because MSSQL doesn't have a simple solution at the moment - } - t.Run("Convert tables to utf8mb4_bin", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/editor_test.go b/tests/integration/editor_test.go index 3e8416925b..ae0aea237b 100644 --- a/tests/integration/editor_test.go +++ b/tests/integration/editor_test.go @@ -404,7 +404,7 @@ func TestCommitMail(t *testing.T) { t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - // Upload two seperate times, so we have two different 'uploads' that can + // Upload two separate times, so we have two different 'uploads' that can // be used indepently of each other. uploadFile := func(t *testing.T, name, content string) string { t.Helper() diff --git a/tests/integration/git_test.go b/tests/integration/git_test.go index f833366568..6ee3be2df2 100644 --- a/tests/integration/git_test.go +++ b/tests/integration/git_test.go @@ -5,9 +5,9 @@ package integration import ( "bytes" + "crypto/rand" "encoding/hex" "fmt" - "math/rand" "net/http" "net/url" "os" diff --git a/tests/integration/gpg_git_test.go b/tests/integration/gpg_git_test.go index 00890cfb38..3ba4a5882c 100644 --- a/tests/integration/gpg_git_test.go +++ b/tests/integration/gpg_git_test.go @@ -19,9 +19,9 @@ import ( "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/armor" "github.com/stretchr/testify/assert" - "golang.org/x/crypto/openpgp" - "golang.org/x/crypto/openpgp/armor" ) func TestGPGGit(t *testing.T) { diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index f1c6b9f135..f6daf0d146 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -42,6 +42,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers" + "code.gitea.io/gitea/services/auth/source/remote" gitea_context "code.gitea.io/gitea/services/context" repo_service "code.gitea.io/gitea/services/repository" files_service "code.gitea.io/gitea/services/repository/files" @@ -53,7 +54,8 @@ import ( gouuid "github.com/google/uuid" "github.com/markbates/goth" "github.com/markbates/goth/gothic" - goth_gitlab "github.com/markbates/goth/providers/gitlab" + goth_gitlab "github.com/markbates/goth/providers/github" + goth_github "github.com/markbates/goth/providers/gitlab" "github.com/santhosh-tekuri/jsonschema/v5" "github.com/stretchr/testify/assert" ) @@ -338,6 +340,36 @@ func authSourcePayloadGitLabCustom(name string) map[string]string { return payload } +func authSourcePayloadGitHub(name string) map[string]string { + payload := authSourcePayloadOAuth2(name) + payload["oauth2_provider"] = "github" + return payload +} + +func authSourcePayloadGitHubCustom(name string) map[string]string { + payload := authSourcePayloadGitHub(name) + payload["oauth2_use_custom_url"] = "on" + payload["oauth2_auth_url"] = goth_github.AuthURL + payload["oauth2_token_url"] = goth_github.TokenURL + payload["oauth2_profile_url"] = goth_github.ProfileURL + return payload +} + +func createRemoteAuthSource(t *testing.T, name, url, matchingSource string) *auth.Source { + assert.NoError(t, auth.CreateSource(context.Background(), &auth.Source{ + Type: auth.Remote, + Name: name, + IsActive: true, + Cfg: &remote.Source{ + URL: url, + MatchingSource: matchingSource, + }, + })) + source, err := auth.GetSourceByName(context.Background(), name) + assert.NoError(t, err) + return source +} + func createUser(ctx context.Context, t testing.TB, user *user_model.User) func() { user.MustChangePassword = false user.LowerName = strings.ToLower(user.Name) @@ -623,7 +655,7 @@ func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile schema, err := jsonschema.Compile(schemaFilePath) assert.NoError(t, err) - var data interface{} + var data any err = json.Unmarshal(resp.Body.Bytes(), &data) assert.NoError(t, err) diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index 94ba670b71..b63127be82 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -15,6 +15,7 @@ import ( "testing" "time" + auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" @@ -194,6 +195,93 @@ func TestNewIssue(t *testing.T) { testNewIssue(t, session, "user2", "repo1", "Title", "Description") } +func TestIssueDependencies(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + session := loginUser(t, owner.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) + + repo, _, f := CreateDeclarativeRepoWithOptions(t, owner, DeclarativeRepoOptions{}) + defer f() + + createIssue := func(t *testing.T, title string) api.Issue { + t.Helper() + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repo.Name) + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{ + Body: "", + Title: title, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + + var apiIssue api.Issue + DecodeJSON(t, resp, &apiIssue) + + return apiIssue + } + addDependency := func(t *testing.T, issue, dependency api.Issue) { + t.Helper() + + urlStr := fmt.Sprintf("/%s/%s/issues/%d/dependency/add", owner.Name, repo.Name, issue.Index) + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "_csrf": GetCSRF(t, session, fmt.Sprintf("/%s/%s/issues/%d", owner.Name, repo.Name, issue.Index)), + "newDependency": fmt.Sprintf("%d", dependency.Index), + }) + session.MakeRequest(t, req, http.StatusSeeOther) + } + removeDependency := func(t *testing.T, issue, dependency api.Issue) { + t.Helper() + + urlStr := fmt.Sprintf("/%s/%s/issues/%d/dependency/delete", owner.Name, repo.Name, issue.Index) + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "_csrf": GetCSRF(t, session, fmt.Sprintf("/%s/%s/issues/%d", owner.Name, repo.Name, issue.Index)), + "removeDependencyID": fmt.Sprintf("%d", dependency.Index), + "dependencyType": "blockedBy", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + } + + assertHasDependency := func(t *testing.T, issueID, dependencyID int64, hasDependency bool) { + t.Helper() + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/dependencies", owner.Name, repo.Name, issueID) + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + var issues []api.Issue + DecodeJSON(t, resp, &issues) + + if hasDependency { + assert.NotEmpty(t, issues) + assert.EqualValues(t, issues[0].Index, dependencyID) + } else { + assert.Empty(t, issues) + } + } + + t.Run("Add dependency", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + issue1 := createIssue(t, "issue #1") + issue2 := createIssue(t, "issue #2") + addDependency(t, issue1, issue2) + + assertHasDependency(t, issue1.Index, issue2.Index, true) + }) + + t.Run("Remove dependency", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + issue1 := createIssue(t, "issue #1") + issue2 := createIssue(t, "issue #2") + addDependency(t, issue1, issue2) + removeDependency(t, issue1, issue2) + + assertHasDependency(t, issue1.Index, issue2.Index, false) + }) +} + func TestIssueCommentClose(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user2") @@ -308,6 +396,16 @@ func TestIssueCommentUpdate(t *testing.T) { comment = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID}) assert.Equal(t, modifiedContent, comment.Content) + + // make the comment empty + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{ + "_csrf": GetCSRF(t, session, issueURL), + "content": "", + }) + session.MakeRequest(t, req, http.StatusOK) + + comment = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID}) + assert.Equal(t, "", comment.Content) } func TestIssueReaction(t *testing.T) { diff --git a/tests/integration/migration-test/gitea-v1.6.4.mssql.sql.gz b/tests/integration/migration-test/gitea-v1.6.4.mssql.sql.gz deleted file mode 100644 index 1b676feda1..0000000000 Binary files a/tests/integration/migration-test/gitea-v1.6.4.mssql.sql.gz and /dev/null differ diff --git a/tests/integration/migration-test/gitea-v1.7.0.mssql.sql.gz b/tests/integration/migration-test/gitea-v1.7.0.mssql.sql.gz deleted file mode 100644 index bd869cfa58..0000000000 Binary files a/tests/integration/migration-test/gitea-v1.7.0.mssql.sql.gz and /dev/null differ diff --git a/tests/integration/migration-test/migration_test.go b/tests/integration/migration-test/migration_test.go index d1aa3f3eb8..e0e5620cd2 100644 --- a/tests/integration/migration-test/migration_test.go +++ b/tests/integration/migration-test/migration_test.go @@ -14,7 +14,6 @@ import ( "path/filepath" "regexp" "sort" - "strings" "testing" "code.gitea.io/gitea/models/db" @@ -258,31 +257,6 @@ func restoreOldDB(t *testing.T, version string) bool { _, err = db.Exec(data) assert.NoError(t, err) db.Close() - - case setting.Database.Type.IsMSSQL(): - host, port := setting.ParseMSSQLHostPort(setting.Database.Host) - db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", - host, port, "master", setting.Database.User, setting.Database.Passwd)) - assert.NoError(t, err) - defer db.Close() - - _, err = db.Exec("DROP DATABASE IF EXISTS [gitea]") - assert.NoError(t, err) - - statements := strings.Split(data, "\nGO\n") - for _, statement := range statements { - if len(statement) > 5 && statement[:5] == "USE [" { - dbname := statement[5 : len(statement)-1] - db.Close() - db, err = sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", - host, port, dbname, setting.Database.User, setting.Database.Passwd)) - assert.NoError(t, err) - defer db.Close() - } - _, err = db.Exec(statement) - assert.NoError(t, err, "Failure whilst running: %s\nError: %v", statement, err) - } - db.Close() } return true } diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index 1da1c6f9c0..46beddb5f3 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -6,9 +6,12 @@ package integration import ( "bytes" "context" + "crypto/sha256" + "encoding/base64" "fmt" "io" "net/http" + "net/url" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -470,6 +473,57 @@ func TestSignInOAuthCallbackSignIn(t *testing.T) { assert.Greater(t, userAfterLogin.LastLoginUnix, userGitLab.LastLoginUnix) } +func TestSignInOAuthCallbackPKCE(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // Setup authentication source + gitlabName := "gitlab" + gitlab := addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName)) + // Create a user as if it had been previously been created by the authentication source. + userGitLabUserID := "5678" + userGitLab := &user_model.User{ + Name: "gitlabuser", + Email: "gitlabuser@example.com", + Passwd: "gitlabuserpassword", + Type: user_model.UserTypeIndividual, + LoginType: auth_model.OAuth2, + LoginSource: gitlab.ID, + LoginName: userGitLabUserID, + } + defer createUser(context.Background(), t, userGitLab)() + + // initial redirection (to generate the code_challenge) + session := emptyTestSession(t) + req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s", gitlabName)) + resp := session.MakeRequest(t, req, http.StatusTemporaryRedirect) + dest, err := url.Parse(resp.Header().Get("Location")) + assert.NoError(t, err) + assert.Equal(t, "S256", dest.Query().Get("code_challenge_method")) + codeChallenge := dest.Query().Get("code_challenge") + assert.NotEmpty(t, codeChallenge) + + // callback (to check the initial code_challenge) + defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) { + codeVerifier := req.URL.Query().Get("code_verifier") + assert.NotEmpty(t, codeVerifier) + assert.Greater(t, len(codeVerifier), 40, codeVerifier) + + sha2 := sha256.New() + io.WriteString(sha2, codeVerifier) + assert.Equal(t, codeChallenge, base64.RawURLEncoding.EncodeToString(sha2.Sum(nil))) + + return goth.User{ + Provider: gitlabName, + UserID: userGitLabUserID, + Email: userGitLab.Email, + }, nil + })() + req = NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", gitlabName)) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/", test.RedirectURL(resp)) + unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userGitLab.ID}) +} + func TestSignInOAuthCallbackRedirectToEscaping(t *testing.T) { defer tests.PrepareTestEnv(t)() diff --git a/tests/integration/pull_review_test.go b/tests/integration/pull_review_test.go index 63ec3f9f35..bcdb352612 100644 --- a/tests/integration/pull_review_test.go +++ b/tests/integration/pull_review_test.go @@ -6,13 +6,16 @@ package integration import ( "context" "net/http" + "net/http/httptest" "net/url" + "path" "strconv" "strings" "testing" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" @@ -399,3 +402,82 @@ func TestPullView_CodeOwner(t *testing.T) { }) }) } + +func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + user1Session := loginUser(t, "user1") + user2Session := loginUser(t, "user2") + + // Have user1 create a fork of repo1. + testRepoFork(t, user1Session, "user2", "repo1", "user1", "repo1") + + t.Run("Submit approve/reject review on merged PR", func(t *testing.T) { + // Create a merged PR (made by user1) in the upstream repo1. + testEditFile(t, user1Session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") + resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "master", "This is a pull title") + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testPullMerge(t, user1Session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) + + // Grab the CSRF token. + req := NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4])) + resp = user2Session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + // Submit an approve review on the PR. + testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "approve", http.StatusUnprocessableEntity) + + // Submit a reject review on the PR. + testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "reject", http.StatusUnprocessableEntity) + }) + + t.Run("Submit approve/reject review on closed PR", func(t *testing.T) { + // Created a closed PR (made by user1) in the upstream repo1. + testEditFileToNewBranch(t, user1Session, "user1", "repo1", "master", "a-test-branch", "README.md", "Hello, World (Editied...again)\n") + resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "a-test-branch", "This is a pull title") + elem := strings.Split(test.RedirectURL(resp), "/") + assert.EqualValues(t, "pulls", elem[3]) + testIssueClose(t, user1Session, elem[1], elem[2], elem[4]) + + // Grab the CSRF token. + req := NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4])) + resp = user2Session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + // Submit an approve review on the PR. + testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "approve", http.StatusUnprocessableEntity) + + // Submit a reject review on the PR. + testSubmitReview(t, user2Session, htmlDoc.GetCSRF(), "user2", "repo1", elem[4], "reject", http.StatusUnprocessableEntity) + }) + }) +} + +func testSubmitReview(t *testing.T, session *TestSession, csrf, owner, repo, pullNumber, reviewType string, expectedSubmitStatus int) *httptest.ResponseRecorder { + options := map[string]string{ + "_csrf": csrf, + "commit_id": "", + "content": "test", + "type": reviewType, + } + + submitURL := path.Join(owner, repo, "pulls", pullNumber, "files", "reviews", "submit") + req := NewRequestWithValues(t, "POST", submitURL, options) + return session.MakeRequest(t, req, expectedSubmitStatus) +} + +func testIssueClose(t *testing.T, session *TestSession, owner, repo, issueNumber string) *httptest.ResponseRecorder { + req := NewRequest(t, "GET", path.Join(owner, repo, "pulls", issueNumber)) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + closeURL := path.Join(owner, repo, "issues", issueNumber, "comments") + + options := map[string]string{ + "_csrf": htmlDoc.GetCSRF(), + "status": "close", + } + + req = NewRequestWithValues(t, "POST", closeURL, options) + return session.MakeRequest(t, req, http.StatusOK) +} diff --git a/tests/integration/pull_status_test.go b/tests/integration/pull_status_test.go index 26c99e6445..80eea34513 100644 --- a/tests/integration/pull_status_test.go +++ b/tests/integration/pull_status_test.go @@ -12,6 +12,9 @@ import ( "testing" auth_model "code.gitea.io/gitea/models/auth" + git_model "code.gitea.io/gitea/models/git" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -68,7 +71,6 @@ func TestPullCreate_CommitStatus(t *testing.T) { // Update commit status, and check if icon is updated as well for _, status := range statusList { - // Call API to add status for commit t.Run("CreateStatus", doAPICreateCommitStatus(testCtx, commitID, api.CreateStatusOption{ State: status, @@ -90,6 +92,10 @@ func TestPullCreate_CommitStatus(t *testing.T) { assert.True(t, ok) assert.Contains(t, cls, statesIcons[status]) } + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"}) + css := unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatusSummary{RepoID: repo1.ID, SHA: commitID}) + assert.EqualValues(t, api.CommitStatusWarning, css.State) }) } diff --git a/tests/integration/release_test.go b/tests/integration/release_test.go index acc8cb68c2..ad3c7bf325 100644 --- a/tests/integration/release_test.go +++ b/tests/integration/release_test.go @@ -9,15 +9,19 @@ import ( "testing" "time" + auth_model "code.gitea.io/gitea/models/auth" + "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/setting" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/tests" "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title string, preRelease, draft bool) { @@ -307,3 +311,34 @@ func TestDownloadReleaseAttachment(t *testing.T) { session := loginUser(t, "user2") session.MakeRequest(t, req, http.StatusOK) } + +func TestReleaseHideArchiveLinksUI(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + release := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{TagName: "v2.0"}) + + require.NoError(t, release.LoadAttributes(db.DefaultContext)) + + session := loginUser(t, release.Repo.OwnerName) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + zipURL := fmt.Sprintf("%s/archive/%s.zip", release.Repo.Link(), release.TagName) + tarGzURL := fmt.Sprintf("%s/archive/%s.tar.gz", release.Repo.Link(), release.TagName) + + resp := session.MakeRequest(t, NewRequest(t, "GET", release.HTMLURL()), http.StatusOK) + body := resp.Body.String() + assert.Contains(t, body, zipURL) + assert.Contains(t, body, tarGzURL) + + hideArchiveLinks := true + + req := NewRequestWithJSON(t, "PATCH", release.APIURL(), &api.EditReleaseOption{ + HideArchiveLinks: &hideArchiveLinks, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + resp = session.MakeRequest(t, NewRequest(t, "GET", release.HTMLURL()), http.StatusOK) + body = resp.Body.String() + assert.NotContains(t, body, zipURL) + assert.NotContains(t, body, tarGzURL) +} diff --git a/tests/integration/remote_test.go b/tests/integration/remote_test.go new file mode 100644 index 0000000000..d905f88a81 --- /dev/null +++ b/tests/integration/remote_test.go @@ -0,0 +1,205 @@ +// Copyright Earl Warren +// SPDX-License-Identifier: MIT + +package integration + +import ( + "context" + "fmt" + "net/http" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/test" + remote_service "code.gitea.io/gitea/services/remote" + "code.gitea.io/gitea/tests" + + "github.com/markbates/goth" + "github.com/stretchr/testify/assert" +) + +func TestRemote_MaybePromoteUserSuccess(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // + // OAuth2 authentication source GitLab + // + gitlabName := "gitlab" + _ = addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName)) + // + // Remote authentication source matching the GitLab authentication source + // + remoteName := "remote" + remote := createRemoteAuthSource(t, remoteName, "http://mygitlab.eu", gitlabName) + + // + // Create a user as if it had previously been created by the remote + // authentication source. + // + gitlabUserID := "5678" + gitlabEmail := "gitlabuser@example.com" + userBeforeSignIn := &user_model.User{ + Name: "gitlabuser", + Type: user_model.UserTypeRemoteUser, + LoginType: auth_model.Remote, + LoginSource: remote.ID, + LoginName: gitlabUserID, + } + defer createUser(context.Background(), t, userBeforeSignIn)() + + // + // A request for user information sent to Goth will return a + // goth.User exactly matching the user created above. + // + defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) { + return goth.User{ + Provider: gitlabName, + UserID: gitlabUserID, + Email: gitlabEmail, + }, nil + })() + req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", gitlabName)) + resp := MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/", test.RedirectURL(resp)) + userAfterSignIn := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userBeforeSignIn.ID}) + + // both are about the same user + assert.Equal(t, userAfterSignIn.ID, userBeforeSignIn.ID) + // the login time was updated, proof the login succeeded + assert.Greater(t, userAfterSignIn.LastLoginUnix, userBeforeSignIn.LastLoginUnix) + // the login type was promoted from Remote to OAuth2 + assert.Equal(t, userBeforeSignIn.LoginType, auth_model.Remote) + assert.Equal(t, userAfterSignIn.LoginType, auth_model.OAuth2) + // the OAuth2 email was used to set the missing user email + assert.Equal(t, userBeforeSignIn.Email, "") + assert.Equal(t, userAfterSignIn.Email, gitlabEmail) +} + +func TestRemote_MaybePromoteUserFail(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + ctx := context.Background() + // + // OAuth2 authentication source GitLab + // + gitlabName := "gitlab" + gitlabSource := addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName)) + // + // Remote authentication source matching the GitLab authentication source + // + remoteName := "remote" + remoteSource := createRemoteAuthSource(t, remoteName, "http://mygitlab.eu", gitlabName) + + { + promoted, reason, err := remote_service.MaybePromoteRemoteUser(ctx, &auth_model.Source{}, "", "") + assert.NoError(t, err) + assert.False(t, promoted) + assert.Equal(t, remote_service.ReasonNotAuth2, reason) + } + + { + remoteSource.Type = auth_model.OAuth2 + promoted, reason, err := remote_service.MaybePromoteRemoteUser(ctx, remoteSource, "", "") + assert.NoError(t, err) + assert.False(t, promoted) + assert.Equal(t, remote_service.ReasonBadAuth2, reason) + remoteSource.Type = auth_model.Remote + } + + { + promoted, reason, err := remote_service.MaybePromoteRemoteUser(ctx, gitlabSource, "unknownloginname", "") + assert.NoError(t, err) + assert.False(t, promoted) + assert.Equal(t, remote_service.ReasonLoginNameNotExists, reason) + } + + { + remoteUserID := "844" + remoteUser := &user_model.User{ + Name: "withmailuser", + Type: user_model.UserTypeRemoteUser, + LoginType: auth_model.Remote, + LoginSource: remoteSource.ID, + LoginName: remoteUserID, + Email: "some@example.com", + } + defer createUser(context.Background(), t, remoteUser)() + promoted, reason, err := remote_service.MaybePromoteRemoteUser(ctx, gitlabSource, remoteUserID, "") + assert.NoError(t, err) + assert.False(t, promoted) + assert.Equal(t, remote_service.ReasonEmailIsSet, reason) + } + + { + remoteUserID := "7464" + nonexistentloginsource := int64(4344) + remoteUser := &user_model.User{ + Name: "badsourceuser", + Type: user_model.UserTypeRemoteUser, + LoginType: auth_model.Remote, + LoginSource: nonexistentloginsource, + LoginName: remoteUserID, + } + defer createUser(context.Background(), t, remoteUser)() + promoted, reason, err := remote_service.MaybePromoteRemoteUser(ctx, gitlabSource, remoteUserID, "") + assert.NoError(t, err) + assert.False(t, promoted) + assert.Equal(t, remote_service.ReasonNoSource, reason) + } + + { + remoteUserID := "33335678" + remoteUser := &user_model.User{ + Name: "badremoteuser", + Type: user_model.UserTypeRemoteUser, + LoginType: auth_model.Remote, + LoginSource: gitlabSource.ID, + LoginName: remoteUserID, + } + defer createUser(context.Background(), t, remoteUser)() + promoted, reason, err := remote_service.MaybePromoteRemoteUser(ctx, gitlabSource, remoteUserID, "") + assert.NoError(t, err) + assert.False(t, promoted) + assert.Equal(t, remote_service.ReasonSourceWrongType, reason) + } + + { + unrelatedName := "unrelated" + unrelatedSource := addAuthSource(t, authSourcePayloadGitHubCustom(unrelatedName)) + assert.NotNil(t, unrelatedSource) + + remoteUserID := "488484" + remoteEmail := "4848484@example.com" + remoteUser := &user_model.User{ + Name: "unrelateduser", + Type: user_model.UserTypeRemoteUser, + LoginType: auth_model.Remote, + LoginSource: remoteSource.ID, + LoginName: remoteUserID, + } + defer createUser(context.Background(), t, remoteUser)() + promoted, reason, err := remote_service.MaybePromoteRemoteUser(ctx, unrelatedSource, remoteUserID, remoteEmail) + assert.NoError(t, err) + assert.False(t, promoted) + assert.Equal(t, remote_service.ReasonNoMatch, reason) + } + + { + remoteUserID := "5678" + remoteEmail := "gitlabuser@example.com" + remoteUser := &user_model.User{ + Name: "remoteuser", + Type: user_model.UserTypeRemoteUser, + LoginType: auth_model.Remote, + LoginSource: remoteSource.ID, + LoginName: remoteUserID, + } + defer createUser(context.Background(), t, remoteUser)() + promoted, reason, err := remote_service.MaybePromoteRemoteUser(ctx, gitlabSource, remoteUserID, remoteEmail) + assert.NoError(t, err) + assert.True(t, promoted) + assert.Equal(t, remote_service.ReasonPromoted, reason) + } +} diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go index 9a278e706d..b98fce0f4a 100644 --- a/tests/integration/repo_webhook_test.go +++ b/tests/integration/repo_webhook_test.go @@ -227,19 +227,19 @@ func TestWebhookForms(t *testing.T) { })) t.Run("matrix/required", testWebhookForms("matrix", session, map[string]string{ - "homeserver_url": "https://matrix.example.com", - "room_id": "123", - "authorization_header": "Bearer 123456", + "homeserver_url": "https://matrix.example.com", + "access_token": "123456", + "room_id": "123", }, map[string]string{ - "authorization_header": "", + "access_token": "", })) t.Run("matrix/optional", testWebhookForms("matrix", session, map[string]string{ "homeserver_url": "https://matrix.example.com", + "access_token": "123456", "room_id": "123", "message_type": "1", // m.text - "branch_filter": "matrix/*", - "authorization_header": "Bearer 123456", + "branch_filter": "matrix/*", })) t.Run("wechatwork/required", testWebhookForms("wechatwork", session, map[string]string{ @@ -267,14 +267,12 @@ func TestWebhookForms(t *testing.T) { })) t.Run("sourcehut_builds/required", testWebhookForms("sourcehut_builds", session, map[string]string{ - "payload_url": "https://sourcehut_builds.example.com", - "manifest_path": ".build.yml", - "visibility": "PRIVATE", - "authorization_header": "Bearer 123456", + "payload_url": "https://sourcehut_builds.example.com", + "manifest_path": ".build.yml", + "visibility": "PRIVATE", + "access_token": "123456", }, map[string]string{ - "authorization_header": "", - }, map[string]string{ - "authorization_header": "token ", + "access_token": "", }, map[string]string{ "manifest_path": "", }, map[string]string{ @@ -289,9 +287,9 @@ func TestWebhookForms(t *testing.T) { "manifest_path": ".build.yml", "visibility": "PRIVATE", "secrets": "on", + "access_token": "123456", - "branch_filter": "srht/*", - "authorization_header": "Bearer 123456", + "branch_filter": "srht/*", })) } diff --git a/tests/integration/size_translations_test.go b/tests/integration/size_translations_test.go index 78cd16795d..1ee5f7b36f 100644 --- a/tests/integration/size_translations_test.go +++ b/tests/integration/size_translations_test.go @@ -89,7 +89,7 @@ func TestDataSizeTranslation(t *testing.T) { fullSize = noDigits.ReplaceAllString(fullSize, "") assert.Equal(t, "git: КиБ; lfs: Б", fullSize) - // Check if file sizes are correclty translated + // Check if file sizes are correctly translated testFileSizeTranslated(t, session, path.Join(testUser, testRepoName, "src/branch/main/137byteFile.txt"), "137 Б") testFileSizeTranslated(t, session, path.Join(testUser, testRepoName, "src/branch/main/1.5kibFile.txt"), "1,5 КиБ") testFileSizeTranslated(t, session, path.Join(testUser, testRepoName, "src/branch/main/1.25mibFile.txt"), "1,3 МиБ") diff --git a/tests/integration/user_test.go b/tests/integration/user_test.go index 3a3932bdc2..02cc9b51cc 100644 --- a/tests/integration/user_test.go +++ b/tests/integration/user_test.go @@ -495,9 +495,7 @@ func TestUserPronouns(t *testing.T) { // Set the pronouns for user2 pronouns := "she/her" req := NewRequestWithJSON(t, "PATCH", "/api/v1/admin/users/user2", &api.EditUserOption{ - LoginName: "user2", - SourceID: 0, - Pronouns: &pronouns, + Pronouns: &pronouns, }).AddTokenAuth(adminToken) resp := MakeRequest(t, req, http.StatusOK) @@ -596,9 +594,7 @@ func TestUserPronouns(t *testing.T) { // Set the pronouns to Unspecified (an empty string) via the API pronouns := "" req := NewRequestWithJSON(t, "PATCH", "/api/v1/admin/users/user2", &api.EditUserOption{ - LoginName: "user2", - SourceID: 0, - Pronouns: &pronouns, + Pronouns: &pronouns, }).AddTokenAuth(adminToken) MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/webfinger_test.go b/tests/integration/webfinger_test.go index 55fb211779..825cffed7a 100644 --- a/tests/integration/webfinger_test.go +++ b/tests/integration/webfinger_test.go @@ -66,4 +66,19 @@ func TestWebfinger(t *testing.T) { req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=mailto:%s", user.Email)) MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=https://%s/%s/", appURL.Host, user.Name)) + session.MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=https://%s/%s", appURL.Host, user.Name)) + session.MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=http://%s/%s/foo", appURL.Host, user.Name)) + session.MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=http://%s", appURL.Host)) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=http://%s/%s/foo", "example.com", user.Name)) + MakeRequest(t, req, http.StatusBadRequest) } diff --git a/tests/mssql.ini.tmpl b/tests/mssql.ini.tmpl deleted file mode 100644 index 9346f75874..0000000000 --- a/tests/mssql.ini.tmpl +++ /dev/null @@ -1,107 +0,0 @@ -APP_NAME = Gitea: Git with a cup of tea -RUN_MODE = prod - -[database] -DB_TYPE = mssql -HOST = {{TEST_MSSQL_HOST}} -NAME = {{TEST_MSSQL_DBNAME}} -USER = {{TEST_MSSQL_USERNAME}} -PASSWD = {{TEST_MSSQL_PASSWORD}} -SSL_MODE = disable - -[indexer] -REPO_INDEXER_ENABLED = true -REPO_INDEXER_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/indexers/repos.bleve - -[queue.issue_indexer] -TYPE = level -DATADIR = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/indexers/issues.queue - -[queue] -TYPE = immediate - -[repository] -ROOT = {{REPO_TEST_DIR}}tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/gitea-repositories - -[repository.local] -LOCAL_COPY_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/tmp/local-repo - -[repository.upload] -TEMP_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/tmp/uploads - -[repository.signing] -SIGNING_KEY = none - -[server] -SSH_DOMAIN = localhost -HTTP_PORT = 3003 -ROOT_URL = http://localhost:3003/ -DISABLE_SSH = false -SSH_LISTEN_HOST = localhost -SSH_PORT = 2201 -START_SSH_SERVER = true -LFS_START_SERVER = true -OFFLINE_MODE = false -LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w -APP_DATA_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/data -BUILTIN_SSH_SERVER_USER = git -SSH_TRUSTED_USER_CA_KEYS = ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCb4DC1dMFnJ6pXWo7GMxTchtzmJHYzfN6sZ9FAPFR4ijMLfGki+olvOMO5Fql1/yGnGfbELQa1S6y4shSvj/5K+zUFScmEXYf3Gcr87RqilLkyk16RS+cHNB1u87xTHbETaa3nyCJeGQRpd4IQ4NKob745mwDZ7jQBH8AZEng50Oh8y8fi8skBBBzaYp1ilgvzG740L7uex6fHV62myq0SXeCa+oJUjq326FU8y+Vsa32H8A3e7tOgXZPdt2TVNltx2S9H2WO8RMi7LfaSwARNfy1zu+bfR50r6ef8Yx5YKCMz4wWb1SHU1GS800mjOjlInLQORYRNMlSwR1+vLlVDciOqFapDSbj+YOVOawR0R1aqlSKpZkt33DuOBPx9qe6CVnIi7Z+Px/KqM+OLCzlLY/RS+LbxQpDWcfTVRiP+S5qRTcE3M3UioN/e0BE/1+MpX90IGpvVkA63ILYbKEa4bM3ASL7ChTCr6xN5XT+GpVJveFKK1cfNx9ExHI4rzYE= - -[attachment] -PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/data/attachments - -[mailer] -ENABLED = true -PROTOCOL = dummy -FROM = mssql-{{TEST_TYPE}}-test@gitea.io - -[service] -REGISTER_EMAIL_CONFIRM = false -REGISTER_MANUAL_CONFIRM = false -DISABLE_REGISTRATION = false -ENABLE_CAPTCHA = false -REQUIRE_SIGNIN_VIEW = false -DEFAULT_KEEP_EMAIL_PRIVATE = false -DEFAULT_ALLOW_CREATE_ORGANIZATION = true -NO_REPLY_ADDRESS = noreply.example.org -ENABLE_NOTIFY_MAIL = true - -[picture] -DISABLE_GRAVATAR = false -ENABLE_FEDERATED_AVATAR = false -AVATAR_UPLOAD_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/data/avatars -REPOSITORY_AVATAR_UPLOAD_PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/data/repo-avatars - -[session] -PROVIDER = file -PROVIDER_CONFIG = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/data/sessions - -[log] -MODE = {{TEST_LOGGER}} -ROOT_PATH = {{REPO_TEST_DIR}}mssql-log -ENABLE_SSH_LOG = true -logger.xorm.MODE = file - -[log.test] -LEVEL = Info -COLORIZE = true - -[log.file] -LEVEL = Debug - -[security] -PASSWORD_HASH_ALGO = argon2 -DISABLE_GIT_HOOKS = false -INSTALL_LOCK = true -SECRET_KEY = 9pCviYTWSb -INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0OTU1NTE2MTh9.hhSVGOANkaKk3vfCd2jDOIww4pUk0xtg9JRde5UogyQ -DISABLE_QUERY_AUTH_TOKEN = true - -[lfs] -PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/data/lfs - -[packages] -ENABLED = true - -[actions] -ENABLED = true diff --git a/tests/test_utils.go b/tests/test_utils.go index 5837e1263c..a607194bef 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -165,18 +165,6 @@ func InitTest(requireGitea bool) { log.Fatal("db.Exec: CREATE SCHEMA: %v", err) } } - - case setting.Database.Type.IsMSSQL(): - host, port := setting.ParseMSSQLHostPort(setting.Database.Host) - db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", - host, port, "master", setting.Database.User, setting.Database.Passwd)) - if err != nil { - log.Fatal("sql.Open: %v", err) - } - if _, err := db.Exec(fmt.Sprintf("If(db_id(N'%s') IS NULL) BEGIN CREATE DATABASE %s; END;", setting.Database.Name, setting.Database.Name)); err != nil { - log.Fatal("db.Exec: %v", err) - } - defer db.Close() } routers.InitWebInstalled(graceful.GetManager().HammerContext()) diff --git a/web_src/css/base.css b/web_src/css/base.css index 0ab923594c..3d91586934 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -59,7 +59,7 @@ html, body { } body { - line-height: 1.4285rem; + line-height: 20px; font-family: var(--fonts-regular); color: var(--color-text); background-color: var(--color-body); @@ -246,6 +246,18 @@ h1.error-code { user-select: none; } +.top-right-buttons { + gap: 0.5rem; +} + +.top-right-buttons .ui.button { + margin-right: 0; +} + +.ui.partial.secondary.menu { + margin-bottom: 0; +} + a { color: var(--color-primary); cursor: pointer; @@ -331,61 +343,6 @@ a.label, background-color: var(--color-label-bg); } -/* fix Fomantic's line-height cutting off "g" on Windows Chrome with Segoe UI */ -.ui.input > input { - line-height: var(--line-height-default); - text-align: start; /* Override fomantic's `text-align: left` to make RTL work via HTML `dir="auto"` */ -} - -/* fix Fomantic's line-height causing vertical scrollbars to appear */ -ul.ui.list li, -ol.ui.list li, -.ui.list > .item, -.ui.list .list > .item { - line-height: var(--line-height-default); -} - -.ui.input.focus > input, -.ui.input > input:focus { - border-color: var(--color-primary); -} - -.ui.action.input .ui.ui.button { - border-color: var(--color-input-border); - padding-top: 0; /* the ".action.input" is "flex + stretch", so let the buttons layout themselves */ - padding-bottom: 0; -} - -/* currently used for search bar dropdowns in repo search and explore code */ -.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection { - min-width: 10em; -} -.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(:focus) { - border-right: none; -} -.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(.active):hover { - border-color: var(--color-input-border); -} -.ui.action.input:not([class*="left action"]) .ui.dropdown.selection.upward.visible { - border-bottom-left-radius: 0 !important; - border-bottom-right-radius: 0 !important; -} -.ui.action.input:not([class*="left action"]) > input, -.ui.action.input:not([class*="left action"]) > input:hover { - border-right: none; -} -.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection, -.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover, -.ui.action.input:not([class*="left action"]) > input:focus + .button, -.ui.action.input:not([class*="left action"]) > input:focus + .button:hover, -.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button, -.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button:hover { - border-left-color: var(--color-primary); -} -.ui.action.input:not([class*="left action"]) > input:focus { - border-right-color: var(--color-primary); -} - .ui.menu { display: flex; } @@ -530,21 +487,6 @@ ol.ui.list li, color: var(--color-text-light-2); } -.ui.list .list > .item .header, -.ui.list > .item .header { - color: var(--color-text-dark); -} - -.ui.list .list > .item > .content, -.ui.list > .item > .content { - color: var(--color-text); -} - -.ui.list .list > .item .description, -.ui.list > .item .description { - color: var(--color-text); -} - /* replace item margin on secondary menu items with gap and remove both the negative margins on the menu as well as margin on the items */ .ui.secondary.menu { @@ -663,10 +605,6 @@ img.ui.avatar, aspect-ratio: 1; } -.ui.divided.list > .item { - border-color: var(--color-secondary); -} - .ui.error.message .header, .ui.warning.message .header { color: inherit; @@ -919,14 +857,6 @@ input:-webkit-autofill:active, text-align: right !important; } -.ui .text.normal { - font-weight: var(--font-weight-normal); -} - -.ui .text.italic { - font-style: italic; -} - .ui .text.truncate { overflow-x: hidden; text-overflow: ellipsis; @@ -934,14 +864,6 @@ input:-webkit-autofill:active, display: inline-block; } -.ui .text.thin { - font-weight: var(--font-weight-normal); -} - -.ui .text.middle { - vertical-align: middle; -} - .ui .message.flash-message { text-align: center; } @@ -1477,11 +1399,6 @@ table th[data-sortt-desc] .svg { vertical-align: -0.15em; } -/* for the jquery.minicolors plugin */ -.minicolors-panel { - background: var(--color-secondary-dark-1) !important; -} - .ui.tabular.menu { border-color: var(--color-secondary); } @@ -1630,6 +1547,7 @@ table th[data-sortt-desc] .svg { align-items: center; gap: .25rem; vertical-align: middle; + min-width: 0; } .ui.ui.button { @@ -1645,19 +1563,10 @@ table th[data-sortt-desc] .svg { align-items: stretch; } -.ui.ui.icon.input .icon { - display: flex; - align-items: center; - justify-content: center; -} - -.ui.icon.input > i.icon { - transition: none; -} - .flex-items-block > .item, .flex-text-block { display: flex; align-items: center; gap: .25rem; + min-width: 0; } diff --git a/web_src/css/dashboard.css b/web_src/css/dashboard.css index 4bb9fa38bf..2ee2399d73 100644 --- a/web_src/css/dashboard.css +++ b/web_src/css/dashboard.css @@ -7,7 +7,6 @@ .dashboard.feeds .context.user.menu .ui.header, .dashboard.issues .context.user.menu .ui.header { font-size: 1rem; - text-transform: none; } .dashboard.feeds .filter.menu, diff --git a/web_src/css/features/colorpicker.css b/web_src/css/features/colorpicker.css new file mode 100644 index 0000000000..b7436783df --- /dev/null +++ b/web_src/css/features/colorpicker.css @@ -0,0 +1,47 @@ +.js-color-picker-input { + display: flex; + position: relative; +} + +.js-color-picker-input input { + padding-top: 8px !important; + padding-bottom: 8px !important; + padding-left: 32px !important; +} + +.js-color-picker-input .preview-square { + position: absolute; + aspect-ratio: 1; + height: 16px; + left: 10px; + top: 50%; + transform: translateY(-50%); + border-radius: 2px; + background: repeating-linear-gradient(45deg, #aaa 25%, transparent 25%, transparent 75%, #aaa 75%, #aaa), repeating-linear-gradient(45deg, #aaa 25%, #fff 25%, #fff 75%, #aaa 75%, #aaa); /* stylelint-disable-line scale-unlimited/declaration-strict-value */ + background-position: 0 0, 4px 4px; + background-size: 8px 8px; +} + +.js-color-picker-input .preview-square::after { + content: ""; + position: absolute; + width: 100%; + height: 100%; + border-radius: inherit; + background-color: currentcolor; +} + +hex-color-picker { + width: 180px; + height: 120px; +} + +hex-color-picker::part(hue-pointer), +hex-color-picker::part(saturation-pointer) { + width: 22px; + height: 22px; +} + +hex-color-picker::part(hue) { + flex-basis: 16px; +} diff --git a/web_src/css/features/projects.css b/web_src/css/features/projects.css index 30df994c38..e23c146748 100644 --- a/web_src/css/features/projects.css +++ b/web_src/css/features/projects.css @@ -22,34 +22,27 @@ cursor: default; } +.project-column .issue-card { + color: var(--color-text); +} + .project-column-header { display: flex; align-items: center; justify-content: space-between; } -.project-column-header.dark-label { - color: var(--color-project-board-dark-label) !important; -} - -.project-column-header.dark-label .project-column-title { - color: var(--color-project-board-dark-label) !important; -} - -.project-column-header.light-label { - color: var(--color-project-board-light-label) !important; -} - -.project-column-header.light-label .project-column-title { - color: var(--color-project-board-light-label) !important; -} - .project-column-title { background: none !important; line-height: 1.25 !important; cursor: inherit; } +.project-column-title, +.project-column-issue-count { + color: inherit !important; +} + .project-column > .cards { flex: 1; display: flex; @@ -64,6 +57,8 @@ .project-column > .divider { margin: 5px 0; + border-color: currentcolor; + opacity: .5; } .project-column:first-child { @@ -102,26 +97,3 @@ .card-ghost * { opacity: 0; } - -.color-field .minicolors.minicolors-theme-default { - display: block; -} - -.color-field .minicolors.minicolors-theme-default .minicolors-input { - height: 38px; - padding-left: 2rem; -} - -.color-field .minicolors.minicolors-theme-default .minicolors-swatch { - top: 10px; -} - -.edit-project-column-modal .color.picker.column, -.new-project-column-modal .color.picker.column { - display: flex; -} - -.edit-project-column-modal .color.picker.column .minicolors, -.new-project-column-modal .color.picker.column .minicolors { - flex: 1; -} diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css index 13962f19d7..118c058b19 100644 --- a/web_src/css/helpers.css +++ b/web_src/css/helpers.css @@ -63,3 +63,20 @@ only use: display: none !important; } } + +.tab-size-1 { tab-size: 1 !important; } +.tab-size-2 { tab-size: 2 !important; } +.tab-size-3 { tab-size: 3 !important; } +.tab-size-4 { tab-size: 4 !important; } +.tab-size-5 { tab-size: 5 !important; } +.tab-size-6 { tab-size: 6 !important; } +.tab-size-7 { tab-size: 7 !important; } +.tab-size-8 { tab-size: 8 !important; } +.tab-size-9 { tab-size: 9 !important; } +.tab-size-10 { tab-size: 10 !important; } +.tab-size-11 { tab-size: 11 !important; } +.tab-size-12 { tab-size: 12 !important; } +.tab-size-13 { tab-size: 13 !important; } +.tab-size-14 { tab-size: 14 !important; } +.tab-size-15 { tab-size: 15 !important; } +.tab-size-16 { tab-size: 16 !important; } diff --git a/web_src/css/index.css b/web_src/css/index.css index 445b20dc12..49ceb2c8ce 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -6,7 +6,9 @@ @import "./modules/container.css"; @import "./modules/divider.css"; @import "./modules/header.css"; +@import "./modules/input.css"; @import "./modules/label.css"; +@import "./modules/list.css"; @import "./modules/segment.css"; @import "./modules/grid.css"; @import "./modules/message.css"; diff --git a/web_src/css/modules/animations.css b/web_src/css/modules/animations.css index 0f78ad25cb..361618c449 100644 --- a/web_src/css/modules/animations.css +++ b/web_src/css/modules/animations.css @@ -34,10 +34,14 @@ border-radius: var(--border-radius-circle); } -.is-loading.small-loading-icon::after { +.is-loading.loading-icon-2px::after { border-width: 2px; } +.is-loading.loading-icon-3px::after { + border-width: 3px; +} + /* for single form button, the loading state should be on the button, but not go semi-transparent, just replace the text on the button with the loader. */ form.single-button-form.is-loading > * { opacity: 1; @@ -62,7 +66,7 @@ form.single-button-form.is-loading .button { background: transparent; } -/* TODO: not needed, use "is-loading small-loading-icon" instead */ +/* TODO: not needed, use "is-loading loading-icon-2px" instead */ code.language-math.is-loading::after { padding: 0; border-width: 2px; diff --git a/web_src/css/modules/divider.css b/web_src/css/modules/divider.css index 48560bd3d9..acc8408f37 100644 --- a/web_src/css/modules/divider.css +++ b/web_src/css/modules/divider.css @@ -2,12 +2,16 @@ margin: 10px 0; height: 0; font-weight: var(--font-weight-medium); - text-transform: uppercase; color: var(--color-text); font-size: 1rem; width: 100%; } +h4.divider { + margin-top: 1.25rem; + margin-bottom: 1.25rem; +} + .divider:not(.divider-text) { border-top: 1px solid var(--color-secondary); } diff --git a/web_src/css/modules/flexcontainer.css b/web_src/css/modules/flexcontainer.css index 1ca513687f..5d4e12cc12 100644 --- a/web_src/css/modules/flexcontainer.css +++ b/web_src/css/modules/flexcontainer.css @@ -6,10 +6,16 @@ margin-top: var(--page-spacing); } +/* small options menu on the left, used in settings/admin pages */ .flex-container-nav { width: 240px; } +/* wide sidebar on the right, used in frontpage */ +.flex-container-sidebar { + width: 35%; +} + .flex-container-main { flex: 1; min-width: 0; /* make the "text truncate" work, otherwise the flex axis is not limited and the text just overflows */ @@ -19,7 +25,9 @@ .flex-container { flex-direction: column; } - .flex-container-nav { + .flex-container-nav, + .flex-container-sidebar { + order: -1; width: auto; } } diff --git a/web_src/css/modules/header.css b/web_src/css/modules/header.css index 05381e1185..9cec5fcbe6 100644 --- a/web_src/css/modules/header.css +++ b/web_src/css/modules/header.css @@ -9,7 +9,6 @@ font-family: var(--fonts-regular); font-weight: var(--font-weight-medium); line-height: 1.28571429; - text-transform: none; } .ui.header:first-child { diff --git a/web_src/css/modules/input.css b/web_src/css/modules/input.css new file mode 100644 index 0000000000..18b785ac82 --- /dev/null +++ b/web_src/css/modules/input.css @@ -0,0 +1,197 @@ +/* based on Fomantic UI input module, with just the parts extracted that we use. If you find any + unused rules here after refactoring, please remove them. */ + +.ui.input { + position: relative; + font-weight: var(--font-weight-normal); + display: inline-flex; + color: var(--color-input-text); +} +.ui.input > input { + margin: 0; + max-width: 100%; + flex: 1 0 auto; + outline: none; + font-family: var(--fonts-regular); + padding: 0.67857143em 1em; + border: 1px solid var(--color-input-border); + color: var(--color-input-text); + border-radius: 0.28571429rem; + line-height: var(--line-height-default); + text-align: start; +} + +.ui.disabled.input, +.ui.input:not(.disabled) input[disabled] { + opacity: var(--opacity-disabled); +} +.ui.disabled.input > input, +.ui.input:not(.disabled) input[disabled] { + pointer-events: none; +} + +.ui.input.focus > input, +.ui.input > input:focus { + border-color: var(--color-primary); +} + +.ui.input.error > input { + background: var(--color-error-bg); + border-color: var(--color-error-border); + color: var(--color-error-text); +} + +.ui.icon.input > i.icon { + display: flex; + align-items: center; + justify-content: center; + cursor: default; + position: absolute; + text-align: center; + top: 0; + right: 0; + margin: 0; + height: 100%; + width: 2.67142857em; + opacity: 0.5; + border-radius: 0 0.28571429rem 0.28571429rem 0; + pointer-events: none; + padding: 4px; +} + +.ui.icon.input > i.icon.is-loading { + position: absolute !important; + height: 28px; + top: 4px; +} + +.ui.icon.input > i.icon.is-loading > * { + visibility: hidden; +} + +.ui.ui.ui.ui.icon.input > textarea, +.ui.ui.ui.ui.icon.input > input { + padding-right: 2.67142857em; +} +.ui.icon.input > i.link.icon { + cursor: pointer; +} +.ui.icon.input > i.circular.icon { + top: 0.35em; + right: 0.5em; +} + +.ui[class*="left icon"].input > i.icon { + right: auto; + left: 1px; + border-radius: 0.28571429rem 0 0 0.28571429rem; +} +.ui[class*="left icon"].input > i.circular.icon { + right: auto; + left: 0.5em; +} +.ui.ui.ui.ui[class*="left icon"].input > textarea, +.ui.ui.ui.ui[class*="left icon"].input > input { + padding-left: 2.67142857em; + padding-right: 1em; +} + +.ui.icon.input > textarea:focus ~ .icon, +.ui.icon.input > input:focus ~ .icon { + opacity: 1; +} + +.ui.icon.input > textarea ~ i.icon { + height: 3em; +} + +.ui.form .field.error > .ui.action.input > .ui.button, +.ui.action.input.error > .ui.button { + border-top: 1px solid var(--color-error-border); + border-bottom: 1px solid var(--color-error-border); +} + +.ui.action.input > .button, +.ui.action.input > .buttons { + display: flex; + align-items: center; + flex: 0 0 auto; +} +.ui.action.input > .button, +.ui.action.input > .buttons > .button { + padding-top: 0.78571429em; + padding-bottom: 0.78571429em; + margin: 0; +} + +.ui.action.input:not([class*="left action"]) > input { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right-color: transparent; +} + +.ui.action.input > .dropdown:first-child, +.ui.action.input > .button:first-child, +.ui.action.input > .buttons:first-child > .button { + border-radius: 0.28571429rem 0 0 0.28571429rem; +} +.ui.action.input > .dropdown:not(:first-child), +.ui.action.input > .button:not(:first-child), +.ui.action.input > .buttons:not(:first-child) > .button { + border-radius: 0; +} +.ui.action.input > .dropdown:last-child, +.ui.action.input > .button:last-child, +.ui.action.input > .buttons:last-child > .button { + border-radius: 0 0.28571429rem 0.28571429rem 0; +} + +.ui.fluid.input { + display: flex; +} +.ui.fluid.input > input { + width: 0 !important; +} + +.ui.tiny.input { + font-size: 0.85714286em; +} +.ui.small.input { + font-size: 0.92857143em; +} + +.ui.action.input .ui.ui.button { + border-color: var(--color-input-border); + padding-top: 0; /* the ".action.input" is "flex + stretch", so let the buttons layout themselves */ + padding-bottom: 0; +} + +/* currently used for search bar dropdowns in repo search and explore code */ +.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection { + min-width: 10em; +} +.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(:focus) { + border-right: none; +} +.ui.action.input:not([class*="left action"]) > .ui.dropdown.selection:not(.active):hover { + border-color: var(--color-input-border); +} +.ui.action.input:not([class*="left action"]) .ui.dropdown.selection.upward.visible { + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} +.ui.action.input:not([class*="left action"]) > input, +.ui.action.input:not([class*="left action"]) > input:hover { + border-right: none; +} +.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection, +.ui.action.input:not([class*="left action"]) > input:focus + .ui.dropdown.selection:hover, +.ui.action.input:not([class*="left action"]) > input:focus + .button, +.ui.action.input:not([class*="left action"]) > input:focus + .button:hover, +.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button, +.ui.action.input:not([class*="left action"]) > input:focus + .icon + .button:hover { + border-left-color: var(--color-primary); +} +.ui.action.input:not([class*="left action"]) > input:focus { + border-right-color: var(--color-primary); +} diff --git a/web_src/css/modules/label.css b/web_src/css/modules/label.css index 0512c5fddb..2032b2c84b 100644 --- a/web_src/css/modules/label.css +++ b/web_src/css/modules/label.css @@ -5,12 +5,12 @@ display: inline-flex; align-items: center; gap: .25rem; + min-width: 0; vertical-align: middle; line-height: 1; background: var(--color-label-bg); color: var(--color-label-text); padding: 0.3em 0.5em; - text-transform: none; font-size: 0.85714286rem; font-weight: var(--font-weight-medium); border: 0 solid transparent; diff --git a/web_src/css/modules/list.css b/web_src/css/modules/list.css new file mode 100644 index 0000000000..32c71e802b --- /dev/null +++ b/web_src/css/modules/list.css @@ -0,0 +1,193 @@ +/* based on Fomantic UI list module, with just the parts extracted that we use. If you find any + unused rules here after refactoring, please remove them. */ + +.ui.list { + list-style-type: none; + margin: 1em 0; + padding: 0; + font-size: 1em; +} + +.ui.list:first-child { + margin-top: 0; + padding-top: 0; +} + +.ui.list:last-child { + margin-bottom: 0; + padding-bottom: 0; +} + +.ui.list > .item, +.ui.list .list > .item { + display: list-item; + table-layout: fixed; + list-style-type: none; + list-style-position: outside; +} + +.ui.list > .list > .item::after, +.ui.list > .item::after { + content: ""; + display: block; + height: 0; + clear: both; + visibility: hidden; +} + +.ui.list .list:not(.icon) { + clear: both; + margin: 0; + padding: 0.75em 0 0.25em 0.5em; +} + +.ui.list .list > .item { + padding: 0.14285714em 0; +} + +.ui.list .list > .item > i.icon, +.ui.list > .item > i.icon { + display: table-cell; + min-width: 1.55em; + padding-top: 0; + transition: color 0.1s ease; + padding-right: 0.28571429em; + vertical-align: top; +} +.ui.list .list > .item > i.icon:only-child, +.ui.list > .item > i.icon:only-child { + display: inline-block; + min-width: auto; + vertical-align: top; +} + +.ui.list .list > .item > .image, +.ui.list > .item > .image { + display: table-cell; + background-color: transparent; + vertical-align: top; +} +.ui.list .list > .item > .image:not(:only-child):not(img), +.ui.list > .item > .image:not(:only-child):not(img) { + padding-right: 0.5em; +} +.ui.list .list > .item > .image img, +.ui.list > .item > .image img { + vertical-align: top; +} +.ui.list .list > .item > img.image, +.ui.list .list > .item > .image:only-child, +.ui.list > .item > img.image, +.ui.list > .item > .image:only-child { + display: inline-block; +} + +.ui.list .list > .item > .content, +.ui.list > .item > .content { + color: var(--color-text); +} +.ui.list .list > .item > .image + .content, +.ui.list .list > .item > i.icon + .content, +.ui.list > .item > .image + .content, +.ui.list > .item > i.icon + .content { + display: table-cell; + width: 100%; + padding: 0 0 0 0.5em; + vertical-align: top; +} +.ui.list .list > .item > img.image + .content, +.ui.list > .item > img.image + .content { + display: inline-block; + width: auto; +} +.ui.list .list > .item > .content > .list, +.ui.list > .item > .content > .list { + margin-left: 0; + padding-left: 0; +} + +.ui.list .list > .item .header, +.ui.list > .item .header { + display: block; + margin: 0; + font-family: var(--fonts-regular); + font-weight: var(--font-weight-medium); + color: var(--color-text-dark); +} + +.ui.list .list > .item .description, +.ui.list > .item .description { + display: block; + color: var(--color-text); +} + +.ui.list > .item a, +.ui.list .list > .item a { + cursor: pointer; +} + +.ui.list .list > .item [class*="right floated"], +.ui.list > .item [class*="right floated"] { + float: right; + margin: 0 0 0 1em; +} + +.ui.menu .ui.list > .item, +.ui.menu .ui.list .list > .item { + display: list-item; + table-layout: fixed; + background-color: transparent; + list-style-type: none; + list-style-position: outside; + padding: 0.21428571em 0; +} +.ui.menu .ui.list .list > .item::before, +.ui.menu .ui.list > .item::before { + border: none; + background: none; +} +.ui.menu .ui.list .list > .item:first-child, +.ui.menu .ui.list > .item:first-child { + padding-top: 0; +} +.ui.menu .ui.list .list > .item:last-child, +.ui.menu .ui.list > .item:last-child { + padding-bottom: 0; +} + +.ui.list .list > .disabled.item, +.ui.list > .disabled.item { + pointer-events: none; + opacity: var(--opacity-disabled); +} + +.ui.list .list > a.item:hover > .icons, +.ui.list > a.item:hover > .icons, +.ui.list .list > a.item:hover > i.icon, +.ui.list > a.item:hover > i.icon { + color: var(--color-text-dark); +} + +.ui.divided.list > .item { + border-top: 1px solid var(--color-secondary); +} +.ui.divided.list .list > .item { + border-top: none; +} +.ui.divided.list .item .list > .item { + border-top: none; +} +.ui.divided.list .list > .item:first-child, +.ui.divided.list > .item:first-child { + border-top: none; +} +.ui.divided.list .list > .item:first-child { + border-top-width: 1px; +} + +.ui.relaxed.list > .item:not(:first-child) { + padding-top: 0.42857143em; +} +.ui.relaxed.list > .item:not(:last-child) { + padding-bottom: 0.42857143em; +} diff --git a/web_src/css/modules/segment.css b/web_src/css/modules/segment.css index d4eaf633d7..04543f0986 100644 --- a/web_src/css/modules/segment.css +++ b/web_src/css/modules/segment.css @@ -153,6 +153,11 @@ border-top: none; } +.ui.attached.segment:has(+ .ui[class*="top attached"].header), +.ui.attached.segment:last-child { + border-radius: 0 0 0.28571429rem 0.28571429rem; +} + .ui[class*="top attached"].segment { bottom: 0; margin-bottom: 0; diff --git a/web_src/css/modules/tippy.css b/web_src/css/modules/tippy.css index 76d36b4293..6ac7c37d93 100644 --- a/web_src/css/modules/tippy.css +++ b/web_src/css/modules/tippy.css @@ -29,6 +29,17 @@ z-index: 1; } +/* bare theme, no styling at all, except box-shadow */ +.tippy-box[data-theme="bare"] { + border: none; + box-shadow: 0 6px 18px var(--color-shadow); +} + +.tippy-box[data-theme="bare"] .tippy-content { + padding: 0; + background: transparent; +} + /* tooltip theme for text tooltips */ .tippy-box[data-theme="tooltip"] { diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 0093cb3eb5..bf6bfd464b 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -402,7 +402,7 @@ td .commit-summary { .pdf-content { width: 100%; - height: 600px; + height: 100vh; border: none !important; display: flex; align-items: center; @@ -594,26 +594,18 @@ td .commit-summary { align-items: center; } -.repository.view.issue .issue-title-buttons, -.repository.view.issue .edit-buttons { +.repository.view.issue .top-right-buttons { display: flex; } -.issue-title-buttons { - gap: 0.5rem; -} - @media (max-width: 767.98px) { .repository.view.issue .issue-title { flex-direction: column; } - .repository.view.issue .issue-title-buttons, - .repository.view.issue .edit-buttons { + .repository.view.issue .top-right-buttons { width: 100%; - justify-content: space-between; - } - .repository.view.issue .edit-buttons { margin-top: .5rem; + justify-content: space-between; } .comment.form .issue-content-left .avatar { display: none; @@ -627,6 +619,10 @@ td .commit-summary { .comment.form .content .form::after { display: none; } + + .repository.view.issue .issue-title.edit-active h1 { + padding-right: 0; + } } .repository.view.issue .issue-title { @@ -644,7 +640,7 @@ td .commit-summary { font-size: 32px; line-height: 40px; margin: 0; - padding-right: 0.25rem; + padding-right: 0.5rem; min-height: var(--repo-header-issue-min-height); } @@ -839,55 +835,53 @@ td .commit-summary { margin-right: 0.25em; } -.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit { - line-height: 34px; /* this must be same as .badge height, to avoid overflow */ - clear: both; /* reset the "float right shabox", in the future, use flexbox instead */ +.singular-commit { + display: flex; + align-items: center; } -.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit > img.avatar, -.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit > .avatar img { - position: relative; - top: -2px; +.singular-commit .badge { + height: 30px !important; } -.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit .shabox .sha.label { +.singular-commit .shabox .sha.label { margin: 0; border: 1px solid var(--color-light-border); } -.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit .shabox .sha.label.isSigned.isWarning { +.singular-commit .shabox .sha.label.isSigned.isWarning { border: 1px solid var(--color-red-badge); background: var(--color-red-badge-bg); } -.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit .shabox .sha.label.isSigned.isWarning:hover { +.singular-commit .shabox .sha.label.isSigned.isWarning:hover { background: var(--color-red-badge-hover-bg) !important; } -.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit .shabox .sha.label.isSigned.isVerified { +.singular-commit .shabox .sha.label.isSigned.isVerified { border: 1px solid var(--color-green-badge); background: var(--color-green-badge-bg); } -.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit .shabox .sha.label.isSigned.isVerified:hover { +.singular-commit .shabox .sha.label.isSigned.isVerified:hover { background: var(--color-green-badge-hover-bg) !important; } -.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted { +.singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted { border: 1px solid var(--color-yellow-badge); background: var(--color-yellow-badge-bg); } -.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted:hover { +.singular-commit .shabox .sha.label.isSigned.isVerifiedUntrusted:hover { background: var(--color-yellow-badge-hover-bg) !important; } -.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched { +.singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched { border: 1px solid var(--color-orange-badge); background: var(--color-orange-badge-bg); } -.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched:hover { +.singular-commit .shabox .sha.label.isSigned.isVerifiedUnmatched:hover { background: var(--color-orange-badge-hover-bg) !important; } @@ -2345,107 +2339,11 @@ td .commit-summary { padding-top: 15px; } -.edit-label.modal .form .color.picker.column, -.new-label.modal .form .color.picker.column { - display: flex; -} - -.edit-label.modal .form .color.picker.column .minicolors, -.new-label.modal .form .color.picker.column .minicolors { - flex: 1; -} - -.edit-label.modal .form .minicolors-swatch.minicolors-sprite, -.new-label.modal .form .minicolors-swatch.minicolors-sprite { - top: 10px; - left: 10px; - width: 15px; - height: 15px; -} - -.tab-size-1 { - tab-size: 1 !important; - -moz-tab-size: 1 !important; -} - -.tab-size-2 { - tab-size: 2 !important; - -moz-tab-size: 2 !important; -} - -.tab-size-3 { - tab-size: 3 !important; - -moz-tab-size: 3 !important; -} - -.tab-size-4 { - tab-size: 4 !important; - -moz-tab-size: 4 !important; -} - -.tab-size-5 { - tab-size: 5 !important; - -moz-tab-size: 5 !important; -} - -.tab-size-6 { - tab-size: 6 !important; - -moz-tab-size: 6 !important; -} - -.tab-size-7 { - tab-size: 7 !important; - -moz-tab-size: 7 !important; -} - -.tab-size-8 { - tab-size: 8 !important; - -moz-tab-size: 8 !important; -} - -.tab-size-9 { - tab-size: 9 !important; - -moz-tab-size: 9 !important; -} - -.tab-size-10 { - tab-size: 10 !important; - -moz-tab-size: 10 !important; -} - -.tab-size-11 { - tab-size: 11 !important; - -moz-tab-size: 11 !important; -} - -.tab-size-12 { - tab-size: 12 !important; - -moz-tab-size: 12 !important; -} - -.tab-size-13 { - tab-size: 13 !important; - -moz-tab-size: 13 !important; -} - -.tab-size-14 { - tab-size: 14 !important; - -moz-tab-size: 14 !important; -} - -.tab-size-15 { - tab-size: 15 !important; - -moz-tab-size: 15 !important; -} - -.tab-size-16 { - tab-size: 16 !important; - -moz-tab-size: 16 !important; -} - .stats-table { display: table; width: 100%; + margin: 6px 0; + border-spacing: 2px; } .stats-table .table-cell { @@ -2453,11 +2351,34 @@ td .commit-summary { } .stats-table .table-cell.tiny { - height: 0.5em; + height: 8px; +} + +.stats-table .table-cell:first-child { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +.stats-table .table-cell:last-child { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.labels-list { + display: inline-flex; + flex-wrap: wrap; + gap: 2.5px; +} + +.labels-list a { + display: flex; + text-decoration: none; } .labels-list .label { - margin: 2px 0; + padding: 0 6px; + margin: 0 !important; + min-height: 20px; display: inline-flex !important; line-height: 1.3; /* there is a `font-size: 1.25em` for inside emoji, so here the line-height needs to be larger slightly */ } @@ -2607,6 +2528,7 @@ tbody.commit-list { #repo-topics .repo-topic { font-weight: var(--font-weight-normal); cursor: pointer; + margin: 0; } #new-dependency-drop-list.ui.selection.dropdown { @@ -2771,7 +2693,7 @@ tbody.commit-list { display: inline-block; background-color: var(--color-red); height: 12px; - width: 40px; + width: 44px; } .diff-stats-bar .diff-stats-add-bar { diff --git a/web_src/css/repo/issue-card.css b/web_src/css/repo/issue-card.css index b9368df4f6..609b1b3dbd 100644 --- a/web_src/css/repo/issue-card.css +++ b/web_src/css/repo/issue-card.css @@ -1,6 +1,7 @@ .issue-card { display: flex; flex-direction: column; + gap: 4px; align-items: start; border-radius: var(--border-radius); padding: 8px 10px; @@ -17,7 +18,6 @@ .issue-card-title { flex: 1; font-size: 14px; - margin-left: 4px; } .issue-card.sortable-chosen .issue-card-title { diff --git a/web_src/css/repo/issue-list.css b/web_src/css/repo/issue-list.css index d19421fcbc..37090f71b4 100644 --- a/web_src/css/repo/issue-list.css +++ b/web_src/css/repo/issue-list.css @@ -69,23 +69,6 @@ } } -#issue-list .flex-item-title .labels-list { - display: flex; - flex-wrap: wrap; - gap: 0.25em; -} - -#issue-list .flex-item-title .labels-list a { - display: flex; - text-decoration: none; -} - -#issue-list .flex-item-title .labels-list .label { - padding: 0 6px; - margin: 0; - min-height: 20px; -} - #issue-list .flex-item-body .branches { display: inline-flex; } diff --git a/web_src/css/themes/theme-gitea-dark.css b/web_src/css/themes/theme-gitea-dark.css index ed6718e40c..c74f334c2d 100644 --- a/web_src/css/themes/theme-gitea-dark.css +++ b/web_src/css/themes/theme-gitea-dark.css @@ -215,8 +215,6 @@ --color-placeholder-text: var(--color-text-light-3); --color-editor-line-highlight: var(--color-primary-light-5); --color-project-board-bg: var(--color-secondary-light-2); - --color-project-board-dark-label: #0e1011; - --color-project-board-light-label: #dde0e2; --color-caret: var(--color-text); /* should ideally be --color-text-dark, see #15651 */ --color-reaction-bg: #e8e8ff12; --color-reaction-hover-bg: var(--color-primary-light-4); diff --git a/web_src/css/themes/theme-gitea-light.css b/web_src/css/themes/theme-gitea-light.css index b10ad7d840..01dd8ba4f7 100644 --- a/web_src/css/themes/theme-gitea-light.css +++ b/web_src/css/themes/theme-gitea-light.css @@ -215,8 +215,6 @@ --color-placeholder-text: var(--color-text-light-3); --color-editor-line-highlight: var(--color-primary-light-6); --color-project-board-bg: var(--color-secondary-light-4); - --color-project-board-dark-label: #0e1114; - --color-project-board-light-label: #eaeef2; --color-caret: var(--color-text-dark); --color-reaction-bg: #0000170a; --color-reaction-hover-bg: var(--color-primary-light-5); diff --git a/web_src/fomantic/build/semantic.css b/web_src/fomantic/build/semantic.css index 525a3af8c6..49c00c4dad 100644 --- a/web_src/fomantic/build/semantic.css +++ b/web_src/fomantic/build/semantic.css @@ -6477,1727 +6477,6 @@ select.ui.dropdown { /******************************* Site Overrides *******************************/ -/*! - * # Fomantic-UI - Input - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/******************************* - Standard -*******************************/ - -/*-------------------- - Inputs ----------------------*/ - -.ui.input { - position: relative; - font-weight: normal; - font-style: normal; - display: inline-flex; - color: rgba(0, 0, 0, 0.87); -} - -.ui.input > input { - margin: 0; - max-width: 100%; - flex: 1 0 auto; - outline: none; - -webkit-tap-highlight-color: rgba(255, 255, 255, 0); - text-align: left; - line-height: 1.21428571em; - font-family: var(--fonts-regular); - padding: 0.67857143em 1em; - background: #FFFFFF; - border: 1px solid rgba(34, 36, 38, 0.15); - color: rgba(0, 0, 0, 0.87); - border-radius: 0.28571429rem; - transition: box-shadow 0.1s ease, border-color 0.1s ease; - box-shadow: none; -} - -/*-------------------- - Placeholder ----------------------*/ - -/* browsers require these rules separate */ - -.ui.input > input::-webkit-input-placeholder { - color: rgba(191, 191, 191, 0.87); -} - -.ui.input > input::-moz-placeholder { - color: rgba(191, 191, 191, 0.87); -} - -.ui.input > input:-ms-input-placeholder { - color: rgba(191, 191, 191, 0.87); -} - -/******************************* - States -*******************************/ - -/*-------------------- - Disabled - ---------------------*/ - -.ui.disabled.input, -.ui.input:not(.disabled) input[disabled] { - opacity: var(--opacity-disabled); -} - -.ui.disabled.input > input, -.ui.input:not(.disabled) input[disabled] { - pointer-events: none; -} - -/*-------------------- - Active ----------------------*/ - -.ui.input > input:active, -.ui.input.down input { - border-color: rgba(0, 0, 0, 0.3); - background: #FAFAFA; - color: rgba(0, 0, 0, 0.87); - box-shadow: none; -} - -/*-------------------- - Loading - ---------------------*/ - -.ui.loading.loading.input > i.icon:before { - position: absolute; - content: ''; - top: 50%; - left: 50%; - margin: -0.64285714em 0 0 -0.64285714em; - width: 1.28571429em; - height: 1.28571429em; - border-radius: 500rem; - border: 0.2em solid rgba(0, 0, 0, 0.1); -} - -.ui.loading.loading.input > i.icon:after { - position: absolute; - content: ''; - top: 50%; - left: 50%; - margin: -0.64285714em 0 0 -0.64285714em; - width: 1.28571429em; - height: 1.28571429em; - animation: loader 0.6s infinite linear; - border: 0.2em solid #767676; - border-radius: 500rem; - box-shadow: 0 0 0 1px transparent; -} - -/*-------------------- - Focus ----------------------*/ - -.ui.input.focus > input, -.ui.input > input:focus { - border-color: #85B7D9; - background: #FFFFFF; - color: rgba(0, 0, 0, 0.8); - box-shadow: none; -} - -.ui.input.focus > input::-webkit-input-placeholder, -.ui.input > input:focus::-webkit-input-placeholder { - color: rgba(115, 115, 115, 0.87); -} - -.ui.input.focus > input::-moz-placeholder, -.ui.input > input:focus::-moz-placeholder { - color: rgba(115, 115, 115, 0.87); -} - -.ui.input.focus > input:-ms-input-placeholder, -.ui.input > input:focus:-ms-input-placeholder { - color: rgba(115, 115, 115, 0.87); -} - -/*-------------------- - States - ---------------------*/ - -.ui.input.error > input { - background-color: #FFF6F6; - border-color: #E0B4B4; - color: #9F3A38; - box-shadow: none; -} - -/* Placeholder */ - -.ui.input.error > input::-webkit-input-placeholder { - color: #e7bdbc; -} - -.ui.input.error > input::-moz-placeholder { - color: #e7bdbc; -} - -.ui.input.error > input:-ms-input-placeholder { - color: #e7bdbc !important; -} - -/* Focused Placeholder */ - -.ui.input.error > input:focus::-webkit-input-placeholder { - color: #da9796; -} - -.ui.input.error > input:focus::-moz-placeholder { - color: #da9796; -} - -.ui.input.error > input:focus:-ms-input-placeholder { - color: #da9796 !important; -} - -.ui.input.info > input { - background-color: #F8FFFF; - border-color: #A9D5DE; - color: #276F86; - box-shadow: none; -} - -/* Placeholder */ - -.ui.input.info > input::-webkit-input-placeholder { - color: #98cfe1; -} - -.ui.input.info > input::-moz-placeholder { - color: #98cfe1; -} - -.ui.input.info > input:-ms-input-placeholder { - color: #98cfe1 !important; -} - -/* Focused Placeholder */ - -.ui.input.info > input:focus::-webkit-input-placeholder { - color: #70bdd6; -} - -.ui.input.info > input:focus::-moz-placeholder { - color: #70bdd6; -} - -.ui.input.info > input:focus:-ms-input-placeholder { - color: #70bdd6 !important; -} - -.ui.input.success > input { - background-color: #FCFFF5; - border-color: #A3C293; - color: #2C662D; - box-shadow: none; -} - -/* Placeholder */ - -.ui.input.success > input::-webkit-input-placeholder { - color: #8fcf90; -} - -.ui.input.success > input::-moz-placeholder { - color: #8fcf90; -} - -.ui.input.success > input:-ms-input-placeholder { - color: #8fcf90 !important; -} - -/* Focused Placeholder */ - -.ui.input.success > input:focus::-webkit-input-placeholder { - color: #6cbf6d; -} - -.ui.input.success > input:focus::-moz-placeholder { - color: #6cbf6d; -} - -.ui.input.success > input:focus:-ms-input-placeholder { - color: #6cbf6d !important; -} - -.ui.input.warning > input { - background-color: #FFFAF3; - border-color: #C9BA9B; - color: #573A08; - box-shadow: none; -} - -/* Placeholder */ - -.ui.input.warning > input::-webkit-input-placeholder { - color: #edad3e; -} - -.ui.input.warning > input::-moz-placeholder { - color: #edad3e; -} - -.ui.input.warning > input:-ms-input-placeholder { - color: #edad3e !important; -} - -/* Focused Placeholder */ - -.ui.input.warning > input:focus::-webkit-input-placeholder { - color: #e39715; -} - -.ui.input.warning > input:focus::-moz-placeholder { - color: #e39715; -} - -.ui.input.warning > input:focus:-ms-input-placeholder { - color: #e39715 !important; -} - -/******************************* - Variations -*******************************/ - -/*-------------------- - Transparent - ---------------------*/ - -.ui.transparent.input > textarea, -.ui.transparent.input > input { - border-color: transparent !important; - background-color: transparent !important; - padding: 0; - box-shadow: none !important; - border-radius: 0 !important; -} - -.field .ui.transparent.input > textarea { - padding: 0.67857143em 1em; -} - -/* Transparent Icon */ - -:not(.field) > .ui.transparent.icon.input > i.icon { - width: 1.1em; -} - -:not(.field) > .ui.ui.ui.transparent.icon.input > input { - padding-left: 0; - padding-right: 2em; -} - -:not(.field) > .ui.ui.ui.transparent[class*="left icon"].input > input { - padding-left: 2em; - padding-right: 0; -} - -/*-------------------- - Icon - ---------------------*/ - -.ui.icon.input > i.icon { - cursor: default; - position: absolute; - line-height: 1; - text-align: center; - top: 0; - right: 0; - margin: 0; - height: 100%; - width: 2.67142857em; - opacity: 0.5; - border-radius: 0 0.28571429rem 0.28571429rem 0; - transition: opacity 0.3s ease; -} - -.ui.icon.input > i.icon:not(.link) { - pointer-events: none; -} - -.ui.ui.ui.ui.icon.input > textarea, -.ui.ui.ui.ui.icon.input > input { - padding-right: 2.67142857em; -} - -.ui.icon.input > i.icon:before, -.ui.icon.input > i.icon:after { - left: 0; - position: absolute; - text-align: center; - top: 50%; - width: 100%; - margin-top: -0.5em; -} - -.ui.icon.input > i.link.icon { - cursor: pointer; -} - -.ui.icon.input > i.circular.icon { - top: 0.35em; - right: 0.5em; -} - -/* Left Icon Input */ - -.ui[class*="left icon"].input > i.icon { - right: auto; - left: 1px; - border-radius: 0.28571429rem 0 0 0.28571429rem; -} - -.ui[class*="left icon"].input > i.circular.icon { - right: auto; - left: 0.5em; -} - -.ui.ui.ui.ui[class*="left icon"].input > textarea, -.ui.ui.ui.ui[class*="left icon"].input > input { - padding-left: 2.67142857em; - padding-right: 1em; -} - -/* Focus */ - -.ui.icon.input > textarea:focus ~ i.icon, -.ui.icon.input > input:focus ~ i.icon { - opacity: 1; -} - -/*-------------------- - Labeled - ---------------------*/ - -/* Adjacent Label */ - -.ui.labeled.input > .label { - flex: 0 0 auto; - margin: 0; - font-size: 1em; -} - -.ui.labeled.input > .label:not(.corner) { - padding-top: 0.78571429em; - padding-bottom: 0.78571429em; -} - -/* Regular Label on Left */ - -.ui.labeled.input:not([class*="corner labeled"]) .label:first-child { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.ui.labeled.input:not([class*="corner labeled"]) .label:first-child + input { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left-color: transparent; -} - -.ui.labeled.input:not([class*="corner labeled"]) .label:first-child + input:focus { - border-left-color: #85B7D9; -} - -/* Regular Label on Right */ - -.ui[class*="right labeled"].input > input { - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; - border-right-color: transparent !important; -} - -.ui[class*="right labeled"].input > input + .label { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.ui[class*="right labeled"].input > input:focus { - border-right-color: #85B7D9 !important; -} - -/* Corner Label */ - -.ui.labeled.input .corner.label { - top: 1px; - right: 1px; - font-size: 0.64285714em; - border-radius: 0 0.28571429rem 0 0; -} - -/* Spacing with corner label */ - -.ui[class*="corner labeled"]:not([class*="left corner labeled"]).labeled.input > textarea, -.ui[class*="corner labeled"]:not([class*="left corner labeled"]).labeled.input > input { - padding-right: 2.5em !important; -} - -.ui[class*="corner labeled"].icon.input:not([class*="left corner labeled"]) > textarea, -.ui[class*="corner labeled"].icon.input:not([class*="left corner labeled"]) > input { - padding-right: 3.25em !important; -} - -.ui[class*="corner labeled"].icon.input:not([class*="left corner labeled"]) > i.icon { - margin-right: 1.25em; -} - -/* Left Labeled */ - -.ui[class*="left corner labeled"].labeled.input > textarea, -.ui[class*="left corner labeled"].labeled.input > input { - padding-left: 2.5em !important; -} - -.ui[class*="left corner labeled"].icon.input > textarea, -.ui[class*="left corner labeled"].icon.input > input { - padding-left: 3.25em !important; -} - -.ui[class*="left corner labeled"].icon.input > i.icon { - margin-left: 1.25em; -} - -.ui.icon.input > textarea ~ i.icon { - height: 3em; -} - -:not(.field) > .ui.transparent.icon.input > textarea ~ i.icon { - height: 1.3em; -} - -/* Corner Label Position */ - -.ui.input > .ui.corner.label { - top: 1px; - right: 1px; -} - -.ui.input > .ui.left.corner.label { - right: auto; - left: 1px; -} - -/* Labeled and action input states */ - -.ui.form .field.error > .ui.action.input > .ui.button, -.ui.form .field.error > .ui.labeled.input:not([class*="corner labeled"]) > .ui.label, -.ui.action.input.error > .ui.button, -.ui.labeled.input.error:not([class*="corner labeled"]) > .ui.label { - border-top: 1px solid #E0B4B4; - border-bottom: 1px solid #E0B4B4; -} - -.ui.form .field.error > .ui[class*="left action"].input > .ui.button, -.ui.form .field.error > .ui.labeled.input:not(.right):not([class*="corner labeled"]) > .ui.label, -.ui[class*="left action"].input.error > .ui.button, -.ui.labeled.input.error:not(.right):not([class*="corner labeled"]) > .ui.label { - border-left: 1px solid #E0B4B4; -} - -.ui.form .field.error > .ui.action.input:not([class*="left action"]) > input + .ui.button, -.ui.form .field.error > .ui.right.labeled.input:not([class*="corner labeled"]) > input + .ui.label, -.ui.action.input.error:not([class*="left action"]) > input + .ui.button, -.ui.right.labeled.input.error:not([class*="corner labeled"]) > input + .ui.label { - border-right: 1px solid #E0B4B4; -} - -.ui.form .field.error > .ui.right.labeled.input:not([class*="corner labeled"]) > .ui.label:first-child, -.ui.right.labeled.input.error:not([class*="corner labeled"]) > .ui.label:first-child { - border-left: 1px solid #E0B4B4; -} - -.ui.form .field.info > .ui.action.input > .ui.button, -.ui.form .field.info > .ui.labeled.input:not([class*="corner labeled"]) > .ui.label, -.ui.action.input.info > .ui.button, -.ui.labeled.input.info:not([class*="corner labeled"]) > .ui.label { - border-top: 1px solid #A9D5DE; - border-bottom: 1px solid #A9D5DE; -} - -.ui.form .field.info > .ui[class*="left action"].input > .ui.button, -.ui.form .field.info > .ui.labeled.input:not(.right):not([class*="corner labeled"]) > .ui.label, -.ui[class*="left action"].input.info > .ui.button, -.ui.labeled.input.info:not(.right):not([class*="corner labeled"]) > .ui.label { - border-left: 1px solid #A9D5DE; -} - -.ui.form .field.info > .ui.action.input:not([class*="left action"]) > input + .ui.button, -.ui.form .field.info > .ui.right.labeled.input:not([class*="corner labeled"]) > input + .ui.label, -.ui.action.input.info:not([class*="left action"]) > input + .ui.button, -.ui.right.labeled.input.info:not([class*="corner labeled"]) > input + .ui.label { - border-right: 1px solid #A9D5DE; -} - -.ui.form .field.info > .ui.right.labeled.input:not([class*="corner labeled"]) > .ui.label:first-child, -.ui.right.labeled.input.info:not([class*="corner labeled"]) > .ui.label:first-child { - border-left: 1px solid #A9D5DE; -} - -.ui.form .field.success > .ui.action.input > .ui.button, -.ui.form .field.success > .ui.labeled.input:not([class*="corner labeled"]) > .ui.label, -.ui.action.input.success > .ui.button, -.ui.labeled.input.success:not([class*="corner labeled"]) > .ui.label { - border-top: 1px solid #A3C293; - border-bottom: 1px solid #A3C293; -} - -.ui.form .field.success > .ui[class*="left action"].input > .ui.button, -.ui.form .field.success > .ui.labeled.input:not(.right):not([class*="corner labeled"]) > .ui.label, -.ui[class*="left action"].input.success > .ui.button, -.ui.labeled.input.success:not(.right):not([class*="corner labeled"]) > .ui.label { - border-left: 1px solid #A3C293; -} - -.ui.form .field.success > .ui.action.input:not([class*="left action"]) > input + .ui.button, -.ui.form .field.success > .ui.right.labeled.input:not([class*="corner labeled"]) > input + .ui.label, -.ui.action.input.success:not([class*="left action"]) > input + .ui.button, -.ui.right.labeled.input.success:not([class*="corner labeled"]) > input + .ui.label { - border-right: 1px solid #A3C293; -} - -.ui.form .field.success > .ui.right.labeled.input:not([class*="corner labeled"]) > .ui.label:first-child, -.ui.right.labeled.input.success:not([class*="corner labeled"]) > .ui.label:first-child { - border-left: 1px solid #A3C293; -} - -.ui.form .field.warning > .ui.action.input > .ui.button, -.ui.form .field.warning > .ui.labeled.input:not([class*="corner labeled"]) > .ui.label, -.ui.action.input.warning > .ui.button, -.ui.labeled.input.warning:not([class*="corner labeled"]) > .ui.label { - border-top: 1px solid #C9BA9B; - border-bottom: 1px solid #C9BA9B; -} - -.ui.form .field.warning > .ui[class*="left action"].input > .ui.button, -.ui.form .field.warning > .ui.labeled.input:not(.right):not([class*="corner labeled"]) > .ui.label, -.ui[class*="left action"].input.warning > .ui.button, -.ui.labeled.input.warning:not(.right):not([class*="corner labeled"]) > .ui.label { - border-left: 1px solid #C9BA9B; -} - -.ui.form .field.warning > .ui.action.input:not([class*="left action"]) > input + .ui.button, -.ui.form .field.warning > .ui.right.labeled.input:not([class*="corner labeled"]) > input + .ui.label, -.ui.action.input.warning:not([class*="left action"]) > input + .ui.button, -.ui.right.labeled.input.warning:not([class*="corner labeled"]) > input + .ui.label { - border-right: 1px solid #C9BA9B; -} - -.ui.form .field.warning > .ui.right.labeled.input:not([class*="corner labeled"]) > .ui.label:first-child, -.ui.right.labeled.input.warning:not([class*="corner labeled"]) > .ui.label:first-child { - border-left: 1px solid #C9BA9B; -} - -/*-------------------- - Action - ---------------------*/ - -.ui.action.input > .button, -.ui.action.input > .buttons { - display: flex; - align-items: center; - flex: 0 0 auto; -} - -.ui.action.input > .button, -.ui.action.input > .buttons > .button { - padding-top: 0.78571429em; - padding-bottom: 0.78571429em; - margin: 0; -} - -/* Input when ui Left*/ - -.ui[class*="left action"].input > input { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left-color: transparent; -} - -/* Input when ui Right*/ - -.ui.action.input:not([class*="left action"]) > input { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-right-color: transparent; -} - -/* Button and Dropdown */ - -.ui.action.input > .dropdown:first-child, -.ui.action.input > .button:first-child, -.ui.action.input > .buttons:first-child > .button { - border-radius: 0.28571429rem 0 0 0.28571429rem; -} - -.ui.action.input > .dropdown:not(:first-child), -.ui.action.input > .button:not(:first-child), -.ui.action.input > .buttons:not(:first-child) > .button { - border-radius: 0; -} - -.ui.action.input > .dropdown:last-child, -.ui.action.input > .button:last-child, -.ui.action.input > .buttons:last-child > .button { - border-radius: 0 0.28571429rem 0.28571429rem 0; -} - -/* Input Focus */ - -.ui.action.input:not([class*="left action"]) > input:focus { - border-right-color: #85B7D9; -} - -.ui.ui[class*="left action"].input > input:focus { - border-left-color: #85B7D9; -} - -/*-------------------- - Fluid - ---------------------*/ - -.ui.fluid.input { - display: flex; -} - -.ui.fluid.input > input { - width: 0 !important; -} - -/*-------------------- - Size ----------------------*/ - -.ui.input { - font-size: 1em; -} - -.ui.mini.input { - font-size: 0.78571429em; -} - -.ui.tiny.input { - font-size: 0.85714286em; -} - -.ui.small.input { - font-size: 0.92857143em; -} - -.ui.large.input { - font-size: 1.14285714em; -} - -.ui.big.input { - font-size: 1.28571429em; -} - -.ui.huge.input { - font-size: 1.42857143em; -} - -.ui.massive.input { - font-size: 1.71428571em; -} - -/******************************* - Theme Overrides -*******************************/ - -/******************************* - Site Overrides -*******************************/ -/*! - * # Fomantic-UI - List - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/******************************* - List -*******************************/ - -ul.ui.list, -ol.ui.list, -.ui.list { - list-style-type: none; - margin: 1em 0; - padding: 0 0; -} - -ul.ui.list:first-child, -ol.ui.list:first-child, -.ui.list:first-child { - margin-top: 0; - padding-top: 0; -} - -ul.ui.list:last-child, -ol.ui.list:last-child, -.ui.list:last-child { - margin-bottom: 0; - padding-bottom: 0; -} - -/******************************* - Content -*******************************/ - -/* List Item */ - -ul.ui.list li, -ol.ui.list li, -.ui.list > .item, -.ui.list .list > .item { - display: list-item; - table-layout: fixed; - list-style-type: none; - list-style-position: outside; - padding: 0.21428571em 0; - line-height: 1.14285714em; -} - -ul.ui.list > li:first-child:after, -ol.ui.list > li:first-child:after, -.ui.list > .list > .item:after, -.ui.list > .item:after { - content: ''; - display: block; - height: 0; - clear: both; - visibility: hidden; -} - -ul.ui.list li:first-child, -ol.ui.list li:first-child, -.ui.list .list > .item:first-child, -.ui.list > .item:first-child { - padding-top: 0; -} - -ul.ui.list li:last-child, -ol.ui.list li:last-child, -.ui.list .list > .item:last-child, -.ui.list > .item:last-child { - padding-bottom: 0; -} - -/* Child List */ - -ul.ui.list ul, -ol.ui.list ol, -.ui.list .list:not(.icon) { - clear: both; - margin: 0; - padding: 0.75em 0 0.25em 0.5em; -} - -/* Child Item */ - -ul.ui.list ul li, -ol.ui.list ol li, -.ui.list .list > .item { - padding: 0.14285714em 0; - line-height: inherit; -} - -/* Icon */ - -.ui.list .list > .item > i.icon, -.ui.list > .item > i.icon { - display: table-cell; - min-width: 1.55em; - margin: 0; - padding-top: 0; - transition: color 0.1s ease; -} - -.ui.list .list > .item > i.icon:not(.loading), -.ui.list > .item > i.icon:not(.loading) { - padding-right: 0.28571429em; - vertical-align: top; -} - -.ui.list .list > .item > i.icon:only-child, -.ui.list > .item > i.icon:only-child { - display: inline-block; - min-width: auto; - vertical-align: top; -} - -/* Image */ - -.ui.list .list > .item > .image, -.ui.list > .item > .image { - display: table-cell; - background-color: transparent; - margin: 0; - vertical-align: top; -} - -.ui.list .list > .item > .image:not(:only-child):not(img), -.ui.list > .item > .image:not(:only-child):not(img) { - padding-right: 0.5em; -} - -.ui.list .list > .item > .image img, -.ui.list > .item > .image img { - vertical-align: top; -} - -.ui.list .list > .item > img.image, -.ui.list .list > .item > .image:only-child, -.ui.list > .item > img.image, -.ui.list > .item > .image:only-child { - display: inline-block; -} - -/* Content */ - -.ui.list .list > .item > .content, -.ui.list > .item > .content { - line-height: 1.14285714em; - color: rgba(0, 0, 0, 0.87); -} - -.ui.list .list > .item > .image + .content, -.ui.list .list > .item > i.icon + .content, -.ui.list > .item > .image + .content, -.ui.list > .item > i.icon + .content { - display: table-cell; - width: 100%; - padding: 0 0 0 0.5em; - vertical-align: top; -} - -.ui.list .list > .item > i.loading.icon + .content, -.ui.list > .item > i.loading.icon + .content { - padding-left: calc(0.2857142857142857em + 0.5em); -} - -.ui.list .list > .item > img.image + .content, -.ui.list > .item > img.image + .content { - display: inline-block; - width: auto; -} - -.ui.list .list > .item > .content > .list, -.ui.list > .item > .content > .list { - margin-left: 0; - padding-left: 0; -} - -/* Header */ - -.ui.list .list > .item .header, -.ui.list > .item .header { - display: block; - margin: 0; - font-family: var(--fonts-regular); - font-weight: 500; - color: rgba(0, 0, 0, 0.87); -} - -/* Description */ - -.ui.list .list > .item .description, -.ui.list > .item .description { - display: block; - color: rgba(0, 0, 0, 0.7); -} - -/* Child Link */ - -.ui.list > .item a, -.ui.list .list > .item a { - cursor: pointer; -} - -/* Linking Item */ - -.ui.list .list > a.item, -.ui.list > a.item { - cursor: pointer; - color: #4183C4; -} - -.ui.list .list > a.item:hover, -.ui.list > a.item:hover { - color: #1e70bf; -} - -/* Linked Item Icons */ - -.ui.list .list > a.item > i.icons, -.ui.list > a.item > i.icons, -.ui.list .list > a.item > i.icon, -.ui.list > a.item > i.icon { - color: rgba(0, 0, 0, 0.4); -} - -/* Header Link */ - -.ui.list .list > .item a.header, -.ui.list > .item a.header { - cursor: pointer; - color: #4183C4 !important; -} - -.ui.list .list > .item > a.header:hover, -.ui.list > .item > a.header:hover { - color: #1e70bf !important; -} - -/* Floated Content */ - -.ui[class*="left floated"].list { - float: left; -} - -.ui[class*="right floated"].list { - float: right; -} - -.ui.list .list > .item [class*="left floated"], -.ui.list > .item [class*="left floated"] { - float: left; - margin: 0 1em 0 0; -} - -.ui.list .list > .item [class*="right floated"], -.ui.list > .item [class*="right floated"] { - float: right; - margin: 0 0 0 1em; -} - -/******************************* - Coupling -*******************************/ - -.ui.menu .ui.list > .item, -.ui.menu .ui.list .list > .item { - display: list-item; - table-layout: fixed; - background-color: transparent; - list-style-type: none; - list-style-position: outside; - padding: 0.21428571em 0; - line-height: 1.14285714em; -} - -.ui.menu .ui.list .list > .item:before, -.ui.menu .ui.list > .item:before { - border: none; - background: none; -} - -.ui.menu .ui.list .list > .item:first-child, -.ui.menu .ui.list > .item:first-child { - padding-top: 0; -} - -.ui.menu .ui.list .list > .item:last-child, -.ui.menu .ui.list > .item:last-child { - padding-bottom: 0; -} - -/******************************* - Types -*******************************/ - -/*------------------- - Horizontal - --------------------*/ - -.ui.horizontal.list { - display: inline-block; - font-size: 0; -} - -.ui.horizontal.list > .item { - display: inline-block; - margin-right: 1em; - font-size: 1rem; -} - -.ui.horizontal.list:not(.celled) > .item:last-child { - margin-right: 0; - padding-right: 0; -} - -.ui.horizontal.list .list:not(.icon) { - padding-left: 0; - padding-bottom: 0; -} - -.ui.horizontal.list > .item > .image, -.ui.horizontal.list .list > .item > .image, -.ui.horizontal.list > .item > i.icon, -.ui.horizontal.list .list > .item > i.icon, -.ui.horizontal.list > .item > .content, -.ui.horizontal.list .list > .item > .content { - vertical-align: middle; -} - -/* Padding on all elements */ - -.ui.horizontal.list > .item:first-child, -.ui.horizontal.list > .item:last-child { - padding-top: 0.21428571em; - padding-bottom: 0.21428571em; -} - -/* Horizontal List */ - -.ui.horizontal.list > .item > i.icon, -.ui.horizontal.list .item > i.icons > i.icon { - margin: 0; - padding: 0 0.25em 0 0; -} - -.ui.horizontal.list > .item > .image + .content, -.ui.horizontal.list > .item > i.icon, -.ui.horizontal.list > .item > i.icon + .content { - float: none; - display: inline-block; - width: auto; -} - -.ui.horizontal.list > .item > .image { - display: inline-block; -} - -/******************************* - States -*******************************/ - -/*------------------- - Disabled - --------------------*/ - -.ui.list .list > .disabled.item, -.ui.list > .disabled.item { - pointer-events: none; - color: rgba(40, 40, 40, 0.3) !important; -} - -/*------------------- - Hover ---------------------*/ - -.ui.list .list > a.item:hover > .icons, -.ui.list > a.item:hover > .icons, -.ui.list .list > a.item:hover > i.icon, -.ui.list > a.item:hover > i.icon { - color: rgba(0, 0, 0, 0.87); -} - -/******************************* - Variations -*******************************/ - -/*------------------- - Aligned - --------------------*/ - -.ui.list[class*="top aligned"] .image, -.ui.list[class*="top aligned"] .content, -.ui.list [class*="top aligned"] { - vertical-align: top !important; -} - -.ui.list[class*="middle aligned"] .image, -.ui.list[class*="middle aligned"] .content, -.ui.list [class*="middle aligned"] { - vertical-align: middle !important; -} - -.ui.list[class*="bottom aligned"] .image, -.ui.list[class*="bottom aligned"] .content, -.ui.list [class*="bottom aligned"] { - vertical-align: bottom !important; -} - -/*------------------- - Link - --------------------*/ - -.ui.link.list .item, -.ui.link.list a.item, -.ui.link.list .item a:not(.ui) { - color: rgba(0, 0, 0, 0.4); - transition: 0.1s color ease; -} - -.ui.link.list.list a.item:hover, -.ui.link.list.list .item a:not(.ui):hover { - color: rgba(0, 0, 0, 0.8); -} - -.ui.link.list.list a.item:active, -.ui.link.list.list .item a:not(.ui):active { - color: rgba(0, 0, 0, 0.9); -} - -.ui.link.list.list .active.item, -.ui.link.list.list .active.item a:not(.ui) { - color: rgba(0, 0, 0, 0.95); -} - -/*------------------- - Selection - --------------------*/ - -.ui.selection.list .list > .item, -.ui.selection.list > .item { - cursor: pointer; - background: transparent; - padding: 0.5em 0.5em; - margin: 0; - color: rgba(0, 0, 0, 0.4); - border-radius: 0.5em; - transition: 0.1s color ease, 0.1s padding-left ease, 0.1s background-color ease; -} - -.ui.selection.list .list > .item:last-child, -.ui.selection.list > .item:last-child { - margin-bottom: 0; -} - -.ui.selection.list .list > .item:hover, -.ui.selection.list > .item:hover { - background: rgba(0, 0, 0, 0.03); - color: rgba(0, 0, 0, 0.8); -} - -.ui.selection.list .list > .item:active, -.ui.selection.list > .item:active { - background: rgba(0, 0, 0, 0.05); - color: rgba(0, 0, 0, 0.9); -} - -.ui.selection.list .list > .item.active, -.ui.selection.list > .item.active { - background: rgba(0, 0, 0, 0.05); - color: rgba(0, 0, 0, 0.95); -} - -/* Celled / Divided Selection List */ - -.ui.celled.selection.list .list > .item, -.ui.divided.selection.list .list > .item, -.ui.celled.selection.list > .item, -.ui.divided.selection.list > .item { - border-radius: 0; -} - -/*------------------- - Animated - --------------------*/ - -.ui.animated.list > .item { - transition: 0.25s color ease 0.1s, 0.25s padding-left ease 0.1s, 0.25s background-color ease 0.1s; -} - -.ui.animated.list:not(.horizontal) > .item:hover { - padding-left: 1em; -} - -/*------------------- - Fitted - --------------------*/ - -.ui.fitted.list:not(.selection) .list > .item, -.ui.fitted.list:not(.selection) > .item { - padding-left: 0; - padding-right: 0; -} - -.ui.fitted.selection.list .list > .item, -.ui.fitted.selection.list > .item { - margin-left: -0.5em; - margin-right: -0.5em; -} - -/*------------------- - Bulleted - --------------------*/ - -ul.ui.list, -.ui.bulleted.list { - margin-left: 1.25rem; -} - -ul.ui.list li, -.ui.bulleted.list .list > .item, -.ui.bulleted.list > .item { - position: relative; -} - -ul.ui.list li:before, -.ui.bulleted.list .list > .item:before, -.ui.bulleted.list > .item:before { - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - pointer-events: none; - position: absolute; - top: auto; - left: auto; - font-weight: normal; - margin-left: -1.25rem; - content: '\2022'; - opacity: 1; - color: inherit; - vertical-align: top; -} - -ul.ui.list li:before, -.ui.bulleted.list .list > a.item:before, -.ui.bulleted.list > a.item:before { - color: rgba(0, 0, 0, 0.87); -} - -ul.ui.list ul, -.ui.bulleted.list .list:not(.icon) { - padding-left: 1.25rem; -} - -/* Horizontal Bulleted */ - -ul.ui.horizontal.bulleted.list, -.ui.horizontal.bulleted.list { - margin-left: 0; -} - -ul.ui.horizontal.bulleted.list li, -.ui.horizontal.bulleted.list > .item { - margin-left: 1.75rem; -} - -ul.ui.horizontal.bulleted.list li:first-child, -.ui.horizontal.bulleted.list > .item:first-child { - margin-left: 0; -} - -ul.ui.horizontal.bulleted.list li::before, -.ui.horizontal.bulleted.list > .item::before { - color: rgba(0, 0, 0, 0.87); -} - -ul.ui.horizontal.bulleted.list li:first-child::before, -.ui.horizontal.bulleted.list > .item:first-child::before { - display: none; -} - -/*------------------- - Ordered - --------------------*/ - -ol.ui.list, -.ui.ordered.list, -.ui.ordered.list .list:not(.icon), -ol.ui.list ol { - counter-reset: ordered; - margin-left: 1.25rem; - list-style-type: none; -} - -ol.ui.list li, -.ui.ordered.list .list > .item, -.ui.ordered.list > .item { - list-style-type: none; - position: relative; -} - -ol.ui.list li:before, -.ui.ordered.list .list > .item:before, -.ui.ordered.list > .item:before { - position: absolute; - top: auto; - left: auto; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - pointer-events: none; - margin-left: -1.25rem; - counter-increment: ordered; - content: counters(ordered, ".") " "; - text-align: right; - color: rgba(0, 0, 0, 0.87); - vertical-align: middle; - opacity: 0.8; -} - -/* Value */ - -.ui.ordered.list .list > .item[data-value]:before, -.ui.ordered.list > .item[data-value]:before { - content: attr(data-value); -} - -ol.ui.list li[value]:before { - content: attr(value); -} - -/* Child Lists */ - -ol.ui.list ol, -.ui.ordered.list .list:not(.icon) { - margin-left: 1em; -} - -ol.ui.list ol li:before, -.ui.ordered.list .list > .item:before { - margin-left: -2em; -} - -/* Horizontal Ordered */ - -ol.ui.horizontal.list, -.ui.ordered.horizontal.list { - margin-left: 0; -} - -ol.ui.horizontal.list li:before, -.ui.ordered.horizontal.list .list > .item:before, -.ui.ordered.horizontal.list > .item:before { - position: static; - margin: 0 0.5em 0 0; -} - -/* Suffixed Ordered */ - -ol.ui.suffixed.list li:before, -.ui.suffixed.ordered.list .list > .item:before, -.ui.suffixed.ordered.list > .item:before { - content: counters(ordered, ".") "."; -} - -/*------------------- - Divided - --------------------*/ - -.ui.divided.list > .item { - border-top: 1px solid rgba(34, 36, 38, 0.15); -} - -.ui.divided.list .list > .item { - border-top: none; -} - -.ui.divided.list .item .list > .item { - border-top: none; -} - -.ui.divided.list .list > .item:first-child, -.ui.divided.list > .item:first-child { - border-top: none; -} - -/* Sub Menu */ - -.ui.divided.list:not(.horizontal) .list > .item:first-child { - border-top-width: 1px; -} - -/* Divided bulleted */ - -.ui.divided.bulleted.list:not(.horizontal), -.ui.divided.bulleted.list .list:not(.icon) { - margin-left: 0; - padding-left: 0; -} - -.ui.divided.bulleted.list > .item:not(.horizontal) { - padding-left: 1.25rem; -} - -/* Divided Ordered */ - -.ui.divided.ordered.list { - margin-left: 0; -} - -.ui.divided.ordered.list .list > .item, -.ui.divided.ordered.list > .item { - padding-left: 1.25rem; -} - -.ui.divided.ordered.list .item .list:not(.icon) { - margin-left: 0; - margin-right: 0; - padding-bottom: 0.21428571em; -} - -.ui.divided.ordered.list .item .list > .item { - padding-left: 1em; -} - -/* Divided Selection */ - -.ui.divided.selection.list .list > .item, -.ui.divided.selection.list > .item { - margin: 0; - border-radius: 0; -} - -/* Divided horizontal */ - -.ui.divided.horizontal.list { - margin-left: 0; -} - -.ui.divided.horizontal.list > .item { - padding-left: 0.5em; -} - -.ui.divided.horizontal.list > .item:not(:last-child) { - padding-right: 0.5em; -} - -.ui.divided.horizontal.list > .item { - border-top: none; - border-right: 1px solid rgba(34, 36, 38, 0.15); - margin: 0; - line-height: 0.6; -} - -.ui.horizontal.divided.list > .item:last-child { - border-right: none; -} - -/*------------------- - Celled - --------------------*/ - -.ui.celled.list > .item, -.ui.celled.list > .list { - border-top: 1px solid rgba(34, 36, 38, 0.15); - padding-left: 0.5em; - padding-right: 0.5em; -} - -.ui.celled.list > .item:last-child { - border-bottom: 1px solid rgba(34, 36, 38, 0.15); -} - -/* Padding on all elements */ - -.ui.celled.list > .item:first-child, -.ui.celled.list > .item:last-child { - padding-top: 0.21428571em; - padding-bottom: 0.21428571em; -} - -/* Sub Menu */ - -.ui.celled.list .item .list > .item { - border-width: 0; -} - -.ui.celled.list .list > .item:first-child { - border-top-width: 0; -} - -/* Celled Bulleted */ - -.ui.celled.bulleted.list { - margin-left: 0; -} - -.ui.celled.bulleted.list .list > .item, -.ui.celled.bulleted.list > .item { - padding-left: 1.25rem; -} - -.ui.celled.bulleted.list .item .list:not(.icon) { - margin-left: -1.25rem; - margin-right: -1.25rem; - padding-bottom: 0.21428571em; -} - -/* Celled Ordered */ - -.ui.celled.ordered.list { - margin-left: 0; -} - -.ui.celled.ordered.list .list > .item, -.ui.celled.ordered.list > .item { - padding-left: 1.25rem; -} - -.ui.celled.ordered.list .item .list:not(.icon) { - margin-left: 0; - margin-right: 0; - padding-bottom: 0.21428571em; -} - -.ui.celled.ordered.list .list > .item { - padding-left: 1em; -} - -/* Celled Horizontal */ - -.ui.horizontal.celled.list { - margin-left: 0; -} - -.ui.horizontal.celled.list .list > .item, -.ui.horizontal.celled.list > .item { - border-top: none; - border-left: 1px solid rgba(34, 36, 38, 0.15); - margin: 0; - padding-left: 0.5em; - padding-right: 0.5em; - line-height: 0.6; -} - -.ui.horizontal.celled.list .list > .item:last-child, -.ui.horizontal.celled.list > .item:last-child { - border-bottom: none; - border-right: 1px solid rgba(34, 36, 38, 0.15); -} - -/*------------------- - Relaxed - --------------------*/ - -.ui.relaxed.list:not(.horizontal) > .item:not(:first-child) { - padding-top: 0.42857143em; -} - -.ui.relaxed.list:not(.horizontal) > .item:not(:last-child) { - padding-bottom: 0.42857143em; -} - -.ui.horizontal.relaxed.list .list > .item:not(:first-child), -.ui.horizontal.relaxed.list > .item:not(:first-child) { - padding-left: 1rem; -} - -.ui.horizontal.relaxed.list .list > .item:not(:last-child), -.ui.horizontal.relaxed.list > .item:not(:last-child) { - padding-right: 1rem; -} - -/* Very Relaxed */ - -.ui[class*="very relaxed"].list:not(.horizontal) > .item:not(:first-child) { - padding-top: 0.85714286em; -} - -.ui[class*="very relaxed"].list:not(.horizontal) > .item:not(:last-child) { - padding-bottom: 0.85714286em; -} - -.ui.horizontal[class*="very relaxed"].list .list > .item:not(:first-child), -.ui.horizontal[class*="very relaxed"].list > .item:not(:first-child) { - padding-left: 1.5rem; -} - -.ui.horizontal[class*="very relaxed"].list .list > .item:not(:last-child), -.ui.horizontal[class*="very relaxed"].list > .item:not(:last-child) { - padding-right: 1.5rem; -} - -/*------------------- - Sizes ---------------------*/ - -.ui.list { - font-size: 1em; -} - -.ui.mini.list { - font-size: 0.78571429em; -} - -.ui.mini.horizontal.list .list > .item, -.ui.mini.horizontal.list > .item { - font-size: 0.78571429rem; -} - -.ui.tiny.list { - font-size: 0.85714286em; -} - -.ui.tiny.horizontal.list .list > .item, -.ui.tiny.horizontal.list > .item { - font-size: 0.85714286rem; -} - -.ui.small.list { - font-size: 0.92857143em; -} - -.ui.small.horizontal.list .list > .item, -.ui.small.horizontal.list > .item { - font-size: 0.92857143rem; -} - -.ui.large.list { - font-size: 1.14285714em; -} - -.ui.large.horizontal.list .list > .item, -.ui.large.horizontal.list > .item { - font-size: 1.14285714rem; -} - -.ui.big.list { - font-size: 1.28571429em; -} - -.ui.big.horizontal.list .list > .item, -.ui.big.horizontal.list > .item { - font-size: 1.28571429rem; -} - -.ui.huge.list { - font-size: 1.42857143em; -} - -.ui.huge.horizontal.list .list > .item, -.ui.huge.horizontal.list > .item { - font-size: 1.42857143rem; -} - -.ui.massive.list { - font-size: 1.71428571em; -} - -.ui.massive.horizontal.list .list > .item, -.ui.massive.horizontal.list > .item { - font-size: 1.71428571rem; -} - -/******************************* - Theme Overrides -*******************************/ - -/******************************* - User Variable Overrides -*******************************/ /* * # Fomantic - Menu * http://github.com/fomantic/Fomantic-UI/ diff --git a/web_src/fomantic/semantic.json b/web_src/fomantic/semantic.json index 151273f3ca..5db57bc8d4 100644 --- a/web_src/fomantic/semantic.json +++ b/web_src/fomantic/semantic.json @@ -26,8 +26,6 @@ "dimmer", "dropdown", "form", - "input", - "list", "menu", "modal", "search", diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue index ac6a8f3bb6..70b12dcb28 100644 --- a/web_src/js/components/ContextPopup.vue +++ b/web_src/js/components/ContextPopup.vue @@ -1,7 +1,6 @@