diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 00000000000..af744db3e16 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,999 @@ +const restrictedSyntax = ['WithStatement', 'ForInStatement', 'LabeledStatement', 'SequenceExpression']; + +module.exports = { + root: true, + reportUnusedDisableDirectives: true, + ignorePatterns: [ + '/web_src/js/vendor', + '/web_src/fomantic', + '/public/assets/js', + ], + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + ecmaVersion: 'latest', + project: true, + extraFileExtensions: ['.vue'], + parser: '@typescript-eslint/parser', // for vue plugin - https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser + }, + settings: { + 'import-x/extensions': ['.js', '.ts'], + 'import-x/parsers': { + '@typescript-eslint/parser': ['.js', '.ts'], + }, + 'import-x/resolver': { + typescript: true, + }, + }, + plugins: [ + '@eslint-community/eslint-plugin-eslint-comments', + '@stylistic/eslint-plugin-js', + '@typescript-eslint/eslint-plugin', + 'eslint-plugin-array-func', + 'eslint-plugin-github', + 'eslint-plugin-import-x', + 'eslint-plugin-no-jquery', + 'eslint-plugin-no-use-extend-native', + 'eslint-plugin-regexp', + 'eslint-plugin-sonarjs', + 'eslint-plugin-unicorn', + 'eslint-plugin-vitest', + 'eslint-plugin-vitest-globals', + 'eslint-plugin-wc', + ], + env: { + es2024: true, + node: true, + }, + overrides: [ + { + files: ['web_src/**/*'], + globals: { + __webpack_public_path__: true, + process: false, // https://github.com/webpack/webpack/issues/15833 + }, + }, + { + files: ['web_src/**/*', 'docs/**/*'], + env: { + browser: true, + node: false, + }, + }, + { + files: ['*.config.*'], + rules: { + 'import-x/no-unused-modules': [0], + }, + }, + { + files: ['**/*.d.ts'], + rules: { + 'import-x/no-unused-modules': [0], + '@typescript-eslint/consistent-type-definitions': [0], + '@typescript-eslint/consistent-type-imports': [0], + }, + }, + { + files: ['web_src/js/types.ts'], + rules: { + 'import-x/no-unused-modules': [0], + }, + }, + { + files: ['**/*.test.*', 'web_src/js/test/setup.ts'], + env: { + 'vitest-globals/env': true, + }, + rules: { + 'vitest/consistent-test-filename': [0], + 'vitest/consistent-test-it': [0], + 'vitest/expect-expect': [0], + 'vitest/max-expects': [0], + 'vitest/max-nested-describe': [0], + 'vitest/no-alias-methods': [0], + 'vitest/no-commented-out-tests': [0], + 'vitest/no-conditional-expect': [0], + 'vitest/no-conditional-in-test': [0], + 'vitest/no-conditional-tests': [0], + 'vitest/no-disabled-tests': [0], + 'vitest/no-done-callback': [0], + 'vitest/no-duplicate-hooks': [0], + 'vitest/no-focused-tests': [0], + 'vitest/no-hooks': [0], + 'vitest/no-identical-title': [2], + 'vitest/no-interpolation-in-snapshots': [0], + 'vitest/no-large-snapshots': [0], + 'vitest/no-mocks-import': [0], + 'vitest/no-restricted-matchers': [0], + 'vitest/no-restricted-vi-methods': [0], + 'vitest/no-standalone-expect': [0], + 'vitest/no-test-prefixes': [0], + 'vitest/no-test-return-statement': [0], + 'vitest/prefer-called-with': [0], + 'vitest/prefer-comparison-matcher': [0], + 'vitest/prefer-each': [0], + 'vitest/prefer-equality-matcher': [0], + 'vitest/prefer-expect-resolves': [0], + 'vitest/prefer-hooks-in-order': [0], + 'vitest/prefer-hooks-on-top': [2], + 'vitest/prefer-lowercase-title': [0], + 'vitest/prefer-mock-promise-shorthand': [0], + 'vitest/prefer-snapshot-hint': [0], + 'vitest/prefer-spy-on': [0], + 'vitest/prefer-strict-equal': [0], + 'vitest/prefer-to-be': [0], + 'vitest/prefer-to-be-falsy': [0], + 'vitest/prefer-to-be-object': [0], + 'vitest/prefer-to-be-truthy': [0], + 'vitest/prefer-to-contain': [0], + 'vitest/prefer-to-have-length': [0], + 'vitest/prefer-todo': [0], + 'vitest/require-hook': [0], + 'vitest/require-to-throw-message': [0], + 'vitest/require-top-level-describe': [0], + 'vitest/valid-describe-callback': [2], + 'vitest/valid-expect': [2], + 'vitest/valid-title': [2], + }, + }, + { + files: ['web_src/js/modules/fetch.ts', 'web_src/js/standalone/**/*'], + rules: { + 'no-restricted-syntax': [2, ...restrictedSyntax], + }, + }, + { + files: ['**/*.vue'], + plugins: [ + 'eslint-plugin-vue', + 'eslint-plugin-vue-scoped-css', + ], + extends: [ + 'plugin:vue/vue3-recommended', + 'plugin:vue-scoped-css/vue3-recommended', + ], + rules: { + 'vue/attributes-order': [0], + 'vue/html-closing-bracket-spacing': [2, {startTag: 'never', endTag: 'never', selfClosingTag: 'never'}], + 'vue/max-attributes-per-line': [0], + 'vue/singleline-html-element-content-newline': [0], + }, + }, + { + files: ['tests/e2e/**'], + plugins: [ + 'eslint-plugin-playwright' + ], + extends: [ + 'plugin:playwright/recommended', + ], + }, + ], + rules: { + '@eslint-community/eslint-comments/disable-enable-pair': [2], + '@eslint-community/eslint-comments/no-aggregating-enable': [2], + '@eslint-community/eslint-comments/no-duplicate-disable': [2], + '@eslint-community/eslint-comments/no-restricted-disable': [0], + '@eslint-community/eslint-comments/no-unlimited-disable': [2], + '@eslint-community/eslint-comments/no-unused-disable': [2], + '@eslint-community/eslint-comments/no-unused-enable': [2], + '@eslint-community/eslint-comments/no-use': [0], + '@eslint-community/eslint-comments/require-description': [0], + '@stylistic/js/array-bracket-newline': [0], + '@stylistic/js/array-bracket-spacing': [2, 'never'], + '@stylistic/js/array-element-newline': [0], + '@stylistic/js/arrow-parens': [2, 'always'], + '@stylistic/js/arrow-spacing': [2, {before: true, after: true}], + '@stylistic/js/block-spacing': [0], + '@stylistic/js/brace-style': [2, '1tbs', {allowSingleLine: true}], + '@stylistic/js/comma-dangle': [2, 'always-multiline'], + '@stylistic/js/comma-spacing': [2, {before: false, after: true}], + '@stylistic/js/comma-style': [2, 'last'], + '@stylistic/js/computed-property-spacing': [2, 'never'], + '@stylistic/js/dot-location': [2, 'property'], + '@stylistic/js/eol-last': [2], + '@stylistic/js/function-call-argument-newline': [0], + '@stylistic/js/function-call-spacing': [2, 'never'], + '@stylistic/js/function-paren-newline': [0], + '@stylistic/js/generator-star-spacing': [0], + '@stylistic/js/implicit-arrow-linebreak': [0], + '@stylistic/js/indent': [2, 2, {ignoreComments: true, SwitchCase: 1}], + '@stylistic/js/key-spacing': [2], + '@stylistic/js/keyword-spacing': [2], + '@stylistic/js/line-comment-position': [0], + '@stylistic/js/linebreak-style': [2, 'unix'], + '@stylistic/js/lines-around-comment': [0], + '@stylistic/js/lines-between-class-members': [0], + '@stylistic/js/max-len': [0], + '@stylistic/js/max-statements-per-line': [0], + '@stylistic/js/multiline-comment-style': [0], + '@stylistic/js/multiline-ternary': [0], + '@stylistic/js/new-parens': [2], + '@stylistic/js/newline-per-chained-call': [0], + '@stylistic/js/no-confusing-arrow': [0], + '@stylistic/js/no-extra-parens': [0], + '@stylistic/js/no-extra-semi': [2], + '@stylistic/js/no-floating-decimal': [0], + '@stylistic/js/no-mixed-operators': [0], + '@stylistic/js/no-mixed-spaces-and-tabs': [2], + '@stylistic/js/no-multi-spaces': [2, {ignoreEOLComments: true, exceptions: {Property: true}}], + '@stylistic/js/no-multiple-empty-lines': [2, {max: 1, maxEOF: 0, maxBOF: 0}], + '@stylistic/js/no-tabs': [2], + '@stylistic/js/no-trailing-spaces': [2], + '@stylistic/js/no-whitespace-before-property': [2], + '@stylistic/js/nonblock-statement-body-position': [2], + '@stylistic/js/object-curly-newline': [0], + '@stylistic/js/object-curly-spacing': [2, 'never'], + '@stylistic/js/object-property-newline': [0], + '@stylistic/js/one-var-declaration-per-line': [0], + '@stylistic/js/operator-linebreak': [2, 'after'], + '@stylistic/js/padded-blocks': [2, 'never'], + '@stylistic/js/padding-line-between-statements': [0], + '@stylistic/js/quote-props': [0], + '@stylistic/js/quotes': [2, 'single', {avoidEscape: true, allowTemplateLiterals: true}], + '@stylistic/js/rest-spread-spacing': [2, 'never'], + '@stylistic/js/semi': [2, 'always', {omitLastInOneLineBlock: true}], + '@stylistic/js/semi-spacing': [2, {before: false, after: true}], + '@stylistic/js/semi-style': [2, 'last'], + '@stylistic/js/space-before-blocks': [2, 'always'], + '@stylistic/js/space-before-function-paren': [2, {anonymous: 'ignore', named: 'never', asyncArrow: 'always'}], + '@stylistic/js/space-in-parens': [2, 'never'], + '@stylistic/js/space-infix-ops': [2], + '@stylistic/js/space-unary-ops': [2], + '@stylistic/js/spaced-comment': [2, 'always'], + '@stylistic/js/switch-colon-spacing': [2], + '@stylistic/js/template-curly-spacing': [2, 'never'], + '@stylistic/js/template-tag-spacing': [2, 'never'], + '@stylistic/js/wrap-iife': [2, 'inside'], + '@stylistic/js/wrap-regex': [0], + '@stylistic/js/yield-star-spacing': [2, 'after'], + '@typescript-eslint/adjacent-overload-signatures': [0], + '@typescript-eslint/array-type': [0], + '@typescript-eslint/await-thenable': [2], + '@typescript-eslint/ban-ts-comment': [2, {'ts-expect-error': false, 'ts-ignore': true, 'ts-nocheck': false, 'ts-check': false}], + '@typescript-eslint/ban-tslint-comment': [0], + '@typescript-eslint/class-literal-property-style': [0], + '@typescript-eslint/class-methods-use-this': [0], + '@typescript-eslint/consistent-generic-constructors': [0], + '@typescript-eslint/consistent-indexed-object-style': [0], + '@typescript-eslint/consistent-return': [0], + '@typescript-eslint/consistent-type-assertions': [2, {assertionStyle: 'as', objectLiteralTypeAssertions: 'allow'}], + '@typescript-eslint/consistent-type-definitions': [2, 'type'], + '@typescript-eslint/consistent-type-exports': [2, {fixMixedExportsWithInlineTypeSpecifier: false}], + '@typescript-eslint/consistent-type-imports': [2, {prefer: 'type-imports', fixStyle: 'separate-type-imports', disallowTypeAnnotations: true}], + '@typescript-eslint/default-param-last': [0], + '@typescript-eslint/dot-notation': [0], + '@typescript-eslint/explicit-function-return-type': [0], + '@typescript-eslint/explicit-member-accessibility': [0], + '@typescript-eslint/explicit-module-boundary-types': [0], + '@typescript-eslint/init-declarations': [0], + '@typescript-eslint/max-params': [0], + '@typescript-eslint/member-ordering': [0], + '@typescript-eslint/method-signature-style': [0], + '@typescript-eslint/naming-convention': [0], + '@typescript-eslint/no-array-constructor': [2], + '@typescript-eslint/no-array-delete': [2], + '@typescript-eslint/no-base-to-string': [0], + '@typescript-eslint/no-confusing-non-null-assertion': [2], + '@typescript-eslint/no-confusing-void-expression': [0], + '@typescript-eslint/no-deprecated': [2], + '@typescript-eslint/no-dupe-class-members': [0], + '@typescript-eslint/no-duplicate-enum-values': [2], + '@typescript-eslint/no-duplicate-type-constituents': [2, {ignoreUnions: true}], + '@typescript-eslint/no-dynamic-delete': [0], + '@typescript-eslint/no-empty-function': [0], + '@typescript-eslint/no-empty-interface': [0], + '@typescript-eslint/no-empty-object-type': [2], + '@typescript-eslint/no-explicit-any': [0], + '@typescript-eslint/no-extra-non-null-assertion': [2], + '@typescript-eslint/no-extraneous-class': [0], + '@typescript-eslint/no-floating-promises': [0], + '@typescript-eslint/no-for-in-array': [2], + '@typescript-eslint/no-implied-eval': [2], + '@typescript-eslint/no-import-type-side-effects': [0], // dupe with consistent-type-imports + '@typescript-eslint/no-inferrable-types': [0], + '@typescript-eslint/no-invalid-this': [0], + '@typescript-eslint/no-invalid-void-type': [0], + '@typescript-eslint/no-loop-func': [0], + '@typescript-eslint/no-loss-of-precision': [0], + '@typescript-eslint/no-magic-numbers': [0], + '@typescript-eslint/no-meaningless-void-operator': [0], + '@typescript-eslint/no-misused-new': [2], + '@typescript-eslint/no-misused-promises': [2, {checksVoidReturn: {attributes: false, arguments: false}}], + '@typescript-eslint/no-mixed-enums': [0], + '@typescript-eslint/no-namespace': [2], + '@typescript-eslint/no-non-null-asserted-nullish-coalescing': [0], + '@typescript-eslint/no-non-null-asserted-optional-chain': [2], + '@typescript-eslint/no-non-null-assertion': [0], + '@typescript-eslint/no-redeclare': [0], + '@typescript-eslint/no-redundant-type-constituents': [2], + '@typescript-eslint/no-require-imports': [2], + '@typescript-eslint/no-restricted-imports': [0], + '@typescript-eslint/no-restricted-types': [0], + '@typescript-eslint/no-shadow': [0], + '@typescript-eslint/no-this-alias': [0], // handled by unicorn/no-this-assignment + '@typescript-eslint/no-unnecessary-boolean-literal-compare': [0], + '@typescript-eslint/no-unnecessary-condition': [0], + '@typescript-eslint/no-unnecessary-qualifier': [0], + '@typescript-eslint/no-unnecessary-template-expression': [0], + '@typescript-eslint/no-unnecessary-type-arguments': [0], + '@typescript-eslint/no-unnecessary-type-assertion': [2], + '@typescript-eslint/no-unnecessary-type-constraint': [2], + '@typescript-eslint/no-unsafe-argument': [0], + '@typescript-eslint/no-unsafe-assignment': [0], + '@typescript-eslint/no-unsafe-call': [0], + '@typescript-eslint/no-unsafe-declaration-merging': [2], + '@typescript-eslint/no-unsafe-enum-comparison': [2], + '@typescript-eslint/no-unsafe-function-type': [2], + '@typescript-eslint/no-unsafe-member-access': [0], + '@typescript-eslint/no-unsafe-return': [0], + '@typescript-eslint/no-unsafe-unary-minus': [2], + '@typescript-eslint/no-unused-expressions': [0], + '@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'all', caughtErrors: 'all', ignoreRestSiblings: false, argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'}], + '@typescript-eslint/no-use-before-define': [0], + '@typescript-eslint/no-useless-constructor': [0], + '@typescript-eslint/no-useless-empty-export': [0], + '@typescript-eslint/no-wrapper-object-types': [2], + '@typescript-eslint/non-nullable-type-assertion-style': [0], + '@typescript-eslint/only-throw-error': [2], + '@typescript-eslint/parameter-properties': [0], + '@typescript-eslint/prefer-as-const': [2], + '@typescript-eslint/prefer-destructuring': [0], + '@typescript-eslint/prefer-enum-initializers': [0], + '@typescript-eslint/prefer-find': [2], + '@typescript-eslint/prefer-for-of': [2], + '@typescript-eslint/prefer-function-type': [2], + '@typescript-eslint/prefer-includes': [2], + '@typescript-eslint/prefer-literal-enum-member': [0], + '@typescript-eslint/prefer-namespace-keyword': [0], + '@typescript-eslint/prefer-nullish-coalescing': [0], + '@typescript-eslint/prefer-optional-chain': [2, {requireNullish: true}], + '@typescript-eslint/prefer-promise-reject-errors': [0], + '@typescript-eslint/prefer-readonly': [0], + '@typescript-eslint/prefer-readonly-parameter-types': [0], + '@typescript-eslint/prefer-reduce-type-parameter': [0], + '@typescript-eslint/prefer-regexp-exec': [0], + '@typescript-eslint/prefer-return-this-type': [0], + '@typescript-eslint/prefer-string-starts-ends-with': [2, {allowSingleElementEquality: 'always'}], + '@typescript-eslint/promise-function-async': [0], + '@typescript-eslint/require-array-sort-compare': [0], + '@typescript-eslint/require-await': [0], + '@typescript-eslint/restrict-plus-operands': [2], + '@typescript-eslint/restrict-template-expressions': [0], + '@typescript-eslint/return-await': [0], + '@typescript-eslint/strict-boolean-expressions': [0], + '@typescript-eslint/switch-exhaustiveness-check': [0], + '@typescript-eslint/triple-slash-reference': [2], + '@typescript-eslint/typedef': [0], + '@typescript-eslint/unbound-method': [0], // too many false-positives + '@typescript-eslint/unified-signatures': [2], + 'accessor-pairs': [2], + 'array-callback-return': [2, {checkForEach: true}], + 'array-func/avoid-reverse': [2], + 'array-func/from-map': [2], + 'array-func/no-unnecessary-this-arg': [2], + 'array-func/prefer-array-from': [2], + 'array-func/prefer-flat-map': [0], // handled by unicorn/prefer-array-flat-map + 'array-func/prefer-flat': [0], // handled by unicorn/prefer-array-flat + 'arrow-body-style': [0], + 'block-scoped-var': [2], + 'camelcase': [0], + 'capitalized-comments': [0], + 'class-methods-use-this': [0], + 'complexity': [0], + 'consistent-return': [0], + 'consistent-this': [0], + 'constructor-super': [2], + 'curly': [0], + 'default-case-last': [2], + 'default-case': [0], + 'default-param-last': [0], + 'dot-notation': [0], + 'eqeqeq': [2], + 'for-direction': [2], + 'func-name-matching': [2], + 'func-names': [0], + 'func-style': [0], + 'getter-return': [2], + 'github/a11y-aria-label-is-well-formatted': [0], + 'github/a11y-no-title-attribute': [0], + 'github/a11y-no-visually-hidden-interactive-element': [0], + 'github/a11y-role-supports-aria-props': [0], + 'github/a11y-svg-has-accessible-name': [0], + 'github/array-foreach': [0], + 'github/async-currenttarget': [2], + 'github/async-preventdefault': [2], + 'github/authenticity-token': [0], + 'github/get-attribute': [0], + 'github/js-class-name': [0], + 'github/no-blur': [0], + 'github/no-d-none': [0], + 'github/no-dataset': [2], + 'github/no-dynamic-script-tag': [2], + 'github/no-implicit-buggy-globals': [2], + 'github/no-inner-html': [0], + 'github/no-innerText': [2], + 'github/no-then': [2], + 'github/no-useless-passive': [2], + 'github/prefer-observers': [2], + 'github/require-passive-events': [2], + 'github/unescaped-html-literal': [0], + 'grouped-accessor-pairs': [2], + 'guard-for-in': [0], + 'id-blacklist': [0], + 'id-length': [0], + 'id-match': [0], + 'import-x/consistent-type-specifier-style': [0], + 'import-x/default': [0], + 'import-x/dynamic-import-chunkname': [0], + 'import-x/export': [2], + 'import-x/exports-last': [0], + 'import-x/extensions': [2, 'always', {ignorePackages: true}], + 'import-x/first': [2], + 'import-x/group-exports': [0], + 'import-x/max-dependencies': [0], + 'import-x/named': [2], + 'import-x/namespace': [0], + 'import-x/newline-after-import': [0], + 'import-x/no-absolute-path': [0], + 'import-x/no-amd': [2], + 'import-x/no-anonymous-default-export': [0], + 'import-x/no-commonjs': [2], + 'import-x/no-cycle': [2, {ignoreExternal: true, maxDepth: 1}], + 'import-x/no-default-export': [0], + 'import-x/no-deprecated': [0], + 'import-x/no-dynamic-require': [0], + 'import-x/no-empty-named-blocks': [2], + 'import-x/no-extraneous-dependencies': [2], + 'import-x/no-import-module-exports': [0], + 'import-x/no-internal-modules': [0], + 'import-x/no-mutable-exports': [0], + 'import-x/no-named-as-default-member': [0], + 'import-x/no-named-as-default': [0], + 'import-x/no-named-default': [0], + 'import-x/no-named-export': [0], + 'import-x/no-namespace': [0], + 'import-x/no-nodejs-modules': [0], + 'import-x/no-relative-packages': [0], + 'import-x/no-relative-parent-imports': [0], + 'import-x/no-restricted-paths': [0], + 'import-x/no-self-import': [2], + 'import-x/no-unassigned-import': [0], + 'import-x/no-unresolved': [2, {commonjs: true, ignore: ['\\?.+$']}], + 'import-x/no-unused-modules': [2, {unusedExports: true}], + 'import-x/no-useless-path-segments': [2, {commonjs: true}], + 'import-x/no-webpack-loader-syntax': [2], + 'import-x/order': [0], + 'import-x/prefer-default-export': [0], + 'import-x/unambiguous': [0], + 'init-declarations': [0], + 'line-comment-position': [0], + 'logical-assignment-operators': [0], + 'max-classes-per-file': [0], + 'max-depth': [0], + 'max-lines-per-function': [0], + 'max-lines': [0], + 'max-nested-callbacks': [0], + 'max-params': [0], + 'max-statements': [0], + 'multiline-comment-style': [2, 'separate-lines'], + 'new-cap': [0], + 'no-alert': [0], + 'no-array-constructor': [0], // handled by @typescript-eslint/no-array-constructor + 'no-async-promise-executor': [0], + 'no-await-in-loop': [0], + 'no-bitwise': [0], + 'no-buffer-constructor': [0], + 'no-caller': [2], + 'no-case-declarations': [2], + 'no-class-assign': [2], + 'no-compare-neg-zero': [2], + 'no-cond-assign': [2, 'except-parens'], + 'no-console': [1, {allow: ['debug', 'info', 'warn', 'error']}], + 'no-const-assign': [2], + 'no-constant-binary-expression': [2], + 'no-constant-condition': [0], + 'no-constructor-return': [2], + 'no-continue': [0], + 'no-control-regex': [0], + 'no-debugger': [1], + 'no-delete-var': [2], + 'no-div-regex': [0], + 'no-dupe-args': [2], + 'no-dupe-class-members': [2], + 'no-dupe-else-if': [2], + 'no-dupe-keys': [2], + 'no-duplicate-case': [2], + 'no-duplicate-imports': [0], + 'no-else-return': [2], + 'no-empty-character-class': [2], + 'no-empty-function': [0], + 'no-empty-pattern': [2], + 'no-empty-static-block': [2], + 'no-empty': [2, {allowEmptyCatch: true}], + 'no-eq-null': [2], + 'no-eval': [2], + 'no-ex-assign': [2], + 'no-extend-native': [2], + 'no-extra-bind': [2], + 'no-extra-boolean-cast': [2], + 'no-extra-label': [0], + 'no-fallthrough': [2], + 'no-func-assign': [2], + 'no-global-assign': [2], + 'no-implicit-coercion': [2], + 'no-implicit-globals': [0], + 'no-implied-eval': [0], // handled by @typescript-eslint/no-implied-eval + 'no-import-assign': [2], + 'no-inline-comments': [0], + 'no-inner-declarations': [2], + 'no-invalid-regexp': [2], + 'no-invalid-this': [0], + 'no-irregular-whitespace': [2], + 'no-iterator': [2], + 'no-jquery/no-ajax-events': [2], + 'no-jquery/no-ajax': [2], + 'no-jquery/no-and-self': [2], + 'no-jquery/no-animate-toggle': [2], + 'no-jquery/no-animate': [2], + 'no-jquery/no-append-html': [2], + 'no-jquery/no-attr': [2], + 'no-jquery/no-bind': [2], + 'no-jquery/no-box-model': [2], + 'no-jquery/no-browser': [2], + 'no-jquery/no-camel-case': [2], + 'no-jquery/no-class-state': [2], + 'no-jquery/no-class': [0], + 'no-jquery/no-clone': [2], + 'no-jquery/no-closest': [0], + 'no-jquery/no-constructor-attributes': [2], + 'no-jquery/no-contains': [2], + 'no-jquery/no-context-prop': [2], + 'no-jquery/no-css': [2], + 'no-jquery/no-data': [0], + 'no-jquery/no-deferred': [2], + 'no-jquery/no-delegate': [2], + 'no-jquery/no-done-fail': [2], + 'no-jquery/no-each-collection': [0], + 'no-jquery/no-each-util': [0], + 'no-jquery/no-each': [0], + 'no-jquery/no-error-shorthand': [2], + 'no-jquery/no-error': [2], + 'no-jquery/no-escape-selector': [2], + 'no-jquery/no-event-shorthand': [2], + 'no-jquery/no-extend': [2], + 'no-jquery/no-fade': [2], + 'no-jquery/no-filter': [0], + 'no-jquery/no-find-collection': [0], + 'no-jquery/no-find-util': [2], + 'no-jquery/no-find': [0], + 'no-jquery/no-fx-interval': [2], + 'no-jquery/no-fx': [2], + 'no-jquery/no-global-eval': [2], + 'no-jquery/no-global-selector': [0], + 'no-jquery/no-grep': [2], + 'no-jquery/no-has': [2], + 'no-jquery/no-hold-ready': [2], + 'no-jquery/no-html': [0], + 'no-jquery/no-in-array': [2], + 'no-jquery/no-is-array': [2], + 'no-jquery/no-is-empty-object': [2], + 'no-jquery/no-is-function': [2], + 'no-jquery/no-is-numeric': [2], + 'no-jquery/no-is-plain-object': [2], + 'no-jquery/no-is-window': [2], + 'no-jquery/no-is': [2], + 'no-jquery/no-jquery-constructor': [0], + 'no-jquery/no-live': [2], + 'no-jquery/no-load-shorthand': [2], + 'no-jquery/no-load': [2], + 'no-jquery/no-map-collection': [0], + 'no-jquery/no-map-util': [2], + 'no-jquery/no-map': [2], + 'no-jquery/no-merge': [2], + 'no-jquery/no-node-name': [2], + 'no-jquery/no-noop': [2], + 'no-jquery/no-now': [2], + 'no-jquery/no-on-ready': [2], + 'no-jquery/no-other-methods': [0], + 'no-jquery/no-other-utils': [2], + 'no-jquery/no-param': [2], + 'no-jquery/no-parent': [0], + 'no-jquery/no-parents': [2], + 'no-jquery/no-parse-html-literal': [2], + 'no-jquery/no-parse-html': [2], + 'no-jquery/no-parse-json': [2], + 'no-jquery/no-parse-xml': [2], + 'no-jquery/no-prop': [2], + 'no-jquery/no-proxy': [2], + 'no-jquery/no-ready-shorthand': [2], + 'no-jquery/no-ready': [2], + 'no-jquery/no-selector-prop': [2], + 'no-jquery/no-serialize': [2], + 'no-jquery/no-size': [2], + 'no-jquery/no-sizzle': [2], + 'no-jquery/no-slide': [2], + 'no-jquery/no-sub': [2], + 'no-jquery/no-support': [2], + 'no-jquery/no-text': [2], + 'no-jquery/no-trigger': [0], + 'no-jquery/no-trim': [2], + 'no-jquery/no-type': [2], + 'no-jquery/no-unique': [2], + 'no-jquery/no-unload-shorthand': [2], + 'no-jquery/no-val': [0], + 'no-jquery/no-visibility': [2], + 'no-jquery/no-when': [2], + 'no-jquery/no-wrap': [2], + 'no-jquery/variable-pattern': [2], + 'no-label-var': [2], + 'no-labels': [0], // handled by no-restricted-syntax + 'no-lone-blocks': [2], + 'no-lonely-if': [0], + 'no-loop-func': [0], + 'no-loss-of-precision': [2], + 'no-magic-numbers': [0], + 'no-misleading-character-class': [2], + 'no-multi-assign': [0], + 'no-multi-str': [2], + 'no-negated-condition': [0], + 'no-nested-ternary': [0], + 'no-new-func': [2], + 'no-new-native-nonconstructor': [2], + 'no-new-object': [2], + 'no-new-symbol': [2], + 'no-new-wrappers': [2], + 'no-new': [0], + 'no-nonoctal-decimal-escape': [2], + 'no-obj-calls': [2], + 'no-octal-escape': [2], + 'no-octal': [2], + 'no-param-reassign': [0], + 'no-plusplus': [0], + 'no-promise-executor-return': [0], + 'no-proto': [2], + 'no-prototype-builtins': [2], + 'no-redeclare': [0], // must be disabled for typescript overloads + 'no-regex-spaces': [2], + 'no-restricted-exports': [0], + 'no-restricted-globals': [2, 'addEventListener', 'blur', 'close', 'closed', 'confirm', 'defaultStatus', 'defaultstatus', 'error', 'event', 'external', 'find', 'focus', 'frameElement', 'frames', 'history', 'innerHeight', 'innerWidth', 'isFinite', 'isNaN', 'length', 'locationbar', 'menubar', 'moveBy', 'moveTo', 'name', 'onblur', 'onerror', 'onfocus', 'onload', 'onresize', 'onunload', 'open', 'opener', 'opera', 'outerHeight', 'outerWidth', 'pageXOffset', 'pageYOffset', 'parent', 'print', 'removeEventListener', 'resizeBy', 'resizeTo', 'screen', 'screenLeft', 'screenTop', 'screenX', 'screenY', 'scroll', 'scrollbars', 'scrollBy', 'scrollTo', 'scrollX', 'scrollY', 'status', 'statusbar', 'stop', 'toolbar', 'top'], + 'no-restricted-imports': [0], + 'no-restricted-syntax': [2, ...restrictedSyntax, {selector: 'CallExpression[callee.name="fetch"]', message: 'use modules/fetch.ts instead'}], + 'no-return-assign': [0], + 'no-script-url': [2], + 'no-self-assign': [2, {props: true}], + 'no-self-compare': [2], + 'no-sequences': [2], + 'no-setter-return': [2], + 'no-shadow-restricted-names': [2], + 'no-shadow': [0], + 'no-sparse-arrays': [2], + 'no-template-curly-in-string': [2], + 'no-ternary': [0], + 'no-this-before-super': [2], + 'no-throw-literal': [2], + 'no-undef-init': [2], + 'no-undef': [0], + 'no-undefined': [0], + 'no-underscore-dangle': [0], + 'no-unexpected-multiline': [2], + 'no-unmodified-loop-condition': [2], + 'no-unneeded-ternary': [2], + 'no-unreachable-loop': [2], + 'no-unreachable': [2], + 'no-unsafe-finally': [2], + 'no-unsafe-negation': [2], + 'no-unused-expressions': [2], + 'no-unused-labels': [2], + 'no-unused-private-class-members': [2], + 'no-unused-vars': [0], // handled by @typescript-eslint/no-unused-vars + 'no-use-before-define': [2, {functions: false, classes: true, variables: true, allowNamedExports: true}], + 'no-use-extend-native/no-use-extend-native': [2], + 'no-useless-backreference': [2], + 'no-useless-call': [2], + 'no-useless-catch': [2], + 'no-useless-computed-key': [2], + 'no-useless-concat': [2], + 'no-useless-constructor': [2], + 'no-useless-escape': [2], + 'no-useless-rename': [2], + 'no-useless-return': [2], + 'no-var': [2], + 'no-void': [2], + 'no-warning-comments': [0], + 'no-with': [0], // handled by no-restricted-syntax + 'object-shorthand': [2, 'always'], + 'one-var-declaration-per-line': [0], + 'one-var': [0], + 'operator-assignment': [2, 'always'], + 'operator-linebreak': [2, 'after'], + 'prefer-arrow-callback': [2, {allowNamedFunctions: true, allowUnboundThis: true}], + 'prefer-const': [2, {destructuring: 'all', ignoreReadBeforeAssign: true}], + 'prefer-destructuring': [0], + 'prefer-exponentiation-operator': [2], + 'prefer-named-capture-group': [0], + 'prefer-numeric-literals': [2], + 'prefer-object-has-own': [2], + 'prefer-object-spread': [2], + 'prefer-promise-reject-errors': [2, {allowEmptyReject: false}], + 'prefer-regex-literals': [2], + 'prefer-rest-params': [2], + 'prefer-spread': [2], + 'prefer-template': [2], + 'radix': [2, 'as-needed'], + 'regexp/confusing-quantifier': [2], + 'regexp/control-character-escape': [2], + 'regexp/hexadecimal-escape': [0], + 'regexp/letter-case': [0], + 'regexp/match-any': [2], + 'regexp/negation': [2], + 'regexp/no-contradiction-with-assertion': [0], + 'regexp/no-control-character': [0], + 'regexp/no-dupe-characters-character-class': [2], + 'regexp/no-dupe-disjunctions': [2], + 'regexp/no-empty-alternative': [2], + 'regexp/no-empty-capturing-group': [2], + 'regexp/no-empty-character-class': [0], + 'regexp/no-empty-group': [2], + 'regexp/no-empty-lookarounds-assertion': [2], + 'regexp/no-empty-string-literal': [2], + 'regexp/no-escape-backspace': [2], + 'regexp/no-extra-lookaround-assertions': [0], + 'regexp/no-invalid-regexp': [2], + 'regexp/no-invisible-character': [2], + 'regexp/no-lazy-ends': [2], + 'regexp/no-legacy-features': [2], + 'regexp/no-misleading-capturing-group': [0], + 'regexp/no-misleading-unicode-character': [0], + 'regexp/no-missing-g-flag': [2], + 'regexp/no-non-standard-flag': [2], + 'regexp/no-obscure-range': [2], + 'regexp/no-octal': [2], + 'regexp/no-optional-assertion': [2], + 'regexp/no-potentially-useless-backreference': [2], + 'regexp/no-standalone-backslash': [2], + 'regexp/no-super-linear-backtracking': [0], + 'regexp/no-super-linear-move': [0], + 'regexp/no-trivially-nested-assertion': [2], + 'regexp/no-trivially-nested-quantifier': [2], + 'regexp/no-unused-capturing-group': [0], + 'regexp/no-useless-assertions': [2], + 'regexp/no-useless-backreference': [2], + 'regexp/no-useless-character-class': [2], + 'regexp/no-useless-dollar-replacements': [2], + 'regexp/no-useless-escape': [2], + 'regexp/no-useless-flag': [2], + 'regexp/no-useless-lazy': [2], + 'regexp/no-useless-non-capturing-group': [2], + 'regexp/no-useless-quantifier': [2], + 'regexp/no-useless-range': [2], + 'regexp/no-useless-set-operand': [2], + 'regexp/no-useless-string-literal': [2], + 'regexp/no-useless-two-nums-quantifier': [2], + 'regexp/no-zero-quantifier': [2], + 'regexp/optimal-lookaround-quantifier': [2], + 'regexp/optimal-quantifier-concatenation': [0], + 'regexp/prefer-character-class': [0], + 'regexp/prefer-d': [0], + 'regexp/prefer-escape-replacement-dollar-char': [0], + 'regexp/prefer-lookaround': [0], + 'regexp/prefer-named-backreference': [0], + 'regexp/prefer-named-capture-group': [0], + 'regexp/prefer-named-replacement': [0], + 'regexp/prefer-plus-quantifier': [2], + 'regexp/prefer-predefined-assertion': [2], + 'regexp/prefer-quantifier': [0], + 'regexp/prefer-question-quantifier': [2], + 'regexp/prefer-range': [2], + 'regexp/prefer-regexp-exec': [2], + 'regexp/prefer-regexp-test': [2], + 'regexp/prefer-result-array-groups': [0], + 'regexp/prefer-set-operation': [2], + 'regexp/prefer-star-quantifier': [2], + 'regexp/prefer-unicode-codepoint-escapes': [2], + 'regexp/prefer-w': [0], + 'regexp/require-unicode-regexp': [0], + 'regexp/simplify-set-operations': [2], + 'regexp/sort-alternatives': [0], + 'regexp/sort-character-class-elements': [0], + 'regexp/sort-flags': [0], + 'regexp/strict': [2], + 'regexp/unicode-escape': [0], + 'regexp/use-ignore-case': [0], + 'require-atomic-updates': [0], + 'require-await': [0], // handled by @typescript-eslint/require-await + 'require-unicode-regexp': [0], + 'require-yield': [2], + 'sonarjs/cognitive-complexity': [0], + 'sonarjs/elseif-without-else': [0], + 'sonarjs/max-switch-cases': [0], + 'sonarjs/no-all-duplicated-branches': [2], + 'sonarjs/no-collapsible-if': [0], + 'sonarjs/no-collection-size-mischeck': [2], + 'sonarjs/no-duplicate-string': [0], + 'sonarjs/no-duplicated-branches': [0], + 'sonarjs/no-element-overwrite': [2], + 'sonarjs/no-empty-collection': [2], + 'sonarjs/no-extra-arguments': [2], + 'sonarjs/no-gratuitous-expressions': [2], + 'sonarjs/no-identical-conditions': [2], + 'sonarjs/no-identical-expressions': [2], + 'sonarjs/no-identical-functions': [2, 5], + 'sonarjs/no-ignored-return': [2], + 'sonarjs/no-inverted-boolean-check': [2], + 'sonarjs/no-nested-switch': [0], + 'sonarjs/no-nested-template-literals': [0], + 'sonarjs/no-one-iteration-loop': [2], + 'sonarjs/no-redundant-boolean': [2], + 'sonarjs/no-redundant-jump': [2], + 'sonarjs/no-same-line-conditional': [2], + 'sonarjs/no-small-switch': [0], + 'sonarjs/no-unused-collection': [2], + 'sonarjs/no-use-of-empty-return-value': [2], + 'sonarjs/no-useless-catch': [2], + 'sonarjs/non-existent-operator': [2], + 'sonarjs/prefer-immediate-return': [0], + 'sonarjs/prefer-object-literal': [0], + 'sonarjs/prefer-single-boolean-return': [0], + 'sonarjs/prefer-while': [2], + 'sort-imports': [0], + 'sort-keys': [0], + 'sort-vars': [0], + 'strict': [0], + 'symbol-description': [2], + 'unicode-bom': [2, 'never'], + 'unicorn/better-regex': [0], + 'unicorn/catch-error-name': [0], + 'unicorn/consistent-destructuring': [2], + 'unicorn/consistent-empty-array-spread': [2], + 'unicorn/consistent-existence-index-check': [0], + 'unicorn/consistent-function-scoping': [0], + 'unicorn/custom-error-definition': [0], + 'unicorn/empty-brace-spaces': [2], + 'unicorn/error-message': [0], + 'unicorn/escape-case': [0], + 'unicorn/expiring-todo-comments': [0], + 'unicorn/explicit-length-check': [0], + 'unicorn/filename-case': [0], + 'unicorn/import-index': [0], + '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], + 'unicorn/no-for-loop': [0], + 'unicorn/no-hex-escape': [0], + 'unicorn/no-instanceof-array': [0], + 'unicorn/no-invalid-fetch-options': [2], + 'unicorn/no-invalid-remove-event-listener': [2], + 'unicorn/no-keyword-prefix': [0], + 'unicorn/no-length-as-slice-end': [2], + 'unicorn/no-lonely-if': [2], + 'unicorn/no-magic-array-flat-depth': [0], + 'unicorn/no-negated-condition': [0], + 'unicorn/no-negation-in-equality-check': [2], + 'unicorn/no-nested-ternary': [0], + 'unicorn/no-new-array': [0], + 'unicorn/no-new-buffer': [0], + '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], + 'unicorn/no-typeof-undefined': [2], + 'unicorn/no-unnecessary-await': [2], + 'unicorn/no-unnecessary-polyfills': [2], + 'unicorn/no-unreadable-array-destructuring': [0], + 'unicorn/no-unreadable-iife': [2], + 'unicorn/no-unused-properties': [2], + 'unicorn/no-useless-fallback-in-spread': [2], + 'unicorn/no-useless-length-check': [2], + 'unicorn/no-useless-promise-resolve-reject': [2], + 'unicorn/no-useless-spread': [2], + 'unicorn/no-useless-switch-case': [2], + 'unicorn/no-useless-undefined': [0], + 'unicorn/no-zero-fractions': [2], + 'unicorn/number-literal-case': [0], + 'unicorn/numeric-separators-style': [0], + 'unicorn/prefer-add-event-listener': [2], + 'unicorn/prefer-array-find': [2], + 'unicorn/prefer-array-flat-map': [2], + 'unicorn/prefer-array-flat': [2], + 'unicorn/prefer-array-index-of': [2], + 'unicorn/prefer-array-some': [2], + 'unicorn/prefer-at': [0], + 'unicorn/prefer-blob-reading-methods': [2], + 'unicorn/prefer-code-point': [0], + 'unicorn/prefer-date-now': [2], + 'unicorn/prefer-default-parameters': [0], + 'unicorn/prefer-dom-node-append': [2], + 'unicorn/prefer-dom-node-dataset': [0], + 'unicorn/prefer-dom-node-remove': [2], + 'unicorn/prefer-dom-node-text-content': [2], + 'unicorn/prefer-event-target': [2], + 'unicorn/prefer-export-from': [0], + 'unicorn/prefer-global-this': [0], + 'unicorn/prefer-includes': [2], + 'unicorn/prefer-json-parse-buffer': [0], + 'unicorn/prefer-keyboard-event-key': [2], + 'unicorn/prefer-logical-operator-over-ternary': [2], + 'unicorn/prefer-math-min-max': [2], + 'unicorn/prefer-math-trunc': [2], + 'unicorn/prefer-modern-dom-apis': [0], + 'unicorn/prefer-modern-math-apis': [2], + 'unicorn/prefer-module': [2], + 'unicorn/prefer-native-coercion-functions': [2], + 'unicorn/prefer-negative-index': [2], + 'unicorn/prefer-node-protocol': [2], + 'unicorn/prefer-number-properties': [0], + 'unicorn/prefer-object-from-entries': [2], + 'unicorn/prefer-object-has-own': [0], + 'unicorn/prefer-optional-catch-binding': [2], + 'unicorn/prefer-prototype-methods': [0], + 'unicorn/prefer-query-selector': [2], + 'unicorn/prefer-reflect-apply': [0], + 'unicorn/prefer-regexp-test': [2], + 'unicorn/prefer-set-has': [0], + 'unicorn/prefer-set-size': [2], + 'unicorn/prefer-spread': [0], + 'unicorn/prefer-string-raw': [0], + 'unicorn/prefer-string-replace-all': [0], + 'unicorn/prefer-string-slice': [0], + 'unicorn/prefer-string-starts-ends-with': [2], + 'unicorn/prefer-string-trim-start-end': [2], + 'unicorn/prefer-structured-clone': [2], + 'unicorn/prefer-switch': [0], + 'unicorn/prefer-ternary': [0], + 'unicorn/prefer-text-content': [2], + 'unicorn/prefer-top-level-await': [0], + 'unicorn/prefer-type-error': [0], + 'unicorn/prevent-abbreviations': [0], + 'unicorn/relative-url-style': [2], + 'unicorn/require-array-join-separator': [2], + 'unicorn/require-number-to-fixed-digits-argument': [2], + 'unicorn/require-post-message-target-origin': [0], + 'unicorn/string-content': [0], + 'unicorn/switch-case-braces': [0], + 'unicorn/template-indent': [2], + 'unicorn/text-encoding-identifier-case': [0], + 'unicorn/throw-new-error': [2], + 'use-isnan': [2], + 'valid-typeof': [2, {requireStringLiterals: true}], + 'vars-on-top': [0], + 'wc/attach-shadow-constructor': [2], + 'wc/define-tag-after-class-definition': [0], + 'wc/expose-class-on-global': [0], + 'wc/file-name-matches-element': [2], + 'wc/guard-define-call': [0], + 'wc/guard-super-call': [2], + 'wc/max-elements-per-file': [0], + 'wc/no-child-traversal-in-attributechangedcallback': [2], + 'wc/no-child-traversal-in-connectedcallback': [2], + 'wc/no-closed-shadow-root': [2], + 'wc/no-constructor-attributes': [2], + 'wc/no-constructor-params': [2], + 'wc/no-constructor': [2], + 'wc/no-customized-built-in-elements': [2], + 'wc/no-exports-with-element': [0], + 'wc/no-invalid-element-name': [2], + 'wc/no-invalid-extends': [2], + 'wc/no-method-prefixed-with-on': [2], + 'wc/no-self-class': [2], + 'wc/no-typos': [2], + 'wc/require-listener-teardown': [2], + 'wc/tag-name-matches-class': [2], + 'yoda': [2, 'never'], + }, +}; diff --git a/.eslintrc.yaml b/.eslintrc.yaml deleted file mode 100644 index 0dd9a6687d8..00000000000 --- a/.eslintrc.yaml +++ /dev/null @@ -1,967 +0,0 @@ -root: true -reportUnusedDisableDirectives: true - -ignorePatterns: - - /web_src/js/vendor - - /web_src/fomantic - - /public/assets/js - -parser: "@typescript-eslint/parser" - -parserOptions: - sourceType: module - ecmaVersion: latest - project: true - extraFileExtensions: [".vue"] - parser: "@typescript-eslint/parser" # for vue plugin - https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser - -settings: - import-x/extensions: [".js", ".ts"] - import-x/parsers: - "@typescript-eslint/parser": [".js", ".ts"] - import-x/resolver: - typescript: true - -plugins: - - "@eslint-community/eslint-plugin-eslint-comments" - - "@stylistic/eslint-plugin-js" - - "@typescript-eslint/eslint-plugin" - - eslint-plugin-array-func - - eslint-plugin-github - - eslint-plugin-import-x - - eslint-plugin-no-jquery - - eslint-plugin-no-use-extend-native - - eslint-plugin-regexp - - eslint-plugin-sonarjs - - eslint-plugin-unicorn - - eslint-plugin-vitest - - eslint-plugin-vitest-globals - - eslint-plugin-wc - -env: - es2024: true - node: true - -overrides: - - files: ["web_src/**/*"] - globals: - __webpack_public_path__: true - process: false # https://github.com/webpack/webpack/issues/15833 - - files: ["web_src/**/*", "docs/**/*"] - env: - browser: true - node: false - - files: ["web_src/**/*worker.*"] - env: - worker: true - rules: - no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, status, statusbar, stop, toolbar, top] - - files: ["*.config.*"] - rules: - import-x/no-unused-modules: [0] - - files: ["**/*.d.ts"] - rules: - import-x/no-unused-modules: [0] - "@typescript-eslint/consistent-type-definitions": [0] - "@typescript-eslint/consistent-type-imports": [0] - - files: ["web_src/js/types.ts"] - rules: - import-x/no-unused-modules: [0] - - files: ["**/*.test.*", "web_src/js/test/setup.ts"] - env: - vitest-globals/env: true - rules: - vitest/consistent-test-filename: [0] - vitest/consistent-test-it: [0] - vitest/expect-expect: [0] - vitest/max-expects: [0] - vitest/max-nested-describe: [0] - vitest/no-alias-methods: [0] - vitest/no-commented-out-tests: [0] - vitest/no-conditional-expect: [0] - vitest/no-conditional-in-test: [0] - vitest/no-conditional-tests: [0] - vitest/no-disabled-tests: [0] - vitest/no-done-callback: [0] - vitest/no-duplicate-hooks: [0] - vitest/no-focused-tests: [0] - vitest/no-hooks: [0] - vitest/no-identical-title: [2] - vitest/no-interpolation-in-snapshots: [0] - vitest/no-large-snapshots: [0] - vitest/no-mocks-import: [0] - vitest/no-restricted-matchers: [0] - vitest/no-restricted-vi-methods: [0] - vitest/no-standalone-expect: [0] - vitest/no-test-prefixes: [0] - vitest/no-test-return-statement: [0] - vitest/prefer-called-with: [0] - vitest/prefer-comparison-matcher: [0] - vitest/prefer-each: [0] - vitest/prefer-equality-matcher: [0] - vitest/prefer-expect-resolves: [0] - vitest/prefer-hooks-in-order: [0] - vitest/prefer-hooks-on-top: [2] - vitest/prefer-lowercase-title: [0] - vitest/prefer-mock-promise-shorthand: [0] - vitest/prefer-snapshot-hint: [0] - vitest/prefer-spy-on: [0] - vitest/prefer-strict-equal: [0] - vitest/prefer-to-be: [0] - vitest/prefer-to-be-falsy: [0] - vitest/prefer-to-be-object: [0] - vitest/prefer-to-be-truthy: [0] - vitest/prefer-to-contain: [0] - vitest/prefer-to-have-length: [0] - vitest/prefer-todo: [0] - vitest/require-hook: [0] - vitest/require-to-throw-message: [0] - vitest/require-top-level-describe: [0] - vitest/valid-describe-callback: [2] - vitest/valid-expect: [2] - vitest/valid-title: [2] - - files: ["web_src/js/modules/fetch.ts", "web_src/js/standalone/**/*"] - rules: - no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression] - - files: ["**/*.vue"] - plugins: - - eslint-plugin-vue - - eslint-plugin-vue-scoped-css - extends: - - plugin:vue/vue3-recommended - - plugin:vue-scoped-css/vue3-recommended - rules: - vue/attributes-order: [0] - vue/html-closing-bracket-spacing: [2, {startTag: never, endTag: never, selfClosingTag: never}] - vue/max-attributes-per-line: [0] - vue/singleline-html-element-content-newline: [0] - - files: ["tests/e2e/**"] - plugins: - - eslint-plugin-playwright - extends: plugin:playwright/recommended - -rules: - "@eslint-community/eslint-comments/disable-enable-pair": [2] - "@eslint-community/eslint-comments/no-aggregating-enable": [2] - "@eslint-community/eslint-comments/no-duplicate-disable": [2] - "@eslint-community/eslint-comments/no-restricted-disable": [0] - "@eslint-community/eslint-comments/no-unlimited-disable": [2] - "@eslint-community/eslint-comments/no-unused-disable": [2] - "@eslint-community/eslint-comments/no-unused-enable": [2] - "@eslint-community/eslint-comments/no-use": [0] - "@eslint-community/eslint-comments/require-description": [0] - "@stylistic/js/array-bracket-newline": [0] - "@stylistic/js/array-bracket-spacing": [2, never] - "@stylistic/js/array-element-newline": [0] - "@stylistic/js/arrow-parens": [2, always] - "@stylistic/js/arrow-spacing": [2, {before: true, after: true}] - "@stylistic/js/block-spacing": [0] - "@stylistic/js/brace-style": [2, 1tbs, {allowSingleLine: true}] - "@stylistic/js/comma-dangle": [2, always-multiline] - "@stylistic/js/comma-spacing": [2, {before: false, after: true}] - "@stylistic/js/comma-style": [2, last] - "@stylistic/js/computed-property-spacing": [2, never] - "@stylistic/js/dot-location": [2, property] - "@stylistic/js/eol-last": [2] - "@stylistic/js/function-call-argument-newline": [0] - "@stylistic/js/function-call-spacing": [2, never] - "@stylistic/js/function-paren-newline": [0] - "@stylistic/js/generator-star-spacing": [0] - "@stylistic/js/implicit-arrow-linebreak": [0] - "@stylistic/js/indent": [2, 2, {ignoreComments: true, SwitchCase: 1}] - "@stylistic/js/key-spacing": [2] - "@stylistic/js/keyword-spacing": [2] - "@stylistic/js/line-comment-position": [0] - "@stylistic/js/linebreak-style": [2, unix] - "@stylistic/js/lines-around-comment": [0] - "@stylistic/js/lines-between-class-members": [0] - "@stylistic/js/max-len": [0] - "@stylistic/js/max-statements-per-line": [0] - "@stylistic/js/multiline-comment-style": [0] - "@stylistic/js/multiline-ternary": [0] - "@stylistic/js/new-parens": [2] - "@stylistic/js/newline-per-chained-call": [0] - "@stylistic/js/no-confusing-arrow": [0] - "@stylistic/js/no-extra-parens": [0] - "@stylistic/js/no-extra-semi": [2] - "@stylistic/js/no-floating-decimal": [0] - "@stylistic/js/no-mixed-operators": [0] - "@stylistic/js/no-mixed-spaces-and-tabs": [2] - "@stylistic/js/no-multi-spaces": [2, {ignoreEOLComments: true, exceptions: {Property: true}}] - "@stylistic/js/no-multiple-empty-lines": [2, {max: 1, maxEOF: 0, maxBOF: 0}] - "@stylistic/js/no-tabs": [2] - "@stylistic/js/no-trailing-spaces": [2] - "@stylistic/js/no-whitespace-before-property": [2] - "@stylistic/js/nonblock-statement-body-position": [2] - "@stylistic/js/object-curly-newline": [0] - "@stylistic/js/object-curly-spacing": [2, never] - "@stylistic/js/object-property-newline": [0] - "@stylistic/js/one-var-declaration-per-line": [0] - "@stylistic/js/operator-linebreak": [2, after] - "@stylistic/js/padded-blocks": [2, never] - "@stylistic/js/padding-line-between-statements": [0] - "@stylistic/js/quote-props": [0] - "@stylistic/js/quotes": [2, single, {avoidEscape: true, allowTemplateLiterals: true}] - "@stylistic/js/rest-spread-spacing": [2, never] - "@stylistic/js/semi": [2, always, {omitLastInOneLineBlock: true}] - "@stylistic/js/semi-spacing": [2, {before: false, after: true}] - "@stylistic/js/semi-style": [2, last] - "@stylistic/js/space-before-blocks": [2, always] - "@stylistic/js/space-before-function-paren": [2, {anonymous: ignore, named: never, asyncArrow: always}] - "@stylistic/js/space-in-parens": [2, never] - "@stylistic/js/space-infix-ops": [2] - "@stylistic/js/space-unary-ops": [2] - "@stylistic/js/spaced-comment": [2, always] - "@stylistic/js/switch-colon-spacing": [2] - "@stylistic/js/template-curly-spacing": [2, never] - "@stylistic/js/template-tag-spacing": [2, never] - "@stylistic/js/wrap-iife": [2, inside] - "@stylistic/js/wrap-regex": [0] - "@stylistic/js/yield-star-spacing": [2, after] - "@typescript-eslint/adjacent-overload-signatures": [0] - "@typescript-eslint/array-type": [0] - "@typescript-eslint/await-thenable": [2] - "@typescript-eslint/ban-ts-comment": [2, {'ts-expect-error': false, 'ts-ignore': true, 'ts-nocheck': false, 'ts-check': false}] - "@typescript-eslint/ban-tslint-comment": [0] - "@typescript-eslint/class-literal-property-style": [0] - "@typescript-eslint/class-methods-use-this": [0] - "@typescript-eslint/consistent-generic-constructors": [0] - "@typescript-eslint/consistent-indexed-object-style": [0] - "@typescript-eslint/consistent-return": [0] - "@typescript-eslint/consistent-type-assertions": [2, {assertionStyle: as, objectLiteralTypeAssertions: allow}] - "@typescript-eslint/consistent-type-definitions": [2, type] - "@typescript-eslint/consistent-type-exports": [2, {fixMixedExportsWithInlineTypeSpecifier: false}] - "@typescript-eslint/consistent-type-imports": [2, {prefer: type-imports, fixStyle: separate-type-imports, disallowTypeAnnotations: true}] - "@typescript-eslint/default-param-last": [0] - "@typescript-eslint/dot-notation": [0] - "@typescript-eslint/explicit-function-return-type": [0] - "@typescript-eslint/explicit-member-accessibility": [0] - "@typescript-eslint/explicit-module-boundary-types": [0] - "@typescript-eslint/init-declarations": [0] - "@typescript-eslint/max-params": [0] - "@typescript-eslint/member-ordering": [0] - "@typescript-eslint/method-signature-style": [0] - "@typescript-eslint/naming-convention": [0] - "@typescript-eslint/no-array-constructor": [2] - "@typescript-eslint/no-array-delete": [2] - "@typescript-eslint/no-base-to-string": [0] - "@typescript-eslint/no-confusing-non-null-assertion": [2] - "@typescript-eslint/no-confusing-void-expression": [0] - "@typescript-eslint/no-deprecated": [2] - "@typescript-eslint/no-dupe-class-members": [0] - "@typescript-eslint/no-duplicate-enum-values": [2] - "@typescript-eslint/no-duplicate-type-constituents": [2, {ignoreUnions: true}] - "@typescript-eslint/no-dynamic-delete": [0] - "@typescript-eslint/no-empty-function": [0] - "@typescript-eslint/no-empty-interface": [0] - "@typescript-eslint/no-empty-object-type": [2] - "@typescript-eslint/no-explicit-any": [0] - "@typescript-eslint/no-extra-non-null-assertion": [2] - "@typescript-eslint/no-extraneous-class": [0] - "@typescript-eslint/no-floating-promises": [0] - "@typescript-eslint/no-for-in-array": [2] - "@typescript-eslint/no-implied-eval": [2] - "@typescript-eslint/no-import-type-side-effects": [0] # dupe with consistent-type-imports - "@typescript-eslint/no-inferrable-types": [0] - "@typescript-eslint/no-invalid-this": [0] - "@typescript-eslint/no-invalid-void-type": [0] - "@typescript-eslint/no-loop-func": [0] - "@typescript-eslint/no-loss-of-precision": [0] - "@typescript-eslint/no-magic-numbers": [0] - "@typescript-eslint/no-meaningless-void-operator": [0] - "@typescript-eslint/no-misused-new": [2] - "@typescript-eslint/no-misused-promises": [2, {checksVoidReturn: {attributes: false, arguments: false}}] - "@typescript-eslint/no-mixed-enums": [0] - "@typescript-eslint/no-namespace": [2] - "@typescript-eslint/no-non-null-asserted-nullish-coalescing": [0] - "@typescript-eslint/no-non-null-asserted-optional-chain": [2] - "@typescript-eslint/no-non-null-assertion": [0] - "@typescript-eslint/no-redeclare": [0] - "@typescript-eslint/no-redundant-type-constituents": [2] - "@typescript-eslint/no-require-imports": [2] - "@typescript-eslint/no-restricted-imports": [0] - "@typescript-eslint/no-restricted-types": [0] - "@typescript-eslint/no-shadow": [0] - "@typescript-eslint/no-this-alias": [0] # handled by unicorn/no-this-assignment - "@typescript-eslint/no-unnecessary-boolean-literal-compare": [0] - "@typescript-eslint/no-unnecessary-condition": [0] - "@typescript-eslint/no-unnecessary-qualifier": [0] - "@typescript-eslint/no-unnecessary-template-expression": [0] - "@typescript-eslint/no-unnecessary-type-arguments": [0] - "@typescript-eslint/no-unnecessary-type-assertion": [2] - "@typescript-eslint/no-unnecessary-type-constraint": [2] - "@typescript-eslint/no-unsafe-argument": [0] - "@typescript-eslint/no-unsafe-assignment": [0] - "@typescript-eslint/no-unsafe-call": [0] - "@typescript-eslint/no-unsafe-declaration-merging": [2] - "@typescript-eslint/no-unsafe-enum-comparison": [2] - "@typescript-eslint/no-unsafe-function-type": [2] - "@typescript-eslint/no-unsafe-member-access": [0] - "@typescript-eslint/no-unsafe-return": [0] - "@typescript-eslint/no-unsafe-unary-minus": [2] - "@typescript-eslint/no-unused-expressions": [0] - "@typescript-eslint/no-unused-vars": [2, {vars: all, args: all, caughtErrors: all, ignoreRestSiblings: false, argsIgnorePattern: ^_, varsIgnorePattern: ^_, caughtErrorsIgnorePattern: ^_, destructuredArrayIgnorePattern: ^_}] - "@typescript-eslint/no-use-before-define": [0] - "@typescript-eslint/no-useless-constructor": [0] - "@typescript-eslint/no-useless-empty-export": [0] - "@typescript-eslint/no-wrapper-object-types": [2] - "@typescript-eslint/non-nullable-type-assertion-style": [0] - "@typescript-eslint/only-throw-error": [2] - "@typescript-eslint/parameter-properties": [0] - "@typescript-eslint/prefer-as-const": [2] - "@typescript-eslint/prefer-destructuring": [0] - "@typescript-eslint/prefer-enum-initializers": [0] - "@typescript-eslint/prefer-find": [2] - "@typescript-eslint/prefer-for-of": [2] - "@typescript-eslint/prefer-function-type": [2] - "@typescript-eslint/prefer-includes": [2] - "@typescript-eslint/prefer-literal-enum-member": [0] - "@typescript-eslint/prefer-namespace-keyword": [0] - "@typescript-eslint/prefer-nullish-coalescing": [0] - "@typescript-eslint/prefer-optional-chain": [2, {requireNullish: true}] - "@typescript-eslint/prefer-promise-reject-errors": [0] - "@typescript-eslint/prefer-readonly": [0] - "@typescript-eslint/prefer-readonly-parameter-types": [0] - "@typescript-eslint/prefer-reduce-type-parameter": [0] - "@typescript-eslint/prefer-regexp-exec": [0] - "@typescript-eslint/prefer-return-this-type": [0] - "@typescript-eslint/prefer-string-starts-ends-with": [2, {allowSingleElementEquality: always}] - "@typescript-eslint/promise-function-async": [0] - "@typescript-eslint/require-array-sort-compare": [0] - "@typescript-eslint/require-await": [0] - "@typescript-eslint/restrict-plus-operands": [2] - "@typescript-eslint/restrict-template-expressions": [0] - "@typescript-eslint/return-await": [0] - "@typescript-eslint/strict-boolean-expressions": [0] - "@typescript-eslint/switch-exhaustiveness-check": [0] - "@typescript-eslint/triple-slash-reference": [2] - "@typescript-eslint/typedef": [0] - "@typescript-eslint/unbound-method": [0] # too many false-positives - "@typescript-eslint/unified-signatures": [2] - accessor-pairs: [2] - array-callback-return: [2, {checkForEach: true}] - array-func/avoid-reverse: [2] - array-func/from-map: [2] - array-func/no-unnecessary-this-arg: [2] - array-func/prefer-array-from: [2] - array-func/prefer-flat-map: [0] # handled by unicorn/prefer-array-flat-map - array-func/prefer-flat: [0] # handled by unicorn/prefer-array-flat - arrow-body-style: [0] - block-scoped-var: [2] - camelcase: [0] - capitalized-comments: [0] - class-methods-use-this: [0] - complexity: [0] - consistent-return: [0] - consistent-this: [0] - constructor-super: [2] - curly: [0] - default-case-last: [2] - default-case: [0] - default-param-last: [0] - dot-notation: [0] - eqeqeq: [2] - for-direction: [2] - func-name-matching: [2] - func-names: [0] - func-style: [0] - getter-return: [2] - github/a11y-aria-label-is-well-formatted: [0] - github/a11y-no-title-attribute: [0] - github/a11y-no-visually-hidden-interactive-element: [0] - github/a11y-role-supports-aria-props: [0] - github/a11y-svg-has-accessible-name: [0] - github/array-foreach: [0] - github/async-currenttarget: [2] - github/async-preventdefault: [2] - github/authenticity-token: [0] - github/get-attribute: [0] - github/js-class-name: [0] - github/no-blur: [0] - github/no-d-none: [0] - github/no-dataset: [2] - github/no-dynamic-script-tag: [2] - github/no-implicit-buggy-globals: [2] - github/no-inner-html: [0] - github/no-innerText: [2] - github/no-then: [2] - github/no-useless-passive: [2] - github/prefer-observers: [2] - github/require-passive-events: [2] - github/unescaped-html-literal: [0] - grouped-accessor-pairs: [2] - guard-for-in: [0] - id-blacklist: [0] - id-length: [0] - id-match: [0] - import-x/consistent-type-specifier-style: [0] - import-x/default: [0] - import-x/dynamic-import-chunkname: [0] - import-x/export: [2] - import-x/exports-last: [0] - import-x/extensions: [2, always, {ignorePackages: true}] - import-x/first: [2] - import-x/group-exports: [0] - import-x/max-dependencies: [0] - import-x/named: [2] - import-x/namespace: [0] - import-x/newline-after-import: [0] - import-x/no-absolute-path: [0] - import-x/no-amd: [2] - import-x/no-anonymous-default-export: [0] - import-x/no-commonjs: [2] - import-x/no-cycle: [2, {ignoreExternal: true, maxDepth: 1}] - import-x/no-default-export: [0] - import-x/no-deprecated: [0] - import-x/no-dynamic-require: [0] - import-x/no-empty-named-blocks: [2] - import-x/no-extraneous-dependencies: [2] - import-x/no-import-module-exports: [0] - import-x/no-internal-modules: [0] - import-x/no-mutable-exports: [0] - import-x/no-named-as-default-member: [0] - import-x/no-named-as-default: [0] - import-x/no-named-default: [0] - import-x/no-named-export: [0] - import-x/no-namespace: [0] - import-x/no-nodejs-modules: [0] - import-x/no-relative-packages: [0] - import-x/no-relative-parent-imports: [0] - import-x/no-restricted-paths: [0] - import-x/no-self-import: [2] - import-x/no-unassigned-import: [0] - import-x/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$"]}] - import-x/no-unused-modules: [2, {unusedExports: true}] - import-x/no-useless-path-segments: [2, {commonjs: true}] - import-x/no-webpack-loader-syntax: [2] - import-x/order: [0] - import-x/prefer-default-export: [0] - import-x/unambiguous: [0] - init-declarations: [0] - line-comment-position: [0] - logical-assignment-operators: [0] - max-classes-per-file: [0] - max-depth: [0] - max-lines-per-function: [0] - max-lines: [0] - max-nested-callbacks: [0] - max-params: [0] - max-statements: [0] - multiline-comment-style: [2, separate-lines] - new-cap: [0] - no-alert: [0] - no-array-constructor: [0] # handled by @typescript-eslint/no-array-constructor - no-async-promise-executor: [0] - no-await-in-loop: [0] - no-bitwise: [0] - no-buffer-constructor: [0] - no-caller: [2] - no-case-declarations: [2] - no-class-assign: [2] - no-compare-neg-zero: [2] - no-cond-assign: [2, except-parens] - no-console: [1, {allow: [debug, info, warn, error]}] - no-const-assign: [2] - no-constant-binary-expression: [2] - no-constant-condition: [0] - no-constructor-return: [2] - no-continue: [0] - no-control-regex: [0] - no-debugger: [1] - no-delete-var: [2] - no-div-regex: [0] - no-dupe-args: [2] - no-dupe-class-members: [2] - no-dupe-else-if: [2] - no-dupe-keys: [2] - no-duplicate-case: [2] - no-duplicate-imports: [0] - no-else-return: [2] - no-empty-character-class: [2] - no-empty-function: [0] - no-empty-pattern: [2] - no-empty-static-block: [2] - no-empty: [2, {allowEmptyCatch: true}] - no-eq-null: [2] - no-eval: [2] - no-ex-assign: [2] - no-extend-native: [2] - no-extra-bind: [2] - no-extra-boolean-cast: [2] - no-extra-label: [0] - no-fallthrough: [2] - no-func-assign: [2] - no-global-assign: [2] - no-implicit-coercion: [2] - no-implicit-globals: [0] - no-implied-eval: [0] # handled by @typescript-eslint/no-implied-eval - no-import-assign: [2] - no-inline-comments: [0] - no-inner-declarations: [2] - no-invalid-regexp: [2] - no-invalid-this: [0] - no-irregular-whitespace: [2] - no-iterator: [2] - no-jquery/no-ajax-events: [2] - no-jquery/no-ajax: [2] - no-jquery/no-and-self: [2] - no-jquery/no-animate-toggle: [2] - no-jquery/no-animate: [2] - no-jquery/no-append-html: [2] - no-jquery/no-attr: [2] - no-jquery/no-bind: [2] - no-jquery/no-box-model: [2] - no-jquery/no-browser: [2] - no-jquery/no-camel-case: [2] - no-jquery/no-class-state: [2] - no-jquery/no-class: [0] - no-jquery/no-clone: [2] - no-jquery/no-closest: [0] - no-jquery/no-constructor-attributes: [2] - no-jquery/no-contains: [2] - no-jquery/no-context-prop: [2] - no-jquery/no-css: [2] - no-jquery/no-data: [0] - no-jquery/no-deferred: [2] - no-jquery/no-delegate: [2] - no-jquery/no-done-fail: [2] - no-jquery/no-each-collection: [0] - no-jquery/no-each-util: [0] - no-jquery/no-each: [0] - no-jquery/no-error-shorthand: [2] - no-jquery/no-error: [2] - no-jquery/no-escape-selector: [2] - no-jquery/no-event-shorthand: [2] - no-jquery/no-extend: [2] - no-jquery/no-fade: [2] - no-jquery/no-filter: [0] - no-jquery/no-find-collection: [0] - no-jquery/no-find-util: [2] - no-jquery/no-find: [0] - no-jquery/no-fx-interval: [2] - no-jquery/no-fx: [2] - no-jquery/no-global-eval: [2] - no-jquery/no-global-selector: [0] - no-jquery/no-grep: [2] - no-jquery/no-has: [2] - no-jquery/no-hold-ready: [2] - no-jquery/no-html: [0] - no-jquery/no-in-array: [2] - no-jquery/no-is-array: [2] - no-jquery/no-is-empty-object: [2] - no-jquery/no-is-function: [2] - no-jquery/no-is-numeric: [2] - no-jquery/no-is-plain-object: [2] - no-jquery/no-is-window: [2] - no-jquery/no-is: [2] - no-jquery/no-jquery-constructor: [0] - no-jquery/no-live: [2] - no-jquery/no-load-shorthand: [2] - no-jquery/no-load: [2] - no-jquery/no-map-collection: [0] - no-jquery/no-map-util: [2] - no-jquery/no-map: [2] - no-jquery/no-merge: [2] - no-jquery/no-node-name: [2] - no-jquery/no-noop: [2] - no-jquery/no-now: [2] - no-jquery/no-on-ready: [2] - no-jquery/no-other-methods: [0] - no-jquery/no-other-utils: [2] - no-jquery/no-param: [2] - no-jquery/no-parent: [0] - no-jquery/no-parents: [2] - no-jquery/no-parse-html-literal: [2] - no-jquery/no-parse-html: [2] - no-jquery/no-parse-json: [2] - no-jquery/no-parse-xml: [2] - no-jquery/no-prop: [2] - no-jquery/no-proxy: [2] - no-jquery/no-ready-shorthand: [2] - no-jquery/no-ready: [2] - no-jquery/no-selector-prop: [2] - no-jquery/no-serialize: [2] - no-jquery/no-size: [2] - no-jquery/no-sizzle: [2] - no-jquery/no-slide: [2] - no-jquery/no-sub: [2] - no-jquery/no-support: [2] - no-jquery/no-text: [2] - no-jquery/no-trigger: [0] - no-jquery/no-trim: [2] - no-jquery/no-type: [2] - no-jquery/no-unique: [2] - no-jquery/no-unload-shorthand: [2] - no-jquery/no-val: [0] - no-jquery/no-visibility: [2] - no-jquery/no-when: [2] - no-jquery/no-wrap: [2] - no-jquery/variable-pattern: [2] - no-label-var: [2] - no-labels: [0] # handled by no-restricted-syntax - no-lone-blocks: [2] - no-lonely-if: [0] - no-loop-func: [0] - no-loss-of-precision: [2] - no-magic-numbers: [0] - no-misleading-character-class: [2] - no-multi-assign: [0] - no-multi-str: [2] - no-negated-condition: [0] - no-nested-ternary: [0] - no-new-func: [2] - no-new-native-nonconstructor: [2] - no-new-object: [2] - no-new-symbol: [2] - no-new-wrappers: [2] - no-new: [0] - no-nonoctal-decimal-escape: [2] - no-obj-calls: [2] - no-octal-escape: [2] - no-octal: [2] - no-param-reassign: [0] - no-plusplus: [0] - no-promise-executor-return: [0] - no-proto: [2] - no-prototype-builtins: [2] - no-redeclare: [0] # must be disabled for typescript overloads - no-regex-spaces: [2] - no-restricted-exports: [0] - no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, location, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, self, status, statusbar, stop, toolbar, top, __dirname, __filename] - no-restricted-imports: [0] - no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression, {selector: "CallExpression[callee.name='fetch']", message: "use modules/fetch.ts instead"}] - no-return-assign: [0] - no-script-url: [2] - no-self-assign: [2, {props: true}] - no-self-compare: [2] - no-sequences: [2] - no-setter-return: [2] - no-shadow-restricted-names: [2] - no-shadow: [0] - no-sparse-arrays: [2] - no-template-curly-in-string: [2] - no-ternary: [0] - no-this-before-super: [2] - no-throw-literal: [2] - no-undef-init: [2] - no-undef: [2, {typeof: true}] # TODO: disable this rule after tsc passes - no-undefined: [0] - no-underscore-dangle: [0] - no-unexpected-multiline: [2] - no-unmodified-loop-condition: [2] - no-unneeded-ternary: [2] - no-unreachable-loop: [2] - no-unreachable: [2] - no-unsafe-finally: [2] - no-unsafe-negation: [2] - no-unused-expressions: [2] - no-unused-labels: [2] - no-unused-private-class-members: [2] - no-unused-vars: [0] # handled by @typescript-eslint/no-unused-vars - no-use-before-define: [2, {functions: false, classes: true, variables: true, allowNamedExports: true}] - no-use-extend-native/no-use-extend-native: [2] - no-useless-backreference: [2] - no-useless-call: [2] - no-useless-catch: [2] - no-useless-computed-key: [2] - no-useless-concat: [2] - no-useless-constructor: [2] - no-useless-escape: [2] - no-useless-rename: [2] - no-useless-return: [2] - no-var: [2] - no-void: [2] - no-warning-comments: [0] - no-with: [0] # handled by no-restricted-syntax - object-shorthand: [2, always] - one-var-declaration-per-line: [0] - one-var: [0] - operator-assignment: [2, always] - operator-linebreak: [2, after] - prefer-arrow-callback: [2, {allowNamedFunctions: true, allowUnboundThis: true}] - prefer-const: [2, {destructuring: all, ignoreReadBeforeAssign: true}] - prefer-destructuring: [0] - prefer-exponentiation-operator: [2] - prefer-named-capture-group: [0] - prefer-numeric-literals: [2] - prefer-object-has-own: [2] - prefer-object-spread: [2] - prefer-promise-reject-errors: [2, {allowEmptyReject: false}] - prefer-regex-literals: [2] - prefer-rest-params: [2] - prefer-spread: [2] - prefer-template: [2] - radix: [2, as-needed] - regexp/confusing-quantifier: [2] - regexp/control-character-escape: [2] - regexp/hexadecimal-escape: [0] - regexp/letter-case: [0] - regexp/match-any: [2] - regexp/negation: [2] - regexp/no-contradiction-with-assertion: [0] - regexp/no-control-character: [0] - regexp/no-dupe-characters-character-class: [2] - regexp/no-dupe-disjunctions: [2] - regexp/no-empty-alternative: [2] - regexp/no-empty-capturing-group: [2] - regexp/no-empty-character-class: [0] - regexp/no-empty-group: [2] - regexp/no-empty-lookarounds-assertion: [2] - regexp/no-empty-string-literal: [2] - regexp/no-escape-backspace: [2] - regexp/no-extra-lookaround-assertions: [0] - regexp/no-invalid-regexp: [2] - regexp/no-invisible-character: [2] - regexp/no-lazy-ends: [2] - regexp/no-legacy-features: [2] - regexp/no-misleading-capturing-group: [0] - regexp/no-misleading-unicode-character: [0] - regexp/no-missing-g-flag: [2] - regexp/no-non-standard-flag: [2] - regexp/no-obscure-range: [2] - regexp/no-octal: [2] - regexp/no-optional-assertion: [2] - regexp/no-potentially-useless-backreference: [2] - regexp/no-standalone-backslash: [2] - regexp/no-super-linear-backtracking: [0] - regexp/no-super-linear-move: [0] - regexp/no-trivially-nested-assertion: [2] - regexp/no-trivially-nested-quantifier: [2] - regexp/no-unused-capturing-group: [0] - regexp/no-useless-assertions: [2] - regexp/no-useless-backreference: [2] - regexp/no-useless-character-class: [2] - regexp/no-useless-dollar-replacements: [2] - regexp/no-useless-escape: [2] - regexp/no-useless-flag: [2] - regexp/no-useless-lazy: [2] - regexp/no-useless-non-capturing-group: [2] - regexp/no-useless-quantifier: [2] - regexp/no-useless-range: [2] - regexp/no-useless-set-operand: [2] - regexp/no-useless-string-literal: [2] - regexp/no-useless-two-nums-quantifier: [2] - regexp/no-zero-quantifier: [2] - regexp/optimal-lookaround-quantifier: [2] - regexp/optimal-quantifier-concatenation: [0] - regexp/prefer-character-class: [0] - regexp/prefer-d: [0] - regexp/prefer-escape-replacement-dollar-char: [0] - regexp/prefer-lookaround: [0] - regexp/prefer-named-backreference: [0] - regexp/prefer-named-capture-group: [0] - regexp/prefer-named-replacement: [0] - regexp/prefer-plus-quantifier: [2] - regexp/prefer-predefined-assertion: [2] - regexp/prefer-quantifier: [0] - regexp/prefer-question-quantifier: [2] - regexp/prefer-range: [2] - regexp/prefer-regexp-exec: [2] - regexp/prefer-regexp-test: [2] - regexp/prefer-result-array-groups: [0] - regexp/prefer-set-operation: [2] - regexp/prefer-star-quantifier: [2] - regexp/prefer-unicode-codepoint-escapes: [2] - regexp/prefer-w: [0] - regexp/require-unicode-regexp: [0] - regexp/simplify-set-operations: [2] - regexp/sort-alternatives: [0] - regexp/sort-character-class-elements: [0] - regexp/sort-flags: [0] - regexp/strict: [2] - regexp/unicode-escape: [0] - regexp/use-ignore-case: [0] - require-atomic-updates: [0] - require-await: [0] # handled by @typescript-eslint/require-await - require-unicode-regexp: [0] - require-yield: [2] - sonarjs/cognitive-complexity: [0] - sonarjs/elseif-without-else: [0] - sonarjs/max-switch-cases: [0] - sonarjs/no-all-duplicated-branches: [2] - sonarjs/no-collapsible-if: [0] - sonarjs/no-collection-size-mischeck: [2] - sonarjs/no-duplicate-string: [0] - sonarjs/no-duplicated-branches: [0] - sonarjs/no-element-overwrite: [2] - sonarjs/no-empty-collection: [2] - sonarjs/no-extra-arguments: [2] - sonarjs/no-gratuitous-expressions: [2] - sonarjs/no-identical-conditions: [2] - sonarjs/no-identical-expressions: [2] - sonarjs/no-identical-functions: [2, 5] - sonarjs/no-ignored-return: [2] - sonarjs/no-inverted-boolean-check: [2] - sonarjs/no-nested-switch: [0] - sonarjs/no-nested-template-literals: [0] - sonarjs/no-one-iteration-loop: [2] - sonarjs/no-redundant-boolean: [2] - sonarjs/no-redundant-jump: [2] - sonarjs/no-same-line-conditional: [2] - sonarjs/no-small-switch: [0] - sonarjs/no-unused-collection: [2] - sonarjs/no-use-of-empty-return-value: [2] - sonarjs/no-useless-catch: [2] - sonarjs/non-existent-operator: [2] - sonarjs/prefer-immediate-return: [0] - sonarjs/prefer-object-literal: [0] - sonarjs/prefer-single-boolean-return: [0] - sonarjs/prefer-while: [2] - sort-imports: [0] - sort-keys: [0] - sort-vars: [0] - strict: [0] - symbol-description: [2] - unicode-bom: [2, never] - unicorn/better-regex: [0] - unicorn/catch-error-name: [0] - unicorn/consistent-destructuring: [2] - unicorn/consistent-empty-array-spread: [2] - unicorn/consistent-existence-index-check: [0] - unicorn/consistent-function-scoping: [0] - unicorn/custom-error-definition: [0] - unicorn/empty-brace-spaces: [2] - unicorn/error-message: [0] - unicorn/escape-case: [0] - unicorn/expiring-todo-comments: [0] - unicorn/explicit-length-check: [0] - unicorn/filename-case: [0] - unicorn/import-index: [0] - 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] - unicorn/no-for-loop: [0] - unicorn/no-hex-escape: [0] - unicorn/no-instanceof-array: [0] - unicorn/no-invalid-fetch-options: [2] - unicorn/no-invalid-remove-event-listener: [2] - unicorn/no-keyword-prefix: [0] - unicorn/no-length-as-slice-end: [2] - unicorn/no-lonely-if: [2] - unicorn/no-magic-array-flat-depth: [0] - unicorn/no-negated-condition: [0] - unicorn/no-negation-in-equality-check: [2] - unicorn/no-nested-ternary: [0] - unicorn/no-new-array: [0] - unicorn/no-new-buffer: [0] - 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] - unicorn/no-typeof-undefined: [2] - unicorn/no-unnecessary-await: [2] - unicorn/no-unnecessary-polyfills: [2] - unicorn/no-unreadable-array-destructuring: [0] - unicorn/no-unreadable-iife: [2] - unicorn/no-unused-properties: [2] - unicorn/no-useless-fallback-in-spread: [2] - unicorn/no-useless-length-check: [2] - unicorn/no-useless-promise-resolve-reject: [2] - unicorn/no-useless-spread: [2] - unicorn/no-useless-switch-case: [2] - unicorn/no-useless-undefined: [0] - unicorn/no-zero-fractions: [2] - unicorn/number-literal-case: [0] - unicorn/numeric-separators-style: [0] - unicorn/prefer-add-event-listener: [2] - unicorn/prefer-array-find: [2] - unicorn/prefer-array-flat-map: [2] - unicorn/prefer-array-flat: [2] - unicorn/prefer-array-index-of: [2] - unicorn/prefer-array-some: [2] - unicorn/prefer-at: [0] - unicorn/prefer-blob-reading-methods: [2] - unicorn/prefer-code-point: [0] - unicorn/prefer-date-now: [2] - unicorn/prefer-default-parameters: [0] - unicorn/prefer-dom-node-append: [2] - unicorn/prefer-dom-node-dataset: [0] - unicorn/prefer-dom-node-remove: [2] - unicorn/prefer-dom-node-text-content: [2] - unicorn/prefer-event-target: [2] - unicorn/prefer-export-from: [0] - unicorn/prefer-global-this: [0] - unicorn/prefer-includes: [2] - unicorn/prefer-json-parse-buffer: [0] - unicorn/prefer-keyboard-event-key: [2] - unicorn/prefer-logical-operator-over-ternary: [2] - unicorn/prefer-math-min-max: [2] - unicorn/prefer-math-trunc: [2] - unicorn/prefer-modern-dom-apis: [0] - unicorn/prefer-modern-math-apis: [2] - unicorn/prefer-module: [2] - unicorn/prefer-native-coercion-functions: [2] - unicorn/prefer-negative-index: [2] - unicorn/prefer-node-protocol: [2] - unicorn/prefer-number-properties: [0] - unicorn/prefer-object-from-entries: [2] - unicorn/prefer-object-has-own: [0] - unicorn/prefer-optional-catch-binding: [2] - unicorn/prefer-prototype-methods: [0] - unicorn/prefer-query-selector: [2] - unicorn/prefer-reflect-apply: [0] - unicorn/prefer-regexp-test: [2] - unicorn/prefer-set-has: [0] - unicorn/prefer-set-size: [2] - unicorn/prefer-spread: [0] - unicorn/prefer-string-raw: [0] - unicorn/prefer-string-replace-all: [0] - unicorn/prefer-string-slice: [0] - unicorn/prefer-string-starts-ends-with: [2] - unicorn/prefer-string-trim-start-end: [2] - unicorn/prefer-structured-clone: [2] - unicorn/prefer-switch: [0] - unicorn/prefer-ternary: [0] - unicorn/prefer-text-content: [2] - unicorn/prefer-top-level-await: [0] - unicorn/prefer-type-error: [0] - unicorn/prevent-abbreviations: [0] - unicorn/relative-url-style: [2] - unicorn/require-array-join-separator: [2] - unicorn/require-number-to-fixed-digits-argument: [2] - unicorn/require-post-message-target-origin: [0] - unicorn/string-content: [0] - unicorn/switch-case-braces: [0] - unicorn/template-indent: [2] - unicorn/text-encoding-identifier-case: [0] - unicorn/throw-new-error: [2] - use-isnan: [2] - valid-typeof: [2, {requireStringLiterals: true}] - vars-on-top: [0] - wc/attach-shadow-constructor: [2] - wc/define-tag-after-class-definition: [0] - wc/expose-class-on-global: [0] - wc/file-name-matches-element: [2] - wc/guard-define-call: [0] - wc/guard-super-call: [2] - wc/max-elements-per-file: [0] - wc/no-child-traversal-in-attributechangedcallback: [2] - wc/no-child-traversal-in-connectedcallback: [2] - wc/no-closed-shadow-root: [2] - wc/no-constructor-attributes: [2] - wc/no-constructor-params: [2] - wc/no-constructor: [2] - wc/no-customized-built-in-elements: [2] - wc/no-exports-with-element: [0] - wc/no-invalid-element-name: [2] - wc/no-invalid-extends: [2] - wc/no-method-prefixed-with-on: [2] - wc/no-self-class: [2] - wc/no-typos: [2] - wc/require-listener-teardown: [2] - wc/tag-name-matches-class: [2] - yoda: [2, never] diff --git a/.golangci.yml b/.golangci.yml index 37617ad3652..c39d7ac5f2f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,6 +19,8 @@ linters: - revive - staticcheck - stylecheck + - tenv + - testifylint - typecheck - unconvert - unused @@ -34,6 +36,10 @@ output: show-stats: true linters-settings: + testifylint: + disable: + - go-require + - require-error stylecheck: checks: ["all", "-ST1005", "-ST1003"] nakedret: diff --git a/MAINTAINERS b/MAINTAINERS index 426181cbcf1..ad02ecc7557 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -46,7 +46,6 @@ 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) diff --git a/Makefile b/Makefile index 0cd6936b3bb..32ea823e1e7 100644 --- a/Makefile +++ b/Makefile @@ -26,17 +26,17 @@ COMMA := , XGO_VERSION := go-1.23.x AIR_PACKAGE ?= github.com/air-verse/air@v1 -EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0 +EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.0.3 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0 -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3 -GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 -MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1 +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2 +GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12 +MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1 GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1 -GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.15.3 +GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.17.0 DOCKER_IMAGE ?= gitea/gitea DOCKER_TAG ?= latest @@ -377,12 +377,12 @@ lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig .PHONY: lint-js lint-js: node_modules npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) -# npx vue-tsc + npx vue-tsc .PHONY: lint-js-fix lint-js-fix: node_modules npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix -# npx vue-tsc + npx vue-tsc .PHONY: lint-css lint-css: node_modules @@ -451,10 +451,6 @@ lint-templates: .venv node_modules lint-yaml: .venv @poetry run yamllint . -.PHONY: tsc -tsc: - npx vue-tsc - .PHONY: watch watch: @bash tools/watch.sh diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 5c23f70d7ca..6377ebf9d2a 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1040,9 +1040,13 @@ LEVEL = Info ;; Don't allow download source archive files from UI ;DISABLE_DOWNLOAD_SOURCE_ARCHIVES = false -;; Allow fork repositories without maximum number limit +;; Allow to fork repositories without maximum number limit ;ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT = true +;; Allow to fork repositories into the same owner (user or organization) +;; This feature is experimental, not fully tested, and may be changed in the future +;ALLOW_FORK_INTO_SAME_OWNER = false + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[repository.editor] diff --git a/go.mod b/go.mod index 17be4cbd526..9b99face841 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/ethantkoenig/rupture v1.0.1 github.com/felixge/fgprof v0.9.5 github.com/fsnotify/fsnotify v1.7.0 - github.com/gliderlabs/ssh v0.3.7 + github.com/gliderlabs/ssh v0.3.8 github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 github.com/go-chi/chi/v5 v5.1.0 @@ -123,7 +123,7 @@ require ( github.com/yuin/goldmark-meta v1.1.0 golang.org/x/crypto v0.31.0 golang.org/x/image v0.21.0 - golang.org/x/net v0.30.0 + golang.org/x/net v0.33.0 golang.org/x/oauth2 v0.23.0 golang.org/x/sync v0.10.0 golang.org/x/sys v0.28.0 diff --git a/go.sum b/go.sum index 73bdb44e337..93b2f9fa999 100644 --- a/go.sum +++ b/go.sum @@ -293,8 +293,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0= github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1/go.mod h1:fenKRzpXDjNpsIBhuhUzvjCKlDjKam0boRAenTE0Q6A= -github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= -github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-ap/activitypub v0.0.0-20240910141749-b4b8c8aa484c h1:82lzmsy5Nr6JA6HcLRVxGfbdSoWfW45C6jnY3zFS7Ks= @@ -932,8 +932,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/models/actions/run_job.go b/models/actions/run_job.go index 4b8664077dc..de4b6aab667 100644 --- a/models/actions/run_job.go +++ b/models/actions/run_job.go @@ -137,7 +137,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col if err != nil { return 0, err } - run.Status = aggregateJobStatus(jobs) + run.Status = AggregateJobStatus(jobs) if run.Started.IsZero() && run.Status.IsRunning() { run.Started = timeutil.TimeStampNow() } @@ -152,29 +152,35 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col return affected, nil } -func aggregateJobStatus(jobs []*ActionRunJob) Status { - allDone := true - allWaiting := true - hasFailure := false +func AggregateJobStatus(jobs []*ActionRunJob) Status { + allSuccessOrSkipped := len(jobs) != 0 + allSkipped := len(jobs) != 0 + var hasFailure, hasCancelled, hasWaiting, hasRunning, hasBlocked bool for _, job := range jobs { - if !job.Status.IsDone() { - allDone = false - } - if job.Status != StatusWaiting && !job.Status.IsDone() { - allWaiting = false - } - if job.Status == StatusFailure || job.Status == StatusCancelled { - hasFailure = true - } + allSuccessOrSkipped = allSuccessOrSkipped && (job.Status == StatusSuccess || job.Status == StatusSkipped) + allSkipped = allSkipped && job.Status == StatusSkipped + hasFailure = hasFailure || job.Status == StatusFailure + hasCancelled = hasCancelled || job.Status == StatusCancelled + hasWaiting = hasWaiting || job.Status == StatusWaiting + hasRunning = hasRunning || job.Status == StatusRunning + hasBlocked = hasBlocked || job.Status == StatusBlocked } - if allDone { - if hasFailure { - return StatusFailure - } + switch { + case allSkipped: + return StatusSkipped + case allSuccessOrSkipped: return StatusSuccess - } - if allWaiting { + case hasCancelled: + return StatusCancelled + case hasFailure: + return StatusFailure + case hasRunning: + return StatusRunning + case hasWaiting: return StatusWaiting + case hasBlocked: + return StatusBlocked + default: + return StatusUnknown // it shouldn't happen } - return StatusRunning } diff --git a/models/actions/run_job_status_test.go b/models/actions/run_job_status_test.go new file mode 100644 index 00000000000..523d38327e4 --- /dev/null +++ b/models/actions/run_job_status_test.go @@ -0,0 +1,85 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAggregateJobStatus(t *testing.T) { + testStatuses := func(expected Status, statuses []Status) { + t.Helper() + var jobs []*ActionRunJob + for _, v := range statuses { + jobs = append(jobs, &ActionRunJob{Status: v}) + } + actual := AggregateJobStatus(jobs) + if !assert.Equal(t, expected, actual) { + var statusStrings []string + for _, s := range statuses { + statusStrings = append(statusStrings, s.String()) + } + t.Errorf("AggregateJobStatus(%v) = %v, want %v", statusStrings, statusNames[actual], statusNames[expected]) + } + } + + cases := []struct { + statuses []Status + expected Status + }{ + // unknown cases, maybe it shouldn't happen in real world + {[]Status{}, StatusUnknown}, + {[]Status{StatusUnknown, StatusSuccess}, StatusUnknown}, + {[]Status{StatusUnknown, StatusSkipped}, StatusUnknown}, + {[]Status{StatusUnknown, StatusFailure}, StatusFailure}, + {[]Status{StatusUnknown, StatusCancelled}, StatusCancelled}, + {[]Status{StatusUnknown, StatusWaiting}, StatusWaiting}, + {[]Status{StatusUnknown, StatusRunning}, StatusRunning}, + {[]Status{StatusUnknown, StatusBlocked}, StatusBlocked}, + + // success with other status + {[]Status{StatusSuccess}, StatusSuccess}, + {[]Status{StatusSuccess, StatusSkipped}, StatusSuccess}, // skipped doesn't affect success + {[]Status{StatusSuccess, StatusFailure}, StatusFailure}, + {[]Status{StatusSuccess, StatusCancelled}, StatusCancelled}, + {[]Status{StatusSuccess, StatusWaiting}, StatusWaiting}, + {[]Status{StatusSuccess, StatusRunning}, StatusRunning}, + {[]Status{StatusSuccess, StatusBlocked}, StatusBlocked}, + + // any cancelled, then cancelled + {[]Status{StatusCancelled}, StatusCancelled}, + {[]Status{StatusCancelled, StatusSuccess}, StatusCancelled}, + {[]Status{StatusCancelled, StatusSkipped}, StatusCancelled}, + {[]Status{StatusCancelled, StatusFailure}, StatusCancelled}, + {[]Status{StatusCancelled, StatusWaiting}, StatusCancelled}, + {[]Status{StatusCancelled, StatusRunning}, StatusCancelled}, + {[]Status{StatusCancelled, StatusBlocked}, StatusCancelled}, + + // failure with other status, fail fast + // Should "running" win? Maybe no: old code does make "running" win, but GitHub does fail fast. + {[]Status{StatusFailure}, StatusFailure}, + {[]Status{StatusFailure, StatusSuccess}, StatusFailure}, + {[]Status{StatusFailure, StatusSkipped}, StatusFailure}, + {[]Status{StatusFailure, StatusCancelled}, StatusCancelled}, + {[]Status{StatusFailure, StatusWaiting}, StatusFailure}, + {[]Status{StatusFailure, StatusRunning}, StatusFailure}, + {[]Status{StatusFailure, StatusBlocked}, StatusFailure}, + + // skipped with other status + // "all skipped" is also considered as "mergeable" by "services/actions.toCommitStatus", the same as GitHub + {[]Status{StatusSkipped}, StatusSkipped}, + {[]Status{StatusSkipped, StatusSuccess}, StatusSuccess}, + {[]Status{StatusSkipped, StatusFailure}, StatusFailure}, + {[]Status{StatusSkipped, StatusCancelled}, StatusCancelled}, + {[]Status{StatusSkipped, StatusWaiting}, StatusWaiting}, + {[]Status{StatusSkipped, StatusRunning}, StatusRunning}, + {[]Status{StatusSkipped, StatusBlocked}, StatusBlocked}, + } + + for _, c := range cases { + testStatuses(c.expected, c.statuses) + } +} diff --git a/models/actions/runner_token_test.go b/models/actions/runner_token_test.go index e85e99abe53..159805e5f7c 100644 --- a/models/actions/runner_token_test.go +++ b/models/actions/runner_token_test.go @@ -17,7 +17,7 @@ func TestGetLatestRunnerToken(t *testing.T) { token := unittest.AssertExistsAndLoadBean(t, &ActionRunnerToken{ID: 3}) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) assert.NoError(t, err) - assert.EqualValues(t, token, expectedToken) + assert.EqualValues(t, expectedToken, token) } func TestNewRunnerToken(t *testing.T) { @@ -26,7 +26,7 @@ func TestNewRunnerToken(t *testing.T) { assert.NoError(t, err) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) assert.NoError(t, err) - assert.EqualValues(t, token, expectedToken) + assert.EqualValues(t, expectedToken, token) } func TestUpdateRunnerToken(t *testing.T) { @@ -36,5 +36,5 @@ func TestUpdateRunnerToken(t *testing.T) { assert.NoError(t, UpdateRunnerToken(db.DefaultContext, token)) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) assert.NoError(t, err) - assert.EqualValues(t, token, expectedToken) + assert.EqualValues(t, expectedToken, token) } diff --git a/models/activities/action.go b/models/activities/action.go index 65d95fbe667..ff7fdb2f106 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -511,7 +511,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder. } if opts.RequestedTeam != nil { - env := organization.OrgFromUser(opts.RequestedUser).AccessibleTeamReposEnv(ctx, opts.RequestedTeam) + env := repo_model.AccessibleTeamReposEnv(ctx, organization.OrgFromUser(opts.RequestedUser), opts.RequestedTeam) teamRepoIDs, err := env.RepoIDs(1, opts.RequestedUser.NumRepos) if err != nil { return nil, fmt.Errorf("GetTeamRepositories: %w", err) diff --git a/models/activities/user_heatmap_test.go b/models/activities/user_heatmap_test.go index b7babcbde1d..a039fd36132 100644 --- a/models/activities/user_heatmap_test.go +++ b/models/activities/user_heatmap_test.go @@ -4,7 +4,6 @@ package activities_test import ( - "fmt" "testing" "time" @@ -91,11 +90,11 @@ func TestGetUserHeatmapDataByUser(t *testing.T) { assert.NoError(t, err) assert.Len(t, actions, contributions, "invalid action count: did the test data became too old?") assert.Equal(t, count, int64(contributions)) - assert.Equal(t, tc.CountResult, contributions, fmt.Sprintf("testcase '%s'", tc.desc)) + assert.Equal(t, tc.CountResult, contributions, "testcase '%s'", tc.desc) // Test JSON rendering jsonData, err := json.Marshal(heatmap) assert.NoError(t, err) - assert.Equal(t, tc.JSONResult, string(jsonData)) + assert.JSONEq(t, tc.JSONResult, string(jsonData)) } } diff --git a/models/asymkey/error.go b/models/asymkey/error.go index 03bc82302f1..2e65d766129 100644 --- a/models/asymkey/error.go +++ b/models/asymkey/error.go @@ -217,6 +217,7 @@ func (err ErrGPGKeyAccessDenied) Unwrap() error { // ErrKeyAccessDenied represents a "KeyAccessDenied" kind of error. type ErrKeyAccessDenied struct { UserID int64 + RepoID int64 KeyID int64 Note string } @@ -228,8 +229,8 @@ func IsErrKeyAccessDenied(err error) bool { } func (err ErrKeyAccessDenied) Error() string { - return fmt.Sprintf("user does not have access to the key [user_id: %d, key_id: %d, note: %s]", - err.UserID, err.KeyID, err.Note) + return fmt.Sprintf("user does not have access to the key [user_id: %d, repo_id: %d, key_id: %d, note: %s]", + err.UserID, err.RepoID, err.KeyID, err.Note) } func (err ErrKeyAccessDenied) Unwrap() error { diff --git a/models/auth/oauth2_test.go b/models/auth/oauth2_test.go index 0829d31d51b..43daa0b5ec1 100644 --- a/models/auth/oauth2_test.go +++ b/models/auth/oauth2_test.go @@ -18,7 +18,7 @@ func TestOAuth2Application_GenerateClientSecret(t *testing.T) { app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) secret, err := app.GenerateClientSecret(db.DefaultContext) assert.NoError(t, err) - assert.True(t, len(secret) > 0) + assert.NotEmpty(t, secret) unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1, ClientSecret: app.ClientSecret}) } @@ -165,7 +165,7 @@ func TestOAuth2Grant_GenerateNewAuthorizationCode(t *testing.T) { code, err := grant.GenerateNewAuthorizationCode(db.DefaultContext, "https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256") assert.NoError(t, err) assert.NotNil(t, code) - assert.True(t, len(code.Code) > 32) // secret length > 32 + assert.Greater(t, len(code.Code), 32) // secret length > 32 } func TestOAuth2Grant_TableName(t *testing.T) { diff --git a/models/auth/source_test.go b/models/auth/source_test.go index 36e76d5e281..84aede0a6b0 100644 --- a/models/auth/source_test.go +++ b/models/auth/source_test.go @@ -13,6 +13,8 @@ import ( "code.gitea.io/gitea/modules/json" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "xorm.io/xorm" "xorm.io/xorm/schemas" ) @@ -54,7 +56,8 @@ func TestDumpAuthSource(t *testing.T) { sb := new(strings.Builder) - db.DumpTables([]*schemas.Table{authSourceSchema}, sb) - + // TODO: this test is quite hacky, it should use a low-level "select" (without model processors) but not a database dump + engine := db.GetEngine(db.DefaultContext).(*xorm.Engine) + require.NoError(t, engine.DumpTables([]*schemas.Table{authSourceSchema}, sb)) assert.Contains(t, sb.String(), `"Provider":"ConvertibleSourceName"`) } diff --git a/models/db/collation.go b/models/db/collation.go index a7db9f54423..79ade873803 100644 --- a/models/db/collation.go +++ b/models/db/collation.go @@ -140,7 +140,7 @@ func CheckCollations(x *xorm.Engine) (*CheckCollationsResult, error) { } func CheckCollationsDefaultEngine() (*CheckCollationsResult, error) { - return CheckCollations(x) + return CheckCollations(xormEngine) } func alterDatabaseCollation(x *xorm.Engine, collation string) error { diff --git a/models/db/context.go b/models/db/context.go index 171e26b933f..51627712b11 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -94,7 +94,7 @@ func GetEngine(ctx context.Context) Engine { if e := getExistingEngine(ctx); e != nil { return e } - return x.Context(ctx) + return xormEngine.Context(ctx) } // getExistingEngine gets an existing db Engine/Statement from this context or returns nil @@ -155,7 +155,7 @@ func TxContext(parentCtx context.Context) (*Context, Committer, error) { return newContext(parentCtx, sess), &halfCommitter{committer: sess}, nil } - sess := x.NewSession() + sess := xormEngine.NewSession() if err := sess.Begin(); err != nil { _ = sess.Close() return nil, nil, err @@ -179,7 +179,7 @@ func WithTx(parentCtx context.Context, f func(ctx context.Context) error) error } func txWithNoCheck(parentCtx context.Context, f func(ctx context.Context) error) error { - sess := x.NewSession() + sess := xormEngine.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { return err @@ -322,7 +322,7 @@ func CountByBean(ctx context.Context, bean any) (int64, error) { // TableName returns the table name according a bean object func TableName(bean any) string { - return x.TableName(bean) + return xormEngine.TableName(bean) } // InTransaction returns true if the engine is in a transaction otherwise return false diff --git a/models/db/convert.go b/models/db/convert.go index 8c124471aba..80b0f7b04b4 100644 --- a/models/db/convert.go +++ b/models/db/convert.go @@ -16,30 +16,30 @@ import ( // ConvertDatabaseTable converts database and tables from utf8 to utf8mb4 if it's mysql and set ROW_FORMAT=dynamic func ConvertDatabaseTable() error { - if x.Dialect().URI().DBType != schemas.MYSQL { + if xormEngine.Dialect().URI().DBType != schemas.MYSQL { return nil } - r, err := CheckCollations(x) + r, err := CheckCollations(xormEngine) if err != nil { return err } - _, err = x.Exec(fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET utf8mb4 COLLATE %s", setting.Database.Name, r.ExpectedCollation)) + _, err = xormEngine.Exec(fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET utf8mb4 COLLATE %s", setting.Database.Name, r.ExpectedCollation)) if err != nil { return err } - tables, err := x.DBMetas() + tables, err := xormEngine.DBMetas() if err != nil { return err } for _, table := range tables { - if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` ROW_FORMAT=dynamic", table.Name)); err != nil { + if _, err := xormEngine.Exec(fmt.Sprintf("ALTER TABLE `%s` ROW_FORMAT=dynamic", table.Name)); err != nil { return err } - if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` CONVERT TO CHARACTER SET utf8mb4 COLLATE %s", table.Name, r.ExpectedCollation)); err != nil { + if _, err := xormEngine.Exec(fmt.Sprintf("ALTER TABLE `%s` CONVERT TO CHARACTER SET utf8mb4 COLLATE %s", table.Name, r.ExpectedCollation)); err != nil { return err } } @@ -49,11 +49,11 @@ func ConvertDatabaseTable() error { // ConvertVarcharToNVarchar converts database and tables from varchar to nvarchar if it's mssql func ConvertVarcharToNVarchar() error { - if x.Dialect().URI().DBType != schemas.MSSQL { + if xormEngine.Dialect().URI().DBType != schemas.MSSQL { return nil } - sess := x.NewSession() + sess := xormEngine.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 diff --git a/models/db/engine.go b/models/db/engine.go index b17188945a4..91015f70384 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -8,17 +8,10 @@ import ( "context" "database/sql" "fmt" - "io" "reflect" "strings" - "time" - - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" "xorm.io/xorm" - "xorm.io/xorm/contexts" - "xorm.io/xorm/names" "xorm.io/xorm/schemas" _ "github.com/go-sql-driver/mysql" // Needed for the MySQL driver @@ -27,9 +20,9 @@ import ( ) var ( - x *xorm.Engine - tables []any - initFuncs []func() error + xormEngine *xorm.Engine + registeredModels []any + registeredInitFuncs []func() error ) // Engine represents a xorm engine or session. @@ -70,167 +63,38 @@ type Engine interface { // TableInfo returns table's information via an object func TableInfo(v any) (*schemas.Table, error) { - return x.TableInfo(v) + return xormEngine.TableInfo(v) } -// DumpTables dump tables information -func DumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error { - return x.DumpTables(tables, w, tp...) -} - -// RegisterModel registers model, if initfunc provided, it will be invoked after data model sync +// RegisterModel registers model, if initFuncs provided, it will be invoked after data model sync func RegisterModel(bean any, initFunc ...func() error) { - tables = append(tables, bean) - if len(initFuncs) > 0 && initFunc[0] != nil { - initFuncs = append(initFuncs, initFunc[0]) + registeredModels = append(registeredModels, bean) + if len(registeredInitFuncs) > 0 && initFunc[0] != nil { + registeredInitFuncs = append(registeredInitFuncs, initFunc[0]) } } -func init() { - gonicNames := []string{"SSL", "UID"} - for _, name := range gonicNames { - names.LintGonicMapper[name] = true - } -} - -// newXORMEngine returns a new XORM engine from the configuration -func newXORMEngine() (*xorm.Engine, error) { - connStr, err := setting.DBConnStr() - if err != nil { - return nil, err - } - - var engine *xorm.Engine - - if setting.Database.Type.IsPostgreSQL() && len(setting.Database.Schema) > 0 { - // OK whilst we sort out our schema issues - create a schema aware postgres - registerPostgresSchemaDriver() - engine, err = xorm.NewEngine("postgresschema", connStr) - } else { - engine, err = xorm.NewEngine(setting.Database.Type.String(), connStr) - } - - if err != nil { - return nil, err - } - if setting.Database.Type == "mysql" { - 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 -} - // SyncAllTables sync the schemas of all tables, is required by unit test code func SyncAllTables() error { - _, err := x.StoreEngine("InnoDB").SyncWithOptions(xorm.SyncOptions{ + _, err := xormEngine.StoreEngine("InnoDB").SyncWithOptions(xorm.SyncOptions{ WarnIfDatabaseColumnMissed: true, - }, tables...) + }, registeredModels...) return err } -// InitEngine initializes the xorm.Engine and sets it as db.DefaultContext -func InitEngine(ctx context.Context) error { - xormEngine, err := newXORMEngine() - if err != nil { - if strings.Contains(err.Error(), "SQLite3 support") { - return fmt.Errorf(`sqlite3 requires: -tags sqlite,sqlite_unlock_notify%s%w`, "\n", err) - } - return fmt.Errorf("failed to connect to database: %w", err) - } - - xormEngine.SetMapper(names.GonicMapper{}) - // WARNING: for serv command, MUST remove the output to os.stdout, - // so use log file to instead print to stdout. - xormEngine.SetLogger(NewXORMLogger(setting.Database.LogSQL)) - xormEngine.ShowSQL(setting.Database.LogSQL) - xormEngine.SetMaxOpenConns(setting.Database.MaxOpenConns) - xormEngine.SetMaxIdleConns(setting.Database.MaxIdleConns) - xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime) - xormEngine.SetDefaultContext(ctx) - - if setting.Database.SlowQueryThreshold > 0 { - xormEngine.AddHook(&SlowQueryHook{ - Threshold: setting.Database.SlowQueryThreshold, - Logger: log.GetLogger("xorm"), - }) - } - - SetDefaultEngine(ctx, xormEngine) - return nil -} - -// SetDefaultEngine sets the default engine for db -func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) { - x = eng - DefaultContext = &Context{Context: ctx, engine: x} -} - -// UnsetDefaultEngine closes and unsets the default engine -// We hope the SetDefaultEngine and UnsetDefaultEngine can be paired, but it's impossible now, -// there are many calls to InitEngine -> SetDefaultEngine directly to overwrite the `x` and DefaultContext without close -// Global database engine related functions are all racy and there is no graceful close right now. -func UnsetDefaultEngine() { - if x != nil { - _ = x.Close() - x = nil - } - DefaultContext = nil -} - -// InitEngineWithMigration initializes a new xorm.Engine and sets it as the db.DefaultContext -// This function must never call .Sync() if the provided migration function fails. -// When called from the "doctor" command, the migration function is a version check -// that prevents the doctor from fixing anything in the database if the migration level -// is different from the expected value. -func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) { - if err = InitEngine(ctx); err != nil { - return err - } - - if err = x.Ping(); err != nil { - return err - } - - preprocessDatabaseCollation(x) - - // We have to run migrateFunc here in case the user is re-running installation on a previously created DB. - // If we do not then table schemas will be changed and there will be conflicts when the migrations run properly. - // - // Installation should only be being re-run if users want to recover an old database. - // However, we should think carefully about should we support re-install on an installed instance, - // as there may be other problems due to secret reinitialization. - if err = migrateFunc(x); err != nil { - return fmt.Errorf("migrate: %w", err) - } - - if err = SyncAllTables(); err != nil { - return fmt.Errorf("sync database struct error: %w", err) - } - - for _, initFunc := range initFuncs { - if err := initFunc(); err != nil { - return fmt.Errorf("initFunc failed: %w", err) - } - } - - return nil -} - // NamesToBean return a list of beans or an error func NamesToBean(names ...string) ([]any, error) { beans := []any{} if len(names) == 0 { - beans = append(beans, tables...) + beans = append(beans, registeredModels...) return beans, nil } // Need to map provided names to beans... beanMap := make(map[string]any) - for _, bean := range tables { + for _, bean := range registeredModels { 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 + beanMap[strings.ToLower(xormEngine.TableName(bean))] = bean + beanMap[strings.ToLower(xormEngine.TableName(bean, true))] = bean } gotBean := make(map[any]bool) @@ -247,36 +111,9 @@ func NamesToBean(names ...string) ([]any, error) { return beans, nil } -// DumpDatabase dumps all data from database according the special database SQL syntax to file system. -func DumpDatabase(filePath, dbType string) error { - var tbs []*schemas.Table - for _, t := range tables { - t, err := x.TableInfo(t) - if err != nil { - return err - } - tbs = append(tbs, t) - } - - type Version struct { - ID int64 `xorm:"pk autoincr"` - Version int64 - } - t, err := x.TableInfo(&Version{}) - if err != nil { - return err - } - tbs = append(tbs, t) - - if len(dbType) > 0 { - return x.DumpTablesToFile(tbs, filePath, schemas.DBType(dbType)) - } - return x.DumpTablesToFile(tbs, filePath) -} - // MaxBatchInsertSize returns the table's max batch insert size func MaxBatchInsertSize(bean any) int { - t, err := x.TableInfo(bean) + t, err := xormEngine.TableInfo(bean) if err != nil { return 50 } @@ -285,18 +122,18 @@ func MaxBatchInsertSize(bean any) int { // IsTableNotEmpty returns true if table has at least one record func IsTableNotEmpty(beanOrTableName any) (bool, error) { - return x.Table(beanOrTableName).Exist() + return xormEngine.Table(beanOrTableName).Exist() } // DeleteAllRecords will delete all the records of this table func DeleteAllRecords(tableName string) error { - _, err := x.Exec(fmt.Sprintf("DELETE FROM %s", tableName)) + _, err := xormEngine.Exec(fmt.Sprintf("DELETE FROM %s", tableName)) return err } // GetMaxID will return max id of the table func GetMaxID(beanOrTableName any) (maxID int64, err error) { - _, err = x.Select("MAX(id)").Table(beanOrTableName).Get(&maxID) + _, err = xormEngine.Select("MAX(id)").Table(beanOrTableName).Get(&maxID) return maxID, err } @@ -308,24 +145,3 @@ func SetLogSQL(ctx context.Context, on bool) { sess.Engine().ShowSQL(on) } } - -type SlowQueryHook struct { - Threshold time.Duration - Logger log.Logger -} - -var _ contexts.Hook = &SlowQueryHook{} - -func (SlowQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) { - return c.Ctx, nil -} - -func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error { - if c.ExecuteTime >= h.Threshold { - // 8 is the amount of skips passed to runtime.Caller, so that in the log the correct function - // is being displayed (the function that ultimately wants to execute the query in the code) - // instead of the function of the slow query hook being called. - h.Logger.Log(8, log.WARN, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime) - } - return nil -} diff --git a/models/db/engine_dump.go b/models/db/engine_dump.go new file mode 100644 index 00000000000..63f2d4e093c --- /dev/null +++ b/models/db/engine_dump.go @@ -0,0 +1,33 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package db + +import "xorm.io/xorm/schemas" + +// DumpDatabase dumps all data from database according the special database SQL syntax to file system. +func DumpDatabase(filePath, dbType string) error { + var tbs []*schemas.Table + for _, t := range registeredModels { + t, err := xormEngine.TableInfo(t) + if err != nil { + return err + } + tbs = append(tbs, t) + } + + type Version struct { + ID int64 `xorm:"pk autoincr"` + Version int64 + } + t, err := xormEngine.TableInfo(&Version{}) + if err != nil { + return err + } + tbs = append(tbs, t) + + if dbType != "" { + return xormEngine.DumpTablesToFile(tbs, filePath, schemas.DBType(dbType)) + } + return xormEngine.DumpTablesToFile(tbs, filePath) +} diff --git a/models/db/engine_hook.go b/models/db/engine_hook.go new file mode 100644 index 00000000000..b4c543c3dd8 --- /dev/null +++ b/models/db/engine_hook.go @@ -0,0 +1,34 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package db + +import ( + "context" + "time" + + "code.gitea.io/gitea/modules/log" + + "xorm.io/xorm/contexts" +) + +type SlowQueryHook struct { + Threshold time.Duration + Logger log.Logger +} + +var _ contexts.Hook = (*SlowQueryHook)(nil) + +func (*SlowQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) { + return c.Ctx, nil +} + +func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error { + if c.ExecuteTime >= h.Threshold { + // 8 is the amount of skips passed to runtime.Caller, so that in the log the correct function + // is being displayed (the function that ultimately wants to execute the query in the code) + // instead of the function of the slow query hook being called. + h.Logger.Log(8, log.WARN, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime) + } + return nil +} diff --git a/models/db/engine_init.go b/models/db/engine_init.go new file mode 100644 index 00000000000..da85018957b --- /dev/null +++ b/models/db/engine_init.go @@ -0,0 +1,140 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package db + +import ( + "context" + "fmt" + "strings" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "xorm.io/xorm" + "xorm.io/xorm/names" +) + +func init() { + gonicNames := []string{"SSL", "UID"} + for _, name := range gonicNames { + names.LintGonicMapper[name] = true + } +} + +// newXORMEngine returns a new XORM engine from the configuration +func newXORMEngine() (*xorm.Engine, error) { + connStr, err := setting.DBConnStr() + if err != nil { + return nil, err + } + + var engine *xorm.Engine + + if setting.Database.Type.IsPostgreSQL() && len(setting.Database.Schema) > 0 { + // OK whilst we sort out our schema issues - create a schema aware postgres + registerPostgresSchemaDriver() + engine, err = xorm.NewEngine("postgresschema", connStr) + } else { + engine, err = xorm.NewEngine(setting.Database.Type.String(), connStr) + } + + if err != nil { + return nil, err + } + if setting.Database.Type == "mysql" { + 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 +} + +// InitEngine initializes the xorm.Engine and sets it as db.DefaultContext +func InitEngine(ctx context.Context) error { + xe, err := newXORMEngine() + if err != nil { + if strings.Contains(err.Error(), "SQLite3 support") { + return fmt.Errorf(`sqlite3 requires: -tags sqlite,sqlite_unlock_notify%s%w`, "\n", err) + } + return fmt.Errorf("failed to connect to database: %w", err) + } + + xe.SetMapper(names.GonicMapper{}) + // WARNING: for serv command, MUST remove the output to os.stdout, + // so use log file to instead print to stdout. + xe.SetLogger(NewXORMLogger(setting.Database.LogSQL)) + xe.ShowSQL(setting.Database.LogSQL) + xe.SetMaxOpenConns(setting.Database.MaxOpenConns) + xe.SetMaxIdleConns(setting.Database.MaxIdleConns) + xe.SetConnMaxLifetime(setting.Database.ConnMaxLifetime) + xe.SetDefaultContext(ctx) + + if setting.Database.SlowQueryThreshold > 0 { + xe.AddHook(&SlowQueryHook{ + Threshold: setting.Database.SlowQueryThreshold, + Logger: log.GetLogger("xorm"), + }) + } + + SetDefaultEngine(ctx, xe) + return nil +} + +// SetDefaultEngine sets the default engine for db +func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) { + xormEngine = eng + DefaultContext = &Context{Context: ctx, engine: xormEngine} +} + +// UnsetDefaultEngine closes and unsets the default engine +// We hope the SetDefaultEngine and UnsetDefaultEngine can be paired, but it's impossible now, +// there are many calls to InitEngine -> SetDefaultEngine directly to overwrite the `xormEngine` and DefaultContext without close +// Global database engine related functions are all racy and there is no graceful close right now. +func UnsetDefaultEngine() { + if xormEngine != nil { + _ = xormEngine.Close() + xormEngine = nil + } + DefaultContext = nil +} + +// InitEngineWithMigration initializes a new xorm.Engine and sets it as the db.DefaultContext +// This function must never call .Sync() if the provided migration function fails. +// When called from the "doctor" command, the migration function is a version check +// that prevents the doctor from fixing anything in the database if the migration level +// is different from the expected value. +func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) { + if err = InitEngine(ctx); err != nil { + return err + } + + if err = xormEngine.Ping(); err != nil { + return err + } + + preprocessDatabaseCollation(xormEngine) + + // We have to run migrateFunc here in case the user is re-running installation on a previously created DB. + // If we do not then table schemas will be changed and there will be conflicts when the migrations run properly. + // + // Installation should only be being re-run if users want to recover an old database. + // However, we should think carefully about should we support re-install on an installed instance, + // as there may be other problems due to secret reinitialization. + if err = migrateFunc(xormEngine); err != nil { + return fmt.Errorf("migrate: %w", err) + } + + if err = SyncAllTables(); err != nil { + return fmt.Errorf("sync database struct error: %w", err) + } + + for _, initFunc := range registeredInitFuncs { + if err := initFunc(); err != nil { + return fmt.Errorf("initFunc failed: %w", err) + } + } + + return nil +} diff --git a/models/db/iterate_test.go b/models/db/iterate_test.go index 0f6ba2cc94a..e9f27906711 100644 --- a/models/db/iterate_test.go +++ b/models/db/iterate_test.go @@ -38,8 +38,6 @@ func TestIterate(t *testing.T) { if !has { return db.ErrNotExist{Resource: "repo_unit", ID: repoUnit.ID} } - assert.EqualValues(t, repoUnit.RepoID, repoUnit.RepoID) - assert.EqualValues(t, repoUnit.CreatedUnix, repoUnit.CreatedUnix) return nil }) assert.NoError(t, err) diff --git a/models/db/name.go b/models/db/name.go index 51be33a8bcb..55c9dffb6ab 100644 --- a/models/db/name.go +++ b/models/db/name.go @@ -16,7 +16,7 @@ var ( // ErrNameEmpty name is empty error ErrNameEmpty = util.SilentWrap{Message: "name is empty", Err: util.ErrInvalidArgument} - // AlphaDashDotPattern characters prohibited in a user name (anything except A-Za-z0-9_.-) + // AlphaDashDotPattern characters prohibited in a username (anything except A-Za-z0-9_.-) AlphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`) ) diff --git a/models/db/sequence.go b/models/db/sequence.go index f49ad935de0..9adc5113acc 100644 --- a/models/db/sequence.go +++ b/models/db/sequence.go @@ -17,11 +17,11 @@ func CountBadSequences(_ context.Context) (int64, error) { return 0, nil } - sess := x.NewSession() + sess := xormEngine.NewSession() defer sess.Close() var sequences []string - schema := x.Dialect().URI().Schema + schema := xormEngine.Dialect().URI().Schema sess.Engine().SetSchema("") if err := sess.Table("information_schema.sequences").Cols("sequence_name").Where("sequence_name LIKE 'tmp_recreate__%_id_seq%' AND sequence_catalog = ?", setting.Database.Name).Find(&sequences); err != nil { @@ -38,7 +38,7 @@ func FixBadSequences(_ context.Context) error { return nil } - sess := x.NewSession() + sess := xormEngine.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { return err diff --git a/models/error.go b/models/error.go deleted file mode 100644 index 75c53245de5..00000000000 --- a/models/error.go +++ /dev/null @@ -1,552 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package models - -import ( - "fmt" - - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/util" -) - -// ErrUserOwnRepos represents a "UserOwnRepos" kind of error. -type ErrUserOwnRepos struct { - UID int64 -} - -// IsErrUserOwnRepos checks if an error is a ErrUserOwnRepos. -func IsErrUserOwnRepos(err error) bool { - _, ok := err.(ErrUserOwnRepos) - return ok -} - -func (err ErrUserOwnRepos) Error() string { - return fmt.Sprintf("user still has ownership of repositories [uid: %d]", err.UID) -} - -// ErrUserHasOrgs represents a "UserHasOrgs" kind of error. -type ErrUserHasOrgs struct { - UID int64 -} - -// IsErrUserHasOrgs checks if an error is a ErrUserHasOrgs. -func IsErrUserHasOrgs(err error) bool { - _, ok := err.(ErrUserHasOrgs) - return ok -} - -func (err ErrUserHasOrgs) Error() string { - return fmt.Sprintf("user still has membership of organizations [uid: %d]", err.UID) -} - -// ErrUserOwnPackages notifies that the user (still) owns the packages. -type ErrUserOwnPackages struct { - UID int64 -} - -// IsErrUserOwnPackages checks if an error is an ErrUserOwnPackages. -func IsErrUserOwnPackages(err error) bool { - _, ok := err.(ErrUserOwnPackages) - return ok -} - -func (err ErrUserOwnPackages) Error() string { - return fmt.Sprintf("user still has ownership of packages [uid: %d]", err.UID) -} - -// ErrDeleteLastAdminUser represents a "DeleteLastAdminUser" kind of error. -type ErrDeleteLastAdminUser struct { - UID int64 -} - -// IsErrDeleteLastAdminUser checks if an error is a ErrDeleteLastAdminUser. -func IsErrDeleteLastAdminUser(err error) bool { - _, ok := err.(ErrDeleteLastAdminUser) - return ok -} - -func (err ErrDeleteLastAdminUser) Error() string { - return fmt.Sprintf("can not delete the last admin user [uid: %d]", err.UID) -} - -// ErrNoPendingRepoTransfer is an error type for repositories without a pending -// transfer request -type ErrNoPendingRepoTransfer struct { - RepoID int64 -} - -func (err ErrNoPendingRepoTransfer) Error() string { - return fmt.Sprintf("repository doesn't have a pending transfer [repo_id: %d]", err.RepoID) -} - -// IsErrNoPendingTransfer is an error type when a repository has no pending -// transfers -func IsErrNoPendingTransfer(err error) bool { - _, ok := err.(ErrNoPendingRepoTransfer) - return ok -} - -func (err ErrNoPendingRepoTransfer) Unwrap() error { - return util.ErrNotExist -} - -// ErrRepoTransferInProgress represents the state of a repository that has an -// ongoing transfer -type ErrRepoTransferInProgress struct { - Uname string - Name string -} - -// IsErrRepoTransferInProgress checks if an error is a ErrRepoTransferInProgress. -func IsErrRepoTransferInProgress(err error) bool { - _, ok := err.(ErrRepoTransferInProgress) - return ok -} - -func (err ErrRepoTransferInProgress) Error() string { - return fmt.Sprintf("repository is already being transferred [uname: %s, name: %s]", err.Uname, err.Name) -} - -func (err ErrRepoTransferInProgress) Unwrap() error { - return util.ErrAlreadyExist -} - -// ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error. -type ErrInvalidCloneAddr struct { - Host string - IsURLError bool - IsInvalidPath bool - IsProtocolInvalid bool - IsPermissionDenied bool - LocalPath bool -} - -// IsErrInvalidCloneAddr checks if an error is a ErrInvalidCloneAddr. -func IsErrInvalidCloneAddr(err error) bool { - _, ok := err.(*ErrInvalidCloneAddr) - return ok -} - -func (err *ErrInvalidCloneAddr) Error() string { - if err.IsInvalidPath { - return fmt.Sprintf("migration/cloning from '%s' is not allowed: the provided path is invalid", err.Host) - } - if err.IsProtocolInvalid { - return fmt.Sprintf("migration/cloning from '%s' is not allowed: the provided url protocol is not allowed", err.Host) - } - if err.IsPermissionDenied { - return fmt.Sprintf("migration/cloning from '%s' is not allowed.", err.Host) - } - if err.IsURLError { - return fmt.Sprintf("migration/cloning from '%s' is not allowed: the provided url is invalid", err.Host) - } - - return fmt.Sprintf("migration/cloning from '%s' is not allowed", err.Host) -} - -func (err *ErrInvalidCloneAddr) Unwrap() error { - return util.ErrInvalidArgument -} - -// ErrUpdateTaskNotExist represents a "UpdateTaskNotExist" kind of error. -type ErrUpdateTaskNotExist struct { - UUID string -} - -// IsErrUpdateTaskNotExist checks if an error is a ErrUpdateTaskNotExist. -func IsErrUpdateTaskNotExist(err error) bool { - _, ok := err.(ErrUpdateTaskNotExist) - return ok -} - -func (err ErrUpdateTaskNotExist) Error() string { - return fmt.Sprintf("update task does not exist [uuid: %s]", err.UUID) -} - -func (err ErrUpdateTaskNotExist) Unwrap() error { - return util.ErrNotExist -} - -// ErrInvalidTagName represents a "InvalidTagName" kind of error. -type ErrInvalidTagName struct { - TagName string -} - -// IsErrInvalidTagName checks if an error is a ErrInvalidTagName. -func IsErrInvalidTagName(err error) bool { - _, ok := err.(ErrInvalidTagName) - return ok -} - -func (err ErrInvalidTagName) Error() string { - return fmt.Sprintf("release tag name is not valid [tag_name: %s]", err.TagName) -} - -func (err ErrInvalidTagName) Unwrap() error { - return util.ErrInvalidArgument -} - -// ErrProtectedTagName represents a "ProtectedTagName" kind of error. -type ErrProtectedTagName struct { - TagName string -} - -// IsErrProtectedTagName checks if an error is a ErrProtectedTagName. -func IsErrProtectedTagName(err error) bool { - _, ok := err.(ErrProtectedTagName) - return ok -} - -func (err ErrProtectedTagName) Error() string { - return fmt.Sprintf("release tag name is protected [tag_name: %s]", err.TagName) -} - -func (err ErrProtectedTagName) Unwrap() error { - return util.ErrPermissionDenied -} - -// ErrRepoFileAlreadyExists represents a "RepoFileAlreadyExist" kind of error. -type ErrRepoFileAlreadyExists struct { - Path string -} - -// IsErrRepoFileAlreadyExists checks if an error is a ErrRepoFileAlreadyExists. -func IsErrRepoFileAlreadyExists(err error) bool { - _, ok := err.(ErrRepoFileAlreadyExists) - return ok -} - -func (err ErrRepoFileAlreadyExists) Error() string { - return fmt.Sprintf("repository file already exists [path: %s]", err.Path) -} - -func (err ErrRepoFileAlreadyExists) Unwrap() error { - return util.ErrAlreadyExist -} - -// ErrRepoFileDoesNotExist represents a "RepoFileDoesNotExist" kind of error. -type ErrRepoFileDoesNotExist struct { - Path string - Name string -} - -// IsErrRepoFileDoesNotExist checks if an error is a ErrRepoDoesNotExist. -func IsErrRepoFileDoesNotExist(err error) bool { - _, ok := err.(ErrRepoFileDoesNotExist) - return ok -} - -func (err ErrRepoFileDoesNotExist) Error() string { - return fmt.Sprintf("repository file does not exist [path: %s]", err.Path) -} - -func (err ErrRepoFileDoesNotExist) Unwrap() error { - return util.ErrNotExist -} - -// ErrFilenameInvalid represents a "FilenameInvalid" kind of error. -type ErrFilenameInvalid struct { - Path string -} - -// IsErrFilenameInvalid checks if an error is an ErrFilenameInvalid. -func IsErrFilenameInvalid(err error) bool { - _, ok := err.(ErrFilenameInvalid) - return ok -} - -func (err ErrFilenameInvalid) Error() string { - return fmt.Sprintf("path contains a malformed path component [path: %s]", err.Path) -} - -func (err ErrFilenameInvalid) Unwrap() error { - return util.ErrInvalidArgument -} - -// ErrUserCannotCommit represents "UserCannotCommit" kind of error. -type ErrUserCannotCommit struct { - UserName string -} - -// IsErrUserCannotCommit checks if an error is an ErrUserCannotCommit. -func IsErrUserCannotCommit(err error) bool { - _, ok := err.(ErrUserCannotCommit) - return ok -} - -func (err ErrUserCannotCommit) Error() string { - return fmt.Sprintf("user cannot commit to repo [user: %s]", err.UserName) -} - -func (err ErrUserCannotCommit) Unwrap() error { - return util.ErrPermissionDenied -} - -// ErrFilePathInvalid represents a "FilePathInvalid" kind of error. -type ErrFilePathInvalid struct { - Message string - Path string - Name string - Type git.EntryMode -} - -// IsErrFilePathInvalid checks if an error is an ErrFilePathInvalid. -func IsErrFilePathInvalid(err error) bool { - _, ok := err.(ErrFilePathInvalid) - return ok -} - -func (err ErrFilePathInvalid) Error() string { - if err.Message != "" { - return err.Message - } - return fmt.Sprintf("path is invalid [path: %s]", err.Path) -} - -func (err ErrFilePathInvalid) Unwrap() error { - return util.ErrInvalidArgument -} - -// ErrFilePathProtected represents a "FilePathProtected" kind of error. -type ErrFilePathProtected struct { - Message string - Path string -} - -// IsErrFilePathProtected checks if an error is an ErrFilePathProtected. -func IsErrFilePathProtected(err error) bool { - _, ok := err.(ErrFilePathProtected) - return ok -} - -func (err ErrFilePathProtected) Error() string { - if err.Message != "" { - return err.Message - } - return fmt.Sprintf("path is protected and can not be changed [path: %s]", err.Path) -} - -func (err ErrFilePathProtected) Unwrap() error { - return util.ErrPermissionDenied -} - -// ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it. -type ErrDisallowedToMerge struct { - Reason string -} - -// IsErrDisallowedToMerge checks if an error is an ErrDisallowedToMerge. -func IsErrDisallowedToMerge(err error) bool { - _, ok := err.(ErrDisallowedToMerge) - return ok -} - -func (err ErrDisallowedToMerge) Error() string { - return fmt.Sprintf("not allowed to merge [reason: %s]", err.Reason) -} - -func (err ErrDisallowedToMerge) Unwrap() error { - return util.ErrPermissionDenied -} - -// ErrTagAlreadyExists represents an error that tag with such name already exists. -type ErrTagAlreadyExists struct { - TagName string -} - -// IsErrTagAlreadyExists checks if an error is an ErrTagAlreadyExists. -func IsErrTagAlreadyExists(err error) bool { - _, ok := err.(ErrTagAlreadyExists) - return ok -} - -func (err ErrTagAlreadyExists) Error() string { - return fmt.Sprintf("tag already exists [name: %s]", err.TagName) -} - -func (err ErrTagAlreadyExists) Unwrap() error { - return util.ErrAlreadyExist -} - -// ErrSHADoesNotMatch represents a "SHADoesNotMatch" kind of error. -type ErrSHADoesNotMatch struct { - Path string - GivenSHA string - CurrentSHA string -} - -// IsErrSHADoesNotMatch checks if an error is a ErrSHADoesNotMatch. -func IsErrSHADoesNotMatch(err error) bool { - _, ok := err.(ErrSHADoesNotMatch) - return ok -} - -func (err ErrSHADoesNotMatch) Error() string { - return fmt.Sprintf("sha does not match [given: %s, expected: %s]", err.GivenSHA, err.CurrentSHA) -} - -// ErrSHANotFound represents a "SHADoesNotMatch" kind of error. -type ErrSHANotFound struct { - SHA string -} - -// IsErrSHANotFound checks if an error is a ErrSHANotFound. -func IsErrSHANotFound(err error) bool { - _, ok := err.(ErrSHANotFound) - return ok -} - -func (err ErrSHANotFound) Error() string { - return fmt.Sprintf("sha not found [%s]", err.SHA) -} - -func (err ErrSHANotFound) Unwrap() error { - return util.ErrNotExist -} - -// ErrCommitIDDoesNotMatch represents a "CommitIDDoesNotMatch" kind of error. -type ErrCommitIDDoesNotMatch struct { - GivenCommitID string - CurrentCommitID string -} - -// IsErrCommitIDDoesNotMatch checks if an error is a ErrCommitIDDoesNotMatch. -func IsErrCommitIDDoesNotMatch(err error) bool { - _, ok := err.(ErrCommitIDDoesNotMatch) - return ok -} - -func (err ErrCommitIDDoesNotMatch) Error() string { - return fmt.Sprintf("file CommitID does not match [given: %s, expected: %s]", err.GivenCommitID, err.CurrentCommitID) -} - -// ErrSHAOrCommitIDNotProvided represents a "SHAOrCommitIDNotProvided" kind of error. -type ErrSHAOrCommitIDNotProvided struct{} - -// IsErrSHAOrCommitIDNotProvided checks if an error is a ErrSHAOrCommitIDNotProvided. -func IsErrSHAOrCommitIDNotProvided(err error) bool { - _, ok := err.(ErrSHAOrCommitIDNotProvided) - return ok -} - -func (err ErrSHAOrCommitIDNotProvided) Error() string { - return "a SHA or commit ID must be proved when updating a file" -} - -// ErrInvalidMergeStyle represents an error if merging with disabled merge strategy -type ErrInvalidMergeStyle struct { - ID int64 - Style repo_model.MergeStyle -} - -// IsErrInvalidMergeStyle checks if an error is a ErrInvalidMergeStyle. -func IsErrInvalidMergeStyle(err error) bool { - _, ok := err.(ErrInvalidMergeStyle) - return ok -} - -func (err ErrInvalidMergeStyle) Error() string { - return fmt.Sprintf("merge strategy is not allowed or is invalid [repo_id: %d, strategy: %s]", - err.ID, err.Style) -} - -func (err ErrInvalidMergeStyle) Unwrap() error { - return util.ErrInvalidArgument -} - -// ErrMergeConflicts represents an error if merging fails with a conflict -type ErrMergeConflicts struct { - Style repo_model.MergeStyle - StdOut string - StdErr string - Err error -} - -// IsErrMergeConflicts checks if an error is a ErrMergeConflicts. -func IsErrMergeConflicts(err error) bool { - _, ok := err.(ErrMergeConflicts) - return ok -} - -func (err ErrMergeConflicts) Error() string { - return fmt.Sprintf("Merge Conflict Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) -} - -// ErrMergeUnrelatedHistories represents an error if merging fails due to unrelated histories -type ErrMergeUnrelatedHistories struct { - Style repo_model.MergeStyle - StdOut string - StdErr string - Err error -} - -// IsErrMergeUnrelatedHistories checks if an error is a ErrMergeUnrelatedHistories. -func IsErrMergeUnrelatedHistories(err error) bool { - _, ok := err.(ErrMergeUnrelatedHistories) - return ok -} - -func (err ErrMergeUnrelatedHistories) Error() string { - return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) -} - -// ErrMergeDivergingFastForwardOnly represents an error if a fast-forward-only merge fails because the branches diverge -type ErrMergeDivergingFastForwardOnly struct { - StdOut string - StdErr string - Err error -} - -// IsErrMergeDivergingFastForwardOnly checks if an error is a ErrMergeDivergingFastForwardOnly. -func IsErrMergeDivergingFastForwardOnly(err error) bool { - _, ok := err.(ErrMergeDivergingFastForwardOnly) - return ok -} - -func (err ErrMergeDivergingFastForwardOnly) Error() string { - return fmt.Sprintf("Merge DivergingFastForwardOnly Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) -} - -// ErrRebaseConflicts represents an error if rebase fails with a conflict -type ErrRebaseConflicts struct { - Style repo_model.MergeStyle - CommitSHA string - StdOut string - StdErr string - Err error -} - -// IsErrRebaseConflicts checks if an error is a ErrRebaseConflicts. -func IsErrRebaseConflicts(err error) bool { - _, ok := err.(ErrRebaseConflicts) - return ok -} - -func (err ErrRebaseConflicts) Error() string { - return fmt.Sprintf("Rebase Error: %v: Whilst Rebasing: %s\n%s\n%s", err.Err, err.CommitSHA, err.StdErr, err.StdOut) -} - -// ErrPullRequestHasMerged represents a "PullRequestHasMerged"-error -type ErrPullRequestHasMerged struct { - ID int64 - IssueID int64 - HeadRepoID int64 - BaseRepoID int64 - HeadBranch string - BaseBranch string -} - -// IsErrPullRequestHasMerged checks if an error is a ErrPullRequestHasMerged. -func IsErrPullRequestHasMerged(err error) bool { - _, ok := err.(ErrPullRequestHasMerged) - return ok -} - -// Error does pretty-printing :D -func (err ErrPullRequestHasMerged) Error() string { - return fmt.Sprintf("pull request has merged [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]", - err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch) -} diff --git a/models/fixtures/action_run.yml b/models/fixtures/action_run.yml index a42ab77ca5b..1db849352f2 100644 --- a/models/fixtures/action_run.yml +++ b/models/fixtures/action_run.yml @@ -36,3 +36,41 @@ updated: 1683636626 need_approval: 0 approved_by: 0 +- + id: 793 + title: "job output" + repo_id: 4 + owner_id: 1 + workflow_id: "test.yaml" + index: 189 + trigger_user_id: 1 + ref: "refs/heads/master" + commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0" + event: "push" + is_fork_pull_request: 0 + status: 1 + started: 1683636528 + stopped: 1683636626 + created: 1683636108 + updated: 1683636626 + need_approval: 0 + approved_by: 0 +- + id: 794 + title: "job output" + repo_id: 4 + owner_id: 1 + workflow_id: "test.yaml" + index: 190 + trigger_user_id: 1 + ref: "refs/heads/test" + commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0" + event: "push" + is_fork_pull_request: 0 + status: 1 + started: 1683636528 + stopped: 1683636626 + created: 1683636108 + updated: 1683636626 + need_approval: 0 + approved_by: 0 diff --git a/models/fixtures/action_run_job.yml b/models/fixtures/action_run_job.yml index fd90f4fd5d2..9b6f5b9a887 100644 --- a/models/fixtures/action_run_job.yml +++ b/models/fixtures/action_run_job.yml @@ -26,3 +26,46 @@ status: 1 started: 1683636528 stopped: 1683636626 +- + id: 194 + run_id: 793 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + is_fork_pull_request: 0 + name: job1 (1) + attempt: 1 + job_id: job1 + task_id: 49 + status: 1 + started: 1683636528 + stopped: 1683636626 +- + id: 195 + run_id: 793 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + is_fork_pull_request: 0 + name: job1 (2) + attempt: 1 + job_id: job1 + task_id: 50 + status: 1 + started: 1683636528 + stopped: 1683636626 +- + id: 196 + run_id: 793 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + is_fork_pull_request: 0 + name: job2 + attempt: 1 + job_id: job2 + needs: [job1] + task_id: 51 + status: 5 + started: 1683636528 + stopped: 1683636626 diff --git a/models/fixtures/action_task.yml b/models/fixtures/action_task.yml index d88a8ed8a91..506a47d8a04 100644 --- a/models/fixtures/action_task.yml +++ b/models/fixtures/action_task.yml @@ -57,3 +57,63 @@ log_length: 707 log_size: 90179 log_expired: 0 +- + id: 49 + job_id: 194 + attempt: 1 + runner_id: 1 + status: 1 # success + started: 1683636528 + stopped: 1683636626 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + is_fork_pull_request: 0 + token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784220 + token_salt: ffffffffff + token_last_eight: ffffffff + log_filename: artifact-test2/2f/47.log + log_in_storage: 1 + log_length: 707 + log_size: 90179 + log_expired: 0 +- + id: 50 + job_id: 195 + attempt: 1 + runner_id: 1 + status: 1 # success + started: 1683636528 + stopped: 1683636626 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + is_fork_pull_request: 0 + token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784221 + token_salt: ffffffffff + token_last_eight: ffffffff + log_filename: artifact-test2/2f/47.log + log_in_storage: 1 + log_length: 707 + log_size: 90179 + log_expired: 0 +- + id: 51 + job_id: 196 + attempt: 1 + runner_id: 1 + status: 6 # running + started: 1683636528 + stopped: 1683636626 + repo_id: 4 + owner_id: 1 + commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0 + is_fork_pull_request: 0 + token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784222 + token_salt: ffffffffff + token_last_eight: ffffffff + log_filename: artifact-test2/2f/47.log + log_in_storage: 1 + log_length: 707 + log_size: 90179 + log_expired: 0 diff --git a/models/fixtures/action_task_output.yml b/models/fixtures/action_task_output.yml new file mode 100644 index 00000000000..314e9f7115b --- /dev/null +++ b/models/fixtures/action_task_output.yml @@ -0,0 +1,20 @@ +- + id: 1 + task_id: 49 + output_key: output_a + output_value: abc +- + id: 2 + task_id: 49 + output_key: output_b + output_value: '' +- + id: 3 + task_id: 50 + output_key: output_a + output_value: '' +- + id: 4 + task_id: 50 + output_key: output_b + output_value: bbb diff --git a/models/fixtures/branch.yml b/models/fixtures/branch.yml index c7bdff77339..17b1869ab63 100644 --- a/models/fixtures/branch.yml +++ b/models/fixtures/branch.yml @@ -81,3 +81,15 @@ is_deleted: false deleted_by_id: 0 deleted_unix: 0 + +- + id: 15 + repo_id: 4 + name: 'master' + commit_id: 'c7cd3cd144e6d23c9d6f3d07e52b2c1a956e0338' + commit_message: 'add Readme' + commit_time: 1588147171 + pusher_id: 13 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 diff --git a/models/git/commit_status_test.go b/models/git/commit_status_test.go index 7ac4da68103..37d785e9385 100644 --- a/models/git/commit_status_test.go +++ b/models/git/commit_status_test.go @@ -34,7 +34,7 @@ func TestGetCommitStatuses(t *testing.T) { SHA: sha1, }) assert.NoError(t, err) - assert.Equal(t, int(maxResults), 5) + assert.Equal(t, 5, int(maxResults)) assert.Len(t, statuses, 5) assert.Equal(t, "ci/awesomeness", statuses[0].Context) @@ -63,7 +63,7 @@ func TestGetCommitStatuses(t *testing.T) { SHA: sha1, }) assert.NoError(t, err) - assert.Equal(t, int(maxResults), 5) + assert.Equal(t, 5, int(maxResults)) assert.Empty(t, statuses) } diff --git a/models/git/protected_branch_test.go b/models/git/protected_branch_test.go index 49d433f845d..e1c91d927d8 100644 --- a/models/git/protected_branch_test.go +++ b/models/git/protected_branch_test.go @@ -4,7 +4,6 @@ package git import ( - "fmt" "testing" "code.gitea.io/gitea/models/db" @@ -76,7 +75,7 @@ func TestBranchRuleMatch(t *testing.T) { infact = " not" } assert.EqualValues(t, kase.ExpectedMatch, pb.Match(kase.BranchName), - fmt.Sprintf("%s should%s match %s but it is%s", kase.BranchName, should, kase.Rule, infact), + "%s should%s match %s but it is%s", kase.BranchName, should, kase.Rule, infact, ) } } diff --git a/models/issues/comment_test.go b/models/issues/comment_test.go index c5bbfdedc28..d81f33f953d 100644 --- a/models/issues/comment_test.go +++ b/models/issues/comment_test.go @@ -64,7 +64,7 @@ func TestFetchCodeComments(t *testing.T) { } func TestAsCommentType(t *testing.T) { - assert.Equal(t, issues_model.CommentType(0), issues_model.CommentTypeComment) + assert.Equal(t, issues_model.CommentTypeComment, issues_model.CommentType(0)) assert.Equal(t, issues_model.CommentTypeUndefined, issues_model.AsCommentType("")) assert.Equal(t, issues_model.CommentTypeUndefined, issues_model.AsCommentType("nonsense")) assert.Equal(t, issues_model.CommentTypeComment, issues_model.AsCommentType("comment")) diff --git a/models/issues/issue.go b/models/issues/issue.go index 64fc20cc05e..fe347c27156 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -125,8 +125,11 @@ type Issue struct { IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not. PullRequest *PullRequest `xorm:"-"` NumComments int - Ref string - PinOrder int `xorm:"DEFAULT 0"` + + // TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl" + Ref string + + PinOrder int `xorm:"DEFAULT 0"` DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"` diff --git a/models/issues/issue_index.go b/models/issues/issue_index.go index 16274d0ef09..2eb61858bfc 100644 --- a/models/issues/issue_index.go +++ b/models/issues/issue_index.go @@ -18,12 +18,12 @@ func RecalculateIssueIndexForRepo(ctx context.Context, repoID int64) error { } defer committer.Close() - var max int64 - if _, err = db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&max); err != nil { + var maxIndex int64 + if _, err = db.GetEngine(ctx).Select(" MAX(`index`)").Table("issue").Where("repo_id=?", repoID).Get(&maxIndex); err != nil { return err } - if err = db.SyncMaxResourceIndex(ctx, "issue_index", repoID, max); err != nil { + if err = db.SyncMaxResourceIndex(ctx, "issue_index", repoID, maxIndex); err != nil { return err } diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go index 548f137f394..dbbb1e41790 100644 --- a/models/issues/issue_test.go +++ b/models/issues/issue_test.go @@ -434,7 +434,7 @@ func assertCreateIssues(t *testing.T, isPull bool) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) - assert.EqualValues(t, milestone.ID, 1) + assert.EqualValues(t, 1, milestone.ID) reaction := &issues_model.Reaction{ Type: "heart", UserID: owner.ID, diff --git a/models/issues/issue_watch_test.go b/models/issues/issue_watch_test.go index d4ce8d8d3d6..fad94e243e6 100644 --- a/models/issues/issue_watch_test.go +++ b/models/issues/issue_watch_test.go @@ -48,17 +48,17 @@ func TestGetIssueWatchers(t *testing.T) { iws, err := issues_model.GetIssueWatchers(db.DefaultContext, 1, db.ListOptions{}) assert.NoError(t, err) // Watcher is inactive, thus 0 - assert.Len(t, iws, 0) + assert.Empty(t, iws) iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 2, db.ListOptions{}) assert.NoError(t, err) // Watcher is explicit not watching - assert.Len(t, iws, 0) + assert.Empty(t, iws) iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 5, db.ListOptions{}) assert.NoError(t, err) // Issue has no Watchers - assert.Len(t, iws, 0) + assert.Empty(t, iws) iws, err = issues_model.GetIssueWatchers(db.DefaultContext, 7, db.ListOptions{}) assert.NoError(t, err) diff --git a/models/issues/label_test.go b/models/issues/label_test.go index c2ff084c236..1d4b6f4684c 100644 --- a/models/issues/label_test.go +++ b/models/issues/label_test.go @@ -31,12 +31,12 @@ func TestLabel_LoadSelectedLabelsAfterClick(t *testing.T) { // First test : with negative and scope label.LoadSelectedLabelsAfterClick([]int64{1, -8}, []string{"", "scope"}) assert.Equal(t, "1", label.QueryString) - assert.Equal(t, true, label.IsSelected) + assert.True(t, label.IsSelected) // Second test : with duplicates label.LoadSelectedLabelsAfterClick([]int64{1, 7, 1, 7, 7}, []string{"", "scope", "", "scope", "scope"}) assert.Equal(t, "1,8", label.QueryString) - assert.Equal(t, false, label.IsSelected) + assert.False(t, label.IsSelected) // Third test : empty set label.LoadSelectedLabelsAfterClick([]int64{}, []string{}) @@ -248,7 +248,7 @@ func TestGetLabelsByIssueID(t *testing.T) { labels, err = issues_model.GetLabelsByIssueID(db.DefaultContext, unittest.NonexistentID) assert.NoError(t, err) - assert.Len(t, labels, 0) + assert.Empty(t, labels) } func TestUpdateLabel(t *testing.T) { @@ -271,7 +271,7 @@ func TestUpdateLabel(t *testing.T) { assert.EqualValues(t, label.Color, newLabel.Color) assert.EqualValues(t, label.Name, newLabel.Name) assert.EqualValues(t, label.Description, newLabel.Description) - assert.EqualValues(t, newLabel.ArchivedUnix, 0) + assert.EqualValues(t, 0, newLabel.ArchivedUnix) unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{}) } diff --git a/models/issues/milestone_test.go b/models/issues/milestone_test.go index e5f6f15ca2a..28cd0c028b8 100644 --- a/models/issues/milestone_test.go +++ b/models/issues/milestone_test.go @@ -87,7 +87,7 @@ func TestGetMilestonesByRepoID(t *testing.T) { IsClosed: optional.Some(false), }) assert.NoError(t, err) - assert.Len(t, milestones, 0) + assert.Empty(t, milestones) } func TestGetMilestones(t *testing.T) { diff --git a/models/issues/pull_list_test.go b/models/issues/pull_list_test.go index 8b814a0d0fc..c7a898ca4e8 100644 --- a/models/issues/pull_list_test.go +++ b/models/issues/pull_list_test.go @@ -40,7 +40,7 @@ func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) { assert.NoError(t, err) assert.Len(t, reviewComments, 2) for _, pr := range prs { - assert.EqualValues(t, reviewComments[pr.IssueID], 1) + assert.EqualValues(t, 1, reviewComments[pr.IssueID]) } } diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go index cb7b47263d8..090659864a8 100644 --- a/models/issues/pull_test.go +++ b/models/issues/pull_test.go @@ -83,7 +83,7 @@ func TestLoadRequestedReviewers(t *testing.T) { assert.NoError(t, pull.LoadIssue(db.DefaultContext)) issue := pull.Issue assert.NoError(t, issue.LoadRepo(db.DefaultContext)) - assert.Len(t, pull.RequestedReviewers, 0) + assert.Empty(t, pull.RequestedReviewers) user1, err := user_model.GetUserByID(db.DefaultContext, 1) assert.NoError(t, err) diff --git a/models/issues/stopwatch_test.go b/models/issues/stopwatch_test.go index 39958a7f36b..a1bf9dc931f 100644 --- a/models/issues/stopwatch_test.go +++ b/models/issues/stopwatch_test.go @@ -32,7 +32,7 @@ func TestCancelStopwatch(t *testing.T) { _ = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{Type: issues_model.CommentTypeCancelTracking, PosterID: user1.ID, IssueID: issue1.ID}) - assert.Nil(t, issues_model.CancelStopwatch(db.DefaultContext, user1, issue2)) + assert.NoError(t, issues_model.CancelStopwatch(db.DefaultContext, user1, issue2)) } func TestStopwatchExists(t *testing.T) { diff --git a/models/issues/tracked_time_test.go b/models/issues/tracked_time_test.go index d82bff967a9..44054a1b836 100644 --- a/models/issues/tracked_time_test.go +++ b/models/issues/tracked_time_test.go @@ -50,7 +50,7 @@ func TestGetTrackedTimes(t *testing.T) { times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{IssueID: -1}) assert.NoError(t, err) - assert.Len(t, times, 0) + assert.Empty(t, times) // by User times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{UserID: 1}) @@ -60,7 +60,7 @@ func TestGetTrackedTimes(t *testing.T) { times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{UserID: 3}) assert.NoError(t, err) - assert.Len(t, times, 0) + assert.Empty(t, times) // by Repo times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 2}) @@ -69,7 +69,7 @@ func TestGetTrackedTimes(t *testing.T) { assert.Equal(t, int64(1), times[0].Time) issue, err := issues_model.GetIssueByID(db.DefaultContext, times[0].IssueID) assert.NoError(t, err) - assert.Equal(t, issue.RepoID, int64(2)) + assert.Equal(t, int64(2), issue.RepoID) times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 1}) assert.NoError(t, err) @@ -77,7 +77,7 @@ func TestGetTrackedTimes(t *testing.T) { times, err = issues_model.GetTrackedTimes(db.DefaultContext, &issues_model.FindTrackedTimesOptions{RepositoryID: 10}) assert.NoError(t, err) - assert.Len(t, times, 0) + assert.Empty(t, times) } func TestTotalTimesForEachUser(t *testing.T) { diff --git a/models/migrations/v1_16/v193_test.go b/models/migrations/v1_16/v193_test.go index d99bbc29620..b279967a2c0 100644 --- a/models/migrations/v1_16/v193_test.go +++ b/models/migrations/v1_16/v193_test.go @@ -56,8 +56,8 @@ func Test_AddRepoIDForAttachment(t *testing.T) { err := x.Table("attachment").Where("issue_id > 0").Find(&issueAttachments) assert.NoError(t, err) for _, attach := range issueAttachments { - assert.Greater(t, attach.RepoID, int64(0)) - assert.Greater(t, attach.IssueID, int64(0)) + assert.Positive(t, attach.RepoID) + assert.Positive(t, attach.IssueID) var issue Issue has, err := x.ID(attach.IssueID).Get(&issue) assert.NoError(t, err) @@ -69,8 +69,8 @@ func Test_AddRepoIDForAttachment(t *testing.T) { err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments) assert.NoError(t, err) for _, attach := range releaseAttachments { - assert.Greater(t, attach.RepoID, int64(0)) - assert.Greater(t, attach.ReleaseID, int64(0)) + assert.Positive(t, attach.RepoID) + assert.Positive(t, attach.ReleaseID) var release Release has, err := x.ID(attach.ReleaseID).Get(&release) assert.NoError(t, err) diff --git a/models/migrations/v1_22/v286_test.go b/models/migrations/v1_22/v286_test.go index a19c9396e2e..1f213ddb6e0 100644 --- a/models/migrations/v1_22/v286_test.go +++ b/models/migrations/v1_22/v286_test.go @@ -107,12 +107,12 @@ func Test_RepositoryFormat(t *testing.T) { repo = new(Repository) ok, err := x.ID(2).Get(repo) assert.NoError(t, err) - assert.EqualValues(t, true, ok) + assert.True(t, ok) assert.EqualValues(t, "sha1", repo.ObjectFormatName) repo = new(Repository) ok, err = x.ID(id).Get(repo) assert.NoError(t, err) - assert.EqualValues(t, true, ok) + assert.True(t, ok) assert.EqualValues(t, "sha256", repo.ObjectFormatName) } diff --git a/models/migrations/v1_22/v294_test.go b/models/migrations/v1_22/v294_test.go index 82a3bcd602e..a1d702cb77d 100644 --- a/models/migrations/v1_22/v294_test.go +++ b/models/migrations/v1_22/v294_test.go @@ -39,7 +39,7 @@ func Test_AddUniqueIndexForProjectIssue(t *testing.T) { tables, err := x.DBMetas() assert.NoError(t, err) - assert.EqualValues(t, 1, len(tables)) + assert.Len(t, tables, 1) found := false for _, index := range tables[0].Indexes { if index.Type == schemas.UniqueType { diff --git a/models/organization/org.go b/models/organization/org.go index 725a99356e9..3e55a367589 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -9,11 +9,8 @@ import ( "fmt" "strings" - actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" - repo_model "code.gitea.io/gitea/models/repo" - secret_model "code.gitea.io/gitea/models/secret" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" @@ -407,33 +404,6 @@ func GetOrgByName(ctx context.Context, name string) (*Organization, error) { return u, nil } -// DeleteOrganization deletes models associated to an organization. -func DeleteOrganization(ctx context.Context, org *Organization) error { - if org.Type != user_model.UserTypeOrganization { - return fmt.Errorf("%s is a user not an organization", org.Name) - } - - if err := db.DeleteBeans(ctx, - &Team{OrgID: org.ID}, - &OrgUser{OrgID: org.ID}, - &TeamUser{OrgID: org.ID}, - &TeamUnit{OrgID: org.ID}, - &TeamInvite{OrgID: org.ID}, - &secret_model.Secret{OwnerID: org.ID}, - &user_model.Blocking{BlockerID: org.ID}, - &actions_model.ActionRunner{OwnerID: org.ID}, - &actions_model.ActionRunnerToken{OwnerID: org.ID}, - ); err != nil { - return fmt.Errorf("DeleteBeans: %w", err) - } - - if _, err := db.GetEngine(ctx).ID(org.ID).Delete(new(user_model.User)); err != nil { - return fmt.Errorf("Delete: %w", err) - } - - return nil -} - // GetOrgUserMaxAuthorizeLevel returns highest authorize level of user in an organization func (org *Organization) GetOrgUserMaxAuthorizeLevel(ctx context.Context, uid int64) (perm.AccessMode, error) { var authorize perm.AccessMode @@ -604,7 +574,9 @@ func RemoveOrgRepo(ctx context.Context, orgID, repoID int64) error { return err } -func (org *Organization) getUserTeams(ctx context.Context, userID int64, cols ...string) ([]*Team, error) { +// GetUserTeams returns all teams that belong to user, +// and that the user has joined. +func (org *Organization) GetUserTeams(ctx context.Context, userID int64, cols ...string) ([]*Team, error) { teams := make([]*Team, 0, org.NumTeams) return teams, db.GetEngine(ctx). Where("`team_user`.org_id = ?", org.ID). @@ -616,7 +588,8 @@ func (org *Organization) getUserTeams(ctx context.Context, userID int64, cols .. Find(&teams) } -func (org *Organization) getUserTeamIDs(ctx context.Context, userID int64) ([]int64, error) { +// GetUserTeamIDs returns of all team IDs of the organization that user is member of. +func (org *Organization) GetUserTeamIDs(ctx context.Context, userID int64) ([]int64, error) { teamIDs := make([]int64, 0, org.NumTeams) return teamIDs, db.GetEngine(ctx). Table("team"). @@ -640,175 +613,3 @@ func getUserTeamIDsQueryBuilder(orgID, userID int64) *builder.Builder { func (org *Organization) TeamsWithAccessToRepo(ctx context.Context, repoID int64, mode perm.AccessMode) ([]*Team, error) { return GetTeamsWithAccessToRepo(ctx, org.ID, repoID, mode) } - -// GetUserTeamIDs returns of all team IDs of the organization that user is member of. -func (org *Organization) GetUserTeamIDs(ctx context.Context, userID int64) ([]int64, error) { - return org.getUserTeamIDs(ctx, userID) -} - -// GetUserTeams returns all teams that belong to user, -// and that the user has joined. -func (org *Organization) GetUserTeams(ctx context.Context, userID int64) ([]*Team, error) { - return org.getUserTeams(ctx, userID) -} - -// AccessibleReposEnvironment operations involving the repositories that are -// accessible to a particular user -type AccessibleReposEnvironment interface { - CountRepos() (int64, error) - RepoIDs(page, pageSize int) ([]int64, error) - Repos(page, pageSize int) (repo_model.RepositoryList, error) - MirrorRepos() (repo_model.RepositoryList, error) - AddKeyword(keyword string) - SetSort(db.SearchOrderBy) -} - -type accessibleReposEnv struct { - org *Organization - user *user_model.User - team *Team - teamIDs []int64 - ctx context.Context - keyword string - orderBy db.SearchOrderBy -} - -// AccessibleReposEnv builds an AccessibleReposEnvironment for the repositories in `org` -// that are accessible to the specified user. -func AccessibleReposEnv(ctx context.Context, org *Organization, userID int64) (AccessibleReposEnvironment, error) { - var user *user_model.User - - if userID > 0 { - u, err := user_model.GetUserByID(ctx, userID) - if err != nil { - return nil, err - } - user = u - } - - teamIDs, err := org.getUserTeamIDs(ctx, userID) - if err != nil { - return nil, err - } - return &accessibleReposEnv{ - org: org, - user: user, - teamIDs: teamIDs, - ctx: ctx, - orderBy: db.SearchOrderByRecentUpdated, - }, nil -} - -// AccessibleTeamReposEnv an AccessibleReposEnvironment for the repositories in `org` -// that are accessible to the specified team. -func (org *Organization) AccessibleTeamReposEnv(ctx context.Context, team *Team) AccessibleReposEnvironment { - return &accessibleReposEnv{ - org: org, - team: team, - ctx: ctx, - orderBy: db.SearchOrderByRecentUpdated, - } -} - -func (env *accessibleReposEnv) cond() builder.Cond { - cond := builder.NewCond() - if env.team != nil { - cond = cond.And(builder.Eq{"team_repo.team_id": env.team.ID}) - } else { - if env.user == nil || !env.user.IsRestricted { - cond = cond.Or(builder.Eq{ - "`repository`.owner_id": env.org.ID, - "`repository`.is_private": false, - }) - } - if len(env.teamIDs) > 0 { - cond = cond.Or(builder.In("team_repo.team_id", env.teamIDs)) - } - } - if env.keyword != "" { - cond = cond.And(builder.Like{"`repository`.lower_name", strings.ToLower(env.keyword)}) - } - return cond -} - -func (env *accessibleReposEnv) CountRepos() (int64, error) { - repoCount, err := db.GetEngine(env.ctx). - Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id"). - Where(env.cond()). - Distinct("`repository`.id"). - Count(&repo_model.Repository{}) - if err != nil { - return 0, fmt.Errorf("count user repositories in organization: %w", err) - } - return repoCount, nil -} - -func (env *accessibleReposEnv) RepoIDs(page, pageSize int) ([]int64, error) { - if page <= 0 { - page = 1 - } - - repoIDs := make([]int64, 0, pageSize) - return repoIDs, db.GetEngine(env.ctx). - Table("repository"). - Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id"). - Where(env.cond()). - GroupBy("`repository`.id,`repository`."+strings.Fields(string(env.orderBy))[0]). - OrderBy(string(env.orderBy)). - Limit(pageSize, (page-1)*pageSize). - Cols("`repository`.id"). - Find(&repoIDs) -} - -func (env *accessibleReposEnv) Repos(page, pageSize int) (repo_model.RepositoryList, error) { - repoIDs, err := env.RepoIDs(page, pageSize) - if err != nil { - return nil, fmt.Errorf("GetUserRepositoryIDs: %w", err) - } - - repos := make([]*repo_model.Repository, 0, len(repoIDs)) - if len(repoIDs) == 0 { - return repos, nil - } - - return repos, db.GetEngine(env.ctx). - In("`repository`.id", repoIDs). - OrderBy(string(env.orderBy)). - Find(&repos) -} - -func (env *accessibleReposEnv) MirrorRepoIDs() ([]int64, error) { - repoIDs := make([]int64, 0, 10) - return repoIDs, db.GetEngine(env.ctx). - Table("repository"). - Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id AND `repository`.is_mirror=?", true). - Where(env.cond()). - GroupBy("`repository`.id, `repository`.updated_unix"). - OrderBy(string(env.orderBy)). - Cols("`repository`.id"). - Find(&repoIDs) -} - -func (env *accessibleReposEnv) MirrorRepos() (repo_model.RepositoryList, error) { - repoIDs, err := env.MirrorRepoIDs() - if err != nil { - return nil, fmt.Errorf("MirrorRepoIDs: %w", err) - } - - repos := make([]*repo_model.Repository, 0, len(repoIDs)) - if len(repoIDs) == 0 { - return repos, nil - } - - return repos, db.GetEngine(env.ctx). - In("`repository`.id", repoIDs). - Find(&repos) -} - -func (env *accessibleReposEnv) AddKeyword(keyword string) { - env.keyword = keyword -} - -func (env *accessibleReposEnv) SetSort(orderBy db.SearchOrderBy) { - env.orderBy = orderBy -} diff --git a/models/organization/org_list_test.go b/models/organization/org_list_test.go index edc8996f3ec..0f0f8a4bcd0 100644 --- a/models/organization/org_list_test.go +++ b/models/organization/org_list_test.go @@ -40,7 +40,7 @@ func TestFindOrgs(t *testing.T) { IncludePrivate: false, }) assert.NoError(t, err) - assert.Len(t, orgs, 0) + assert.Empty(t, orgs) total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ UserID: 4, diff --git a/models/organization/org_repo.go b/models/organization/org_repo.go deleted file mode 100644 index f7e59928f42..00000000000 --- a/models/organization/org_repo.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package organization - -import ( - "context" - - "code.gitea.io/gitea/models/db" - repo_model "code.gitea.io/gitea/models/repo" -) - -// GetOrgRepositories get repos belonging to the given organization -func GetOrgRepositories(ctx context.Context, orgID int64) (repo_model.RepositoryList, error) { - var orgRepos []*repo_model.Repository - return orgRepos, db.GetEngine(ctx).Where("owner_id = ?", orgID).Find(&orgRepos) -} diff --git a/models/organization/org_test.go b/models/organization/org_test.go index 7159f0fc465..2c5b4090df3 100644 --- a/models/organization/org_test.go +++ b/models/organization/org_test.go @@ -283,7 +283,7 @@ func TestGetOrgUsersByOrgID(t *testing.T) { OrgID: unittest.NonexistentID, }) assert.NoError(t, err) - assert.Len(t, orgUsers, 0) + assert.Empty(t, orgUsers) } func TestChangeOrgUserStatus(t *testing.T) { @@ -318,7 +318,7 @@ func TestAccessibleReposEnv_CountRepos(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) testSuccess := func(userID, expectedCount int64) { - env, err := organization.AccessibleReposEnv(db.DefaultContext, org, userID) + env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID) assert.NoError(t, err) count, err := env.CountRepos() assert.NoError(t, err) @@ -332,7 +332,7 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) testSuccess := func(userID int64, expectedRepoIDs []int64) { - env, err := organization.AccessibleReposEnv(db.DefaultContext, org, userID) + env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID) assert.NoError(t, err) repoIDs, err := env.RepoIDs(1, 100) assert.NoError(t, err) @@ -346,7 +346,7 @@ func TestAccessibleReposEnv_Repos(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) testSuccess := func(userID int64, expectedRepoIDs []int64) { - env, err := organization.AccessibleReposEnv(db.DefaultContext, org, userID) + env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID) assert.NoError(t, err) repos, err := env.Repos(1, 100) assert.NoError(t, err) @@ -365,7 +365,7 @@ func TestAccessibleReposEnv_MirrorRepos(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) testSuccess := func(userID int64, expectedRepoIDs []int64) { - env, err := organization.AccessibleReposEnv(db.DefaultContext, org, userID) + env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID) assert.NoError(t, err) repos, err := env.MirrorRepos() assert.NoError(t, err) diff --git a/models/organization/org_user.go b/models/organization/org_user.go index 1d3b2fab44d..08d936d922f 100644 --- a/models/organization/org_user.go +++ b/models/organization/org_user.go @@ -36,6 +36,21 @@ func init() { db.RegisterModel(new(OrgUser)) } +// ErrUserHasOrgs represents a "UserHasOrgs" kind of error. +type ErrUserHasOrgs struct { + UID int64 +} + +// IsErrUserHasOrgs checks if an error is a ErrUserHasOrgs. +func IsErrUserHasOrgs(err error) bool { + _, ok := err.(ErrUserHasOrgs) + return ok +} + +func (err ErrUserHasOrgs) Error() string { + return fmt.Sprintf("user still has membership of organizations [uid: %d]", err.UID) +} + // GetOrganizationCount returns count of membership of organization of the user. func GetOrganizationCount(ctx context.Context, u *user_model.User) (int64, error) { return db.GetEngine(ctx). diff --git a/models/organization/team.go b/models/organization/team.go index fb7f0c04939..96666da39a7 100644 --- a/models/organization/team.go +++ b/models/organization/team.go @@ -11,7 +11,6 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" - repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" @@ -78,9 +77,8 @@ type Team struct { LowerName string Name string Description string - AccessMode perm.AccessMode `xorm:"'authorize'"` - Repos []*repo_model.Repository `xorm:"-"` - Members []*user_model.User `xorm:"-"` + AccessMode perm.AccessMode `xorm:"'authorize'"` + Members []*user_model.User `xorm:"-"` NumRepos int NumMembers int Units []*TeamUnit `xorm:"-"` @@ -155,17 +153,6 @@ func (t *Team) IsMember(ctx context.Context, userID int64) bool { return isMember } -// LoadRepositories returns paginated repositories in team of organization. -func (t *Team) LoadRepositories(ctx context.Context) (err error) { - if t.Repos != nil { - return nil - } - t.Repos, err = GetTeamRepositories(ctx, &SearchTeamRepoOptions{ - TeamID: t.ID, - }) - return err -} - // LoadMembers returns paginated members in team of organization. func (t *Team) LoadMembers(ctx context.Context) (err error) { t.Members, err = GetTeamMembers(ctx, &SearchMembersOptions{ diff --git a/models/organization/team_list.go b/models/organization/team_list.go index 4ceb405e310..6f2a922e954 100644 --- a/models/organization/team_list.go +++ b/models/organization/team_list.go @@ -9,7 +9,6 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" - repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "xorm.io/builder" @@ -98,11 +97,11 @@ func SearchTeam(ctx context.Context, opts *SearchTeamOptions) (TeamList, int64, } // GetRepoTeams gets the list of teams that has access to the repository -func GetRepoTeams(ctx context.Context, repo *repo_model.Repository) (teams TeamList, err error) { +func GetRepoTeams(ctx context.Context, orgID, repoID int64) (teams TeamList, err error) { return teams, db.GetEngine(ctx). Join("INNER", "team_repo", "team_repo.team_id = team.id"). - Where("team.org_id = ?", repo.OwnerID). - And("team_repo.repo_id=?", repo.ID). + Where("team.org_id = ?", orgID). + And("team_repo.repo_id=?", repoID). OrderBy("CASE WHEN name LIKE '" + OwnerTeamName + "' THEN '' ELSE name END"). Find(&teams) } diff --git a/models/organization/team_repo.go b/models/organization/team_repo.go index c90dfdeda04..53edd203a8a 100644 --- a/models/organization/team_repo.go +++ b/models/organization/team_repo.go @@ -8,10 +8,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" - repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" - - "xorm.io/builder" ) // TeamRepo represents an team-repository relation. @@ -32,29 +29,6 @@ func HasTeamRepo(ctx context.Context, orgID, teamID, repoID int64) bool { return has } -type SearchTeamRepoOptions struct { - db.ListOptions - TeamID int64 -} - -// GetRepositories returns paginated repositories in team of organization. -func GetTeamRepositories(ctx context.Context, opts *SearchTeamRepoOptions) (repo_model.RepositoryList, error) { - sess := db.GetEngine(ctx) - if opts.TeamID > 0 { - sess = sess.In("id", - builder.Select("repo_id"). - From("team_repo"). - Where(builder.Eq{"team_id": opts.TeamID}), - ) - } - if opts.PageSize > 0 { - sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) - } - var repos []*repo_model.Repository - return repos, sess.OrderBy("repository.name"). - Find(&repos) -} - // AddTeamRepo adds a repo for an organization's team func AddTeamRepo(ctx context.Context, orgID, teamID, repoID int64) error { _, err := db.GetEngine(ctx).Insert(&TeamRepo{ diff --git a/models/organization/team_test.go b/models/organization/team_test.go index 8c34e7a6124..deaabbfa2c0 100644 --- a/models/organization/team_test.go +++ b/models/organization/team_test.go @@ -8,6 +8,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "github.com/stretchr/testify/assert" @@ -42,9 +43,12 @@ func TestTeam_GetRepositories(t *testing.T) { test := func(teamID int64) { team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) - assert.NoError(t, team.LoadRepositories(db.DefaultContext)) - assert.Len(t, team.Repos, team.NumRepos) - for _, repo := range team.Repos { + repos, err := repo_model.GetTeamRepositories(db.DefaultContext, &repo_model.SearchTeamRepoOptions{ + TeamID: team.ID, + }) + assert.NoError(t, err) + assert.Len(t, repos, team.NumRepos) + for _, repo := range repos { unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: teamID, RepoID: repo.ID}) } } diff --git a/models/packages/package.go b/models/packages/package.go index 417d62d1993..c12f345f0e2 100644 --- a/models/packages/package.go +++ b/models/packages/package.go @@ -301,6 +301,21 @@ func FindUnreferencedPackages(ctx context.Context) ([]*Package, error) { Find(&ps) } +// ErrUserOwnPackages notifies that the user (still) owns the packages. +type ErrUserOwnPackages struct { + UID int64 +} + +// IsErrUserOwnPackages checks if an error is an ErrUserOwnPackages. +func IsErrUserOwnPackages(err error) bool { + _, ok := err.(ErrUserOwnPackages) + return ok +} + +func (err ErrUserOwnPackages) Error() string { + return fmt.Sprintf("user still has ownership of packages [uid: %d]", err.UID) +} + // HasOwnerPackages tests if a user/org has accessible packages func HasOwnerPackages(ctx context.Context, ownerID int64) (bool, error) { return db.GetEngine(ctx). diff --git a/models/perm/access_mode_test.go b/models/perm/access_mode_test.go index 982fceee5a5..c4c7d483fb2 100644 --- a/models/perm/access_mode_test.go +++ b/models/perm/access_mode_test.go @@ -15,7 +15,7 @@ func TestAccessMode(t *testing.T) { m := ParseAccessMode(name) assert.Equal(t, AccessMode(i), m) } - assert.Equal(t, AccessMode(4), AccessModeOwner) + assert.Equal(t, AccessModeOwner, AccessMode(4)) assert.Equal(t, "owner", AccessModeOwner.ToString()) assert.Equal(t, AccessModeNone, ParseAccessMode("owner")) assert.Equal(t, AccessModeNone, ParseAccessMode("invalid")) diff --git a/models/project/column_test.go b/models/project/column_test.go index 911649fb726..566667e45d1 100644 --- a/models/project/column_test.go +++ b/models/project/column_test.go @@ -5,7 +5,6 @@ package project import ( "fmt" - "strings" "testing" "code.gitea.io/gitea/models/db" @@ -66,7 +65,7 @@ func Test_moveIssuesToAnotherColumn(t *testing.T) { issues, err = column1.GetIssues(db.DefaultContext) assert.NoError(t, err) - assert.Len(t, issues, 0) + assert.Empty(t, issues) issues, err = column2.GetIssues(db.DefaultContext) assert.NoError(t, err) @@ -123,5 +122,5 @@ func Test_NewColumn(t *testing.T) { ProjectID: project1.ID, }) assert.Error(t, err) - assert.True(t, strings.Contains(err.Error(), "maximum number of columns reached")) + assert.Contains(t, err.Error(), "maximum number of columns reached") } diff --git a/models/repo.go b/models/repo.go index 0dc8ee5df33..3e9c52fdd9c 100644 --- a/models/repo.go +++ b/models/repo.go @@ -6,15 +6,12 @@ package models import ( "context" - "fmt" "strconv" _ "image/jpeg" // Needed for jpeg support - asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" - access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -315,48 +312,3 @@ func DoctorUserStarNum(ctx context.Context) (err error) { return err } - -// DeleteDeployKey delete deploy keys -func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error { - key, err := asymkey_model.GetDeployKeyByID(ctx, id) - if err != nil { - if asymkey_model.IsErrDeployKeyNotExist(err) { - return nil - } - return fmt.Errorf("GetDeployKeyByID: %w", err) - } - - // Check if user has access to delete this key. - if !doer.IsAdmin { - repo, err := repo_model.GetRepositoryByID(ctx, key.RepoID) - if err != nil { - return fmt.Errorf("GetRepositoryByID: %w", err) - } - has, err := access_model.IsUserRepoAdmin(ctx, repo, doer) - if err != nil { - return fmt.Errorf("GetUserRepoPermission: %w", err) - } else if !has { - return asymkey_model.ErrKeyAccessDenied{ - UserID: doer.ID, - KeyID: key.ID, - Note: "deploy", - } - } - } - - if _, err := db.DeleteByID[asymkey_model.DeployKey](ctx, key.ID); err != nil { - return fmt.Errorf("delete deploy key [%d]: %w", key.ID, err) - } - - // Check if this is the last reference to same key content. - has, err := asymkey_model.IsDeployKeyExistByKeyID(ctx, key.KeyID) - if err != nil { - return err - } else if !has { - if _, err = db.DeleteByID[asymkey_model.PublicKey](ctx, key.KeyID); err != nil { - return err - } - } - - return nil -} diff --git a/models/repo/org_repo.go b/models/repo/org_repo.go new file mode 100644 index 00000000000..5f0af2d475a --- /dev/null +++ b/models/repo/org_repo.go @@ -0,0 +1,206 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "context" + "fmt" + "strings" + + "code.gitea.io/gitea/models/db" + org_model "code.gitea.io/gitea/models/organization" + user_model "code.gitea.io/gitea/models/user" + + "xorm.io/builder" +) + +// GetOrgRepositories get repos belonging to the given organization +func GetOrgRepositories(ctx context.Context, orgID int64) (RepositoryList, error) { + var orgRepos []*Repository + return orgRepos, db.GetEngine(ctx).Where("owner_id = ?", orgID).Find(&orgRepos) +} + +type SearchTeamRepoOptions struct { + db.ListOptions + TeamID int64 +} + +// GetRepositories returns paginated repositories in team of organization. +func GetTeamRepositories(ctx context.Context, opts *SearchTeamRepoOptions) (RepositoryList, error) { + sess := db.GetEngine(ctx) + if opts.TeamID > 0 { + sess = sess.In("id", + builder.Select("repo_id"). + From("team_repo"). + Where(builder.Eq{"team_id": opts.TeamID}), + ) + } + if opts.PageSize > 0 { + sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize) + } + var repos []*Repository + return repos, sess.OrderBy("repository.name"). + Find(&repos) +} + +// AccessibleReposEnvironment operations involving the repositories that are +// accessible to a particular user +type AccessibleReposEnvironment interface { + CountRepos() (int64, error) + RepoIDs(page, pageSize int) ([]int64, error) + Repos(page, pageSize int) (RepositoryList, error) + MirrorRepos() (RepositoryList, error) + AddKeyword(keyword string) + SetSort(db.SearchOrderBy) +} + +type accessibleReposEnv struct { + org *org_model.Organization + user *user_model.User + team *org_model.Team + teamIDs []int64 + ctx context.Context + keyword string + orderBy db.SearchOrderBy +} + +// AccessibleReposEnv builds an AccessibleReposEnvironment for the repositories in `org` +// that are accessible to the specified user. +func AccessibleReposEnv(ctx context.Context, org *org_model.Organization, userID int64) (AccessibleReposEnvironment, error) { + var user *user_model.User + + if userID > 0 { + u, err := user_model.GetUserByID(ctx, userID) + if err != nil { + return nil, err + } + user = u + } + + teamIDs, err := org.GetUserTeamIDs(ctx, userID) + if err != nil { + return nil, err + } + return &accessibleReposEnv{ + org: org, + user: user, + teamIDs: teamIDs, + ctx: ctx, + orderBy: db.SearchOrderByRecentUpdated, + }, nil +} + +// AccessibleTeamReposEnv an AccessibleReposEnvironment for the repositories in `org` +// that are accessible to the specified team. +func AccessibleTeamReposEnv(ctx context.Context, org *org_model.Organization, team *org_model.Team) AccessibleReposEnvironment { + return &accessibleReposEnv{ + org: org, + team: team, + ctx: ctx, + orderBy: db.SearchOrderByRecentUpdated, + } +} + +func (env *accessibleReposEnv) cond() builder.Cond { + cond := builder.NewCond() + if env.team != nil { + cond = cond.And(builder.Eq{"team_repo.team_id": env.team.ID}) + } else { + if env.user == nil || !env.user.IsRestricted { + cond = cond.Or(builder.Eq{ + "`repository`.owner_id": env.org.ID, + "`repository`.is_private": false, + }) + } + if len(env.teamIDs) > 0 { + cond = cond.Or(builder.In("team_repo.team_id", env.teamIDs)) + } + } + if env.keyword != "" { + cond = cond.And(builder.Like{"`repository`.lower_name", strings.ToLower(env.keyword)}) + } + return cond +} + +func (env *accessibleReposEnv) CountRepos() (int64, error) { + repoCount, err := db.GetEngine(env.ctx). + Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id"). + Where(env.cond()). + Distinct("`repository`.id"). + Count(&Repository{}) + if err != nil { + return 0, fmt.Errorf("count user repositories in organization: %w", err) + } + return repoCount, nil +} + +func (env *accessibleReposEnv) RepoIDs(page, pageSize int) ([]int64, error) { + if page <= 0 { + page = 1 + } + + repoIDs := make([]int64, 0, pageSize) + return repoIDs, db.GetEngine(env.ctx). + Table("repository"). + Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id"). + Where(env.cond()). + GroupBy("`repository`.id,`repository`."+strings.Fields(string(env.orderBy))[0]). + OrderBy(string(env.orderBy)). + Limit(pageSize, (page-1)*pageSize). + Cols("`repository`.id"). + Find(&repoIDs) +} + +func (env *accessibleReposEnv) Repos(page, pageSize int) (RepositoryList, error) { + repoIDs, err := env.RepoIDs(page, pageSize) + if err != nil { + return nil, fmt.Errorf("GetUserRepositoryIDs: %w", err) + } + + repos := make([]*Repository, 0, len(repoIDs)) + if len(repoIDs) == 0 { + return repos, nil + } + + return repos, db.GetEngine(env.ctx). + In("`repository`.id", repoIDs). + OrderBy(string(env.orderBy)). + Find(&repos) +} + +func (env *accessibleReposEnv) MirrorRepoIDs() ([]int64, error) { + repoIDs := make([]int64, 0, 10) + return repoIDs, db.GetEngine(env.ctx). + Table("repository"). + Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id AND `repository`.is_mirror=?", true). + Where(env.cond()). + GroupBy("`repository`.id, `repository`.updated_unix"). + OrderBy(string(env.orderBy)). + Cols("`repository`.id"). + Find(&repoIDs) +} + +func (env *accessibleReposEnv) MirrorRepos() (RepositoryList, error) { + repoIDs, err := env.MirrorRepoIDs() + if err != nil { + return nil, fmt.Errorf("MirrorRepoIDs: %w", err) + } + + repos := make([]*Repository, 0, len(repoIDs)) + if len(repoIDs) == 0 { + return repos, nil + } + + return repos, db.GetEngine(env.ctx). + In("`repository`.id", repoIDs). + Find(&repos) +} + +func (env *accessibleReposEnv) AddKeyword(keyword string) { + env.keyword = keyword +} + +func (env *accessibleReposEnv) SetSort(orderBy db.SearchOrderBy) { + env.orderBy = orderBy +} diff --git a/models/repo/repo.go b/models/repo/repo.go index 8d211caf40b..2d9b9de88d4 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -37,7 +37,7 @@ type ErrUserDoesNotHaveAccessToRepo struct { RepoName string } -// IsErrUserDoesNotHaveAccessToRepo checks if an error is a ErrRepoFileAlreadyExists. +// IsErrUserDoesNotHaveAccessToRepo checks if an error is a ErrUserDoesNotHaveAccessToRepo. func IsErrUserDoesNotHaveAccessToRepo(err error) bool { _, ok := err.(ErrUserDoesNotHaveAccessToRepo) return ok @@ -866,6 +866,21 @@ func (repo *Repository) TemplateRepo(ctx context.Context) *Repository { return repo } +// ErrUserOwnRepos represents a "UserOwnRepos" kind of error. +type ErrUserOwnRepos struct { + UID int64 +} + +// IsErrUserOwnRepos checks if an error is a ErrUserOwnRepos. +func IsErrUserOwnRepos(err error) bool { + _, ok := err.(ErrUserOwnRepos) + return ok +} + +func (err ErrUserOwnRepos) Error() string { + return fmt.Sprintf("user still has ownership of repositories [uid: %d]", err.UID) +} + type CountRepositoryOptions struct { OwnerID int64 Private optional.Option[bool] diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go index 6468e0f6058..6d88d170da3 100644 --- a/models/repo/repo_test.go +++ b/models/repo/repo_test.go @@ -144,8 +144,8 @@ func TestGetRepositoryByURL(t *testing.T) { assert.NotNil(t, repo) assert.NoError(t, err) - assert.Equal(t, repo.ID, int64(2)) - assert.Equal(t, repo.OwnerID, int64(2)) + assert.Equal(t, int64(2), repo.ID) + assert.Equal(t, int64(2), repo.OwnerID) } test(t, "https://try.gitea.io/user2/repo2") @@ -159,8 +159,8 @@ func TestGetRepositoryByURL(t *testing.T) { assert.NotNil(t, repo) assert.NoError(t, err) - assert.Equal(t, repo.ID, int64(2)) - assert.Equal(t, repo.OwnerID, int64(2)) + assert.Equal(t, int64(2), repo.ID) + assert.Equal(t, int64(2), repo.OwnerID) } test(t, "git+ssh://sshuser@try.gitea.io/user2/repo2") @@ -177,8 +177,8 @@ func TestGetRepositoryByURL(t *testing.T) { assert.NotNil(t, repo) assert.NoError(t, err) - assert.Equal(t, repo.ID, int64(2)) - assert.Equal(t, repo.OwnerID, int64(2)) + assert.Equal(t, int64(2), repo.ID) + assert.Equal(t, int64(2), repo.OwnerID) } test(t, "sshuser@try.gitea.io:user2/repo2") diff --git a/models/repo/star_test.go b/models/repo/star_test.go index aaac89d975d..b540f54310c 100644 --- a/models/repo/star_test.go +++ b/models/repo/star_test.go @@ -52,7 +52,7 @@ func TestRepository_GetStargazers2(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0}) assert.NoError(t, err) - assert.Len(t, gazers, 0) + assert.Empty(t, gazers) } func TestClearRepoStars(t *testing.T) { @@ -71,5 +71,5 @@ func TestClearRepoStars(t *testing.T) { gazers, err := repo_model.GetStargazers(db.DefaultContext, repo, db.ListOptions{Page: 0}) assert.NoError(t, err) - assert.Len(t, gazers, 0) + assert.Empty(t, gazers) } diff --git a/models/repo_transfer.go b/models/repo/transfer.go similarity index 73% rename from models/repo_transfer.go rename to models/repo/transfer.go index 37f591f65dc..43e15b33bcf 100644 --- a/models/repo_transfer.go +++ b/models/repo/transfer.go @@ -1,7 +1,7 @@ // Copyright 2021 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package models +package repo import ( "context" @@ -10,16 +10,58 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" - repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" "xorm.io/builder" ) +// ErrNoPendingRepoTransfer is an error type for repositories without a pending +// transfer request +type ErrNoPendingRepoTransfer struct { + RepoID int64 +} + +func (err ErrNoPendingRepoTransfer) Error() string { + return fmt.Sprintf("repository doesn't have a pending transfer [repo_id: %d]", err.RepoID) +} + +// IsErrNoPendingTransfer is an error type when a repository has no pending +// transfers +func IsErrNoPendingTransfer(err error) bool { + _, ok := err.(ErrNoPendingRepoTransfer) + return ok +} + +func (err ErrNoPendingRepoTransfer) Unwrap() error { + return util.ErrNotExist +} + +// ErrRepoTransferInProgress represents the state of a repository that has an +// ongoing transfer +type ErrRepoTransferInProgress struct { + Uname string + Name string +} + +// IsErrRepoTransferInProgress checks if an error is a ErrRepoTransferInProgress. +func IsErrRepoTransferInProgress(err error) bool { + _, ok := err.(ErrRepoTransferInProgress) + return ok +} + +func (err ErrRepoTransferInProgress) Error() string { + return fmt.Sprintf("repository is already being transferred [uname: %s, name: %s]", err.Uname, err.Name) +} + +func (err ErrRepoTransferInProgress) Unwrap() error { + return util.ErrAlreadyExist +} + // RepoTransfer is used to manage repository transfers -type RepoTransfer struct { +type RepoTransfer struct { //nolint ID int64 `xorm:"pk autoincr"` DoerID int64 Doer *user_model.User `xorm:"-"` @@ -126,7 +168,7 @@ func GetPendingRepositoryTransfers(ctx context.Context, opts *PendingRepositoryT // GetPendingRepositoryTransfer fetches the most recent and ongoing transfer // process for the repository -func GetPendingRepositoryTransfer(ctx context.Context, repo *repo_model.Repository) (*RepoTransfer, error) { +func GetPendingRepositoryTransfer(ctx context.Context, repo *Repository) (*RepoTransfer, error) { transfers, err := GetPendingRepositoryTransfers(ctx, &PendingRepositoryTransferOptions{RepoID: repo.ID}) if err != nil { return nil, err @@ -145,11 +187,11 @@ func DeleteRepositoryTransfer(ctx context.Context, repoID int64) error { } // TestRepositoryReadyForTransfer make sure repo is ready to transfer -func TestRepositoryReadyForTransfer(status repo_model.RepositoryStatus) error { +func TestRepositoryReadyForTransfer(status RepositoryStatus) error { switch status { - case repo_model.RepositoryBeingMigrated: + case RepositoryBeingMigrated: return errors.New("repo is not ready, currently migrating") - case repo_model.RepositoryPendingTransfer: + case RepositoryPendingTransfer: return ErrRepoTransferInProgress{} } return nil @@ -159,7 +201,7 @@ func TestRepositoryReadyForTransfer(status repo_model.RepositoryStatus) error { // it marks the repository transfer as "pending" func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.User, repoID int64, teams []*organization.Team) error { return db.WithTx(ctx, func(ctx context.Context) error { - repo, err := repo_model.GetRepositoryByID(ctx, repoID) + repo, err := GetRepositoryByID(ctx, repoID) if err != nil { return err } @@ -169,16 +211,16 @@ func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_m return err } - repo.Status = repo_model.RepositoryPendingTransfer - if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { + repo.Status = RepositoryPendingTransfer + if err := UpdateRepositoryCols(ctx, repo, "status"); err != nil { return err } // Check if new owner has repository with same name. - if has, err := repo_model.IsRepositoryModelExist(ctx, newOwner, repo.Name); err != nil { + if has, err := IsRepositoryModelExist(ctx, newOwner, repo.Name); err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { - return repo_model.ErrRepoAlreadyExist{ + return ErrRepoAlreadyExist{ Uname: newOwner.LowerName, Name: repo.Name, } diff --git a/models/repo/user_repo_test.go b/models/repo/user_repo_test.go index f2abc2ffa01..44ebe5f214c 100644 --- a/models/repo/user_repo_test.go +++ b/models/repo/user_repo_test.go @@ -21,7 +21,7 @@ func TestRepoAssignees(t *testing.T) { users, err := repo_model.GetRepoAssignees(db.DefaultContext, repo2) assert.NoError(t, err) assert.Len(t, users, 1) - assert.Equal(t, users[0].ID, int64(2)) + assert.Equal(t, int64(2), users[0].ID) repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}) users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21) diff --git a/models/repo/watch_test.go b/models/repo/watch_test.go index a95a2679616..c39ef607e8b 100644 --- a/models/repo/watch_test.go +++ b/models/repo/watch_test.go @@ -41,7 +41,7 @@ func TestGetWatchers(t *testing.T) { watches, err = repo_model.GetWatchers(db.DefaultContext, unittest.NonexistentID) assert.NoError(t, err) - assert.Len(t, watches, 0) + assert.Empty(t, watches) } func TestRepository_GetWatchers(t *testing.T) { @@ -58,7 +58,7 @@ func TestRepository_GetWatchers(t *testing.T) { repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 9}) watchers, err = repo_model.GetRepoWatchers(db.DefaultContext, repo.ID, db.ListOptions{Page: 1}) assert.NoError(t, err) - assert.Len(t, watchers, 0) + assert.Empty(t, watchers) } func TestWatchIfAuto(t *testing.T) { diff --git a/models/unittest/unit_tests.go b/models/unittest/unit_tests.go index 3b0f28d3a60..4ac858e04ea 100644 --- a/models/unittest/unit_tests.go +++ b/models/unittest/unit_tests.go @@ -79,7 +79,7 @@ func AssertExistsAndLoadMap(t assert.TestingT, table string, conditions ...any) e := db.GetEngine(db.DefaultContext).Table(table) res, err := whereOrderConditions(e, conditions).Query() assert.NoError(t, err) - assert.True(t, len(res) == 1, + assert.Len(t, res, 1, "Expected to find one row in %s (with conditions %+v), but found %d", table, conditions, len(res), ) diff --git a/models/user/email_address_test.go b/models/user/email_address_test.go index c2e010d95b3..d72d873de2c 100644 --- a/models/user/email_address_test.go +++ b/models/user/email_address_test.go @@ -97,8 +97,7 @@ func TestListEmails(t *testing.T) { } emails, count, err := user_model.SearchEmails(db.DefaultContext, opts) assert.NoError(t, err) - assert.NotEqual(t, int64(0), count) - assert.True(t, count > 5) + assert.Greater(t, count, int64(5)) contains := func(match func(s *user_model.SearchEmailResult) bool) bool { for _, v := range emails { diff --git a/models/user/setting_test.go b/models/user/setting_test.go index c56fe930750..c607d9fd008 100644 --- a/models/user/setting_test.go +++ b/models/user/setting_test.go @@ -56,5 +56,5 @@ func TestSettings(t *testing.T) { assert.NoError(t, err) settings, err = user_model.GetUserAllSettings(db.DefaultContext, 99) assert.NoError(t, err) - assert.Len(t, settings, 0) + assert.Empty(t, settings) } diff --git a/models/user/user.go b/models/user/user.go index bd92693b6e0..72caafc3baa 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -788,6 +788,21 @@ func createUser(ctx context.Context, u *User, meta *Meta, createdByAdmin bool, o return committer.Commit() } +// ErrDeleteLastAdminUser represents a "DeleteLastAdminUser" kind of error. +type ErrDeleteLastAdminUser struct { + UID int64 +} + +// IsErrDeleteLastAdminUser checks if an error is a ErrDeleteLastAdminUser. +func IsErrDeleteLastAdminUser(err error) bool { + _, ok := err.(ErrDeleteLastAdminUser) + return ok +} + +func (err ErrDeleteLastAdminUser) Error() string { + return fmt.Sprintf("can not delete the last admin user [uid: %d]", err.UID) +} + // IsLastAdminUser check whether user is the last admin func IsLastAdminUser(ctx context.Context, user *User) bool { if user.IsAdmin && CountUsers(ctx, &CountUserFilter{IsAdmin: optional.Some(true)}) <= 1 { diff --git a/models/user/user_test.go b/models/user/user_test.go index 6701be39a55..7ebc64f69e7 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -201,7 +201,7 @@ func TestNewGitSig(t *testing.T) { assert.NotContains(t, sig.Name, "<") assert.NotContains(t, sig.Name, ">") assert.NotContains(t, sig.Name, "\n") - assert.NotEqual(t, len(strings.TrimSpace(sig.Name)), 0) + assert.NotEmpty(t, strings.TrimSpace(sig.Name)) } } @@ -216,7 +216,7 @@ func TestDisplayName(t *testing.T) { if len(strings.TrimSpace(user.FullName)) == 0 { assert.Equal(t, user.Name, displayName) } - assert.NotEqual(t, len(strings.TrimSpace(displayName)), 0) + assert.NotEmpty(t, strings.TrimSpace(displayName)) } } @@ -322,15 +322,15 @@ func TestGetMaileableUsersByIDs(t *testing.T) { assert.NoError(t, err) assert.Len(t, results, 1) if len(results) > 1 { - assert.Equal(t, results[0].ID, 1) + assert.Equal(t, 1, results[0].ID) } results, err = user_model.GetMaileableUsersByIDs(db.DefaultContext, []int64{1, 4}, true) assert.NoError(t, err) assert.Len(t, results, 2) if len(results) > 2 { - assert.Equal(t, results[0].ID, 1) - assert.Equal(t, results[1].ID, 4) + assert.Equal(t, 1, results[0].ID) + assert.Equal(t, 4, results[1].ID) } } @@ -499,7 +499,7 @@ func Test_ValidateUser(t *testing.T) { {ID: 2, Visibility: structs.VisibleTypePrivate}: true, } for kase, expected := range kases { - assert.EqualValues(t, expected, nil == user_model.ValidateUser(kase), fmt.Sprintf("case: %+v", kase)) + assert.EqualValues(t, expected, nil == user_model.ValidateUser(kase), "case: %+v", kase) } } @@ -570,11 +570,11 @@ func TestDisabledUserFeatures(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - assert.Len(t, setting.Admin.UserDisabledFeatures.Values(), 0) + assert.Empty(t, setting.Admin.UserDisabledFeatures.Values()) // 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) + assert.Empty(t, user_model.DisabledFeaturesWithLoginType(user).Values()) for _, f := range testValues.Values() { assert.False(t, user_model.IsFeatureDisabledWithLoginType(user, f)) } @@ -600,5 +600,5 @@ func TestGetInactiveUsers(t *testing.T) { interval := time.Now().Unix() - 1730468968 + 3600*24 users, err = user_model.GetInactiveUsers(db.DefaultContext, time.Duration(interval*int64(time.Second))) assert.NoError(t, err) - assert.Len(t, users, 0) + assert.Empty(t, users) } diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go index f4403776cec..c6c3f40d46e 100644 --- a/models/webhook/webhook_test.go +++ b/models/webhook/webhook_test.go @@ -43,7 +43,7 @@ func TestWebhook_History(t *testing.T) { webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2}) tasks, err = webhook.History(db.DefaultContext, 0) assert.NoError(t, err) - assert.Len(t, tasks, 0) + assert.Empty(t, tasks) } func TestWebhook_UpdateEvent(t *testing.T) { @@ -206,7 +206,7 @@ func TestHookTasks(t *testing.T) { hookTasks, err = HookTasks(db.DefaultContext, unittest.NonexistentID, 1) assert.NoError(t, err) - assert.Len(t, hookTasks, 0) + assert.Empty(t, hookTasks) } func TestCreateHookTask(t *testing.T) { diff --git a/modules/activitypub/client_test.go b/modules/activitypub/client_test.go index 65ea8d4d5bf..d0c48454457 100644 --- a/modules/activitypub/client_test.go +++ b/modules/activitypub/client_test.go @@ -8,7 +8,6 @@ import ( "io" "net/http" "net/http/httptest" - "regexp" "testing" "code.gitea.io/gitea/models/db" @@ -28,9 +27,9 @@ func TestActivityPubSignedPost(t *testing.T) { expected := "BODY" srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Regexp(t, regexp.MustCompile("^"+setting.Federation.DigestAlgorithm), r.Header.Get("Digest")) + assert.Regexp(t, "^"+setting.Federation.DigestAlgorithm, r.Header.Get("Digest")) assert.Contains(t, r.Header.Get("Signature"), pubID) - assert.Equal(t, r.Header.Get("Content-Type"), ActivityStreamsContentType) + assert.Equal(t, ActivityStreamsContentType, r.Header.Get("Content-Type")) body, err := io.ReadAll(r.Body) assert.NoError(t, err) assert.Equal(t, expected, string(body)) diff --git a/modules/assetfs/layered_test.go b/modules/assetfs/layered_test.go index b82111e745e..03a3ae0d7cc 100644 --- a/modules/assetfs/layered_test.go +++ b/modules/assetfs/layered_test.go @@ -58,7 +58,7 @@ func TestLayered(t *testing.T) { assertRead := func(expected string, expectedErr error, elems ...string) { bs, err := assets.ReadFile(elems...) if err != nil { - assert.ErrorAs(t, err, &expectedErr) + assert.ErrorIs(t, err, expectedErr) } else { assert.NoError(t, err) assert.Equal(t, expected, string(bs)) diff --git a/modules/auth/pam/pam_test.go b/modules/auth/pam/pam_test.go index c277d59c415..7265b5d0c15 100644 --- a/modules/auth/pam/pam_test.go +++ b/modules/auth/pam/pam_test.go @@ -15,5 +15,5 @@ func TestPamAuth(t *testing.T) { result, err := Auth("gitea", "user1", "false-pwd") assert.Error(t, err) assert.EqualError(t, err, "Authentication failure") - assert.Len(t, result, 0) + assert.Len(t, result) } diff --git a/modules/auth/password/hash/dummy_test.go b/modules/auth/password/hash/dummy_test.go index f3b36df6250..e56e3f1a7f7 100644 --- a/modules/auth/password/hash/dummy_test.go +++ b/modules/auth/password/hash/dummy_test.go @@ -18,7 +18,7 @@ func TestDummyHasher(t *testing.T) { password, salt := "password", "ZogKvWdyEx" hash, err := dummy.Hash(password, salt) - assert.Nil(t, err) + assert.NoError(t, err) assert.Equal(t, hash, salt+":"+password) assert.True(t, dummy.VerifyPassword(password, hash, salt)) diff --git a/modules/auth/password/password.go b/modules/auth/password/password.go index 85f9780709c..c66b62937fd 100644 --- a/modules/auth/password/password.go +++ b/modules/auth/password/password.go @@ -99,10 +99,10 @@ func IsComplexEnough(pwd string) bool { func Generate(n int) (string, error) { NewComplexity() buffer := make([]byte, n) - max := big.NewInt(int64(len(validChars))) + maxInt := big.NewInt(int64(len(validChars))) for { for j := 0; j < n; j++ { - rnd, err := rand.Int(rand.Reader, max) + rnd, err := rand.Int(rand.Reader, maxInt) if err != nil { return "", err } diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go index 86cccdf2092..f63679048e3 100644 --- a/modules/base/tool_test.go +++ b/modules/base/tool_test.go @@ -6,7 +6,6 @@ package base import ( "crypto/sha1" "fmt" - "os" "testing" "time" @@ -157,7 +156,7 @@ func TestStringsToInt64s(t *testing.T) { testSuccess([]string{"1", "4", "16", "64", "256"}, []int64{1, 4, 16, 64, 256}) ints, err := StringsToInt64s([]string{"-1", "a"}) - assert.Len(t, ints, 0) + assert.Empty(t, ints) assert.Error(t, err) } @@ -172,9 +171,9 @@ func TestInt64sToStrings(t *testing.T) { // TODO: Test EntryIcon func TestSetupGiteaRoot(t *testing.T) { - _ = os.Setenv("GITEA_ROOT", "test") + t.Setenv("GITEA_ROOT", "test") assert.Equal(t, "test", SetupGiteaRoot()) - _ = os.Setenv("GITEA_ROOT", "") + t.Setenv("GITEA_ROOT", "") assert.NotEqual(t, "test", SetupGiteaRoot()) } diff --git a/modules/dump/dumper_test.go b/modules/dump/dumper_test.go index b444fa2de53..2db3a598a4c 100644 --- a/modules/dump/dumper_test.go +++ b/modules/dump/dumper_test.go @@ -25,7 +25,7 @@ func TestPrepareFileNameAndType(t *testing.T) { assert.Equal(t, fmt.Sprintf("outFile=%s, outType=%s", expFile, expType), fmt.Sprintf("outFile=%s, outType=%s", outFile, outType), - fmt.Sprintf("argFile=%s, argType=%s", argFile, argType), + "argFile=%s, argType=%s", argFile, argType, ) } diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go index 7dfda721554..532dbad9894 100644 --- a/modules/git/batch_reader.go +++ b/modules/git/batch_reader.go @@ -7,10 +7,8 @@ import ( "bufio" "bytes" "context" - "fmt" "io" "math" - "runtime" "strconv" "strings" @@ -32,7 +30,6 @@ type WriteCloserError interface { func ensureValidGitRepository(ctx context.Context, repoPath string) error { stderr := strings.Builder{} err := NewCommand(ctx, "rev-parse"). - SetDescription(fmt.Sprintf("%s rev-parse [repo_path: %s]", GitExecutable, repoPath)). Run(&RunOpts{ Dir: repoPath, Stderr: &stderr, @@ -62,13 +59,9 @@ func catFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError, cancel() }() - _, filename, line, _ := runtime.Caller(2) - filename = strings.TrimPrefix(filename, callerPrefix) - go func() { stderr := strings.Builder{} err := NewCommand(ctx, "cat-file", "--batch-check"). - SetDescription(fmt.Sprintf("%s cat-file --batch-check [repo_path: %s] (%s:%d)", GitExecutable, repoPath, filename, line)). Run(&RunOpts{ Dir: repoPath, Stdin: batchStdinReader, @@ -114,13 +107,9 @@ func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi cancel() }() - _, filename, line, _ := runtime.Caller(2) - filename = strings.TrimPrefix(filename, callerPrefix) - go func() { stderr := strings.Builder{} err := NewCommand(ctx, "cat-file", "--batch"). - SetDescription(fmt.Sprintf("%s cat-file --batch [repo_path: %s] (%s:%d)", GitExecutable, repoPath, filename, line)). Run(&RunOpts{ Dir: repoPath, Stdin: batchStdinReader, @@ -320,13 +309,6 @@ func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBu return mode, fname, sha, n, err } -var callerPrefix string - -func init() { - _, filename, _, _ := runtime.Caller(0) - callerPrefix = strings.TrimSuffix(filename, "modules/git/batch_reader.go") -} - func DiscardFull(rd *bufio.Reader, discard int64) error { if discard > math.MaxInt32 { n, err := rd.Discard(math.MaxInt32) diff --git a/modules/git/blame.go b/modules/git/blame.go index a9b2706f217..cad720edf43 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -7,7 +7,6 @@ import ( "bufio" "bytes" "context" - "fmt" "io" "os" @@ -142,9 +141,7 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath // There is no equivalent on Windows. May be implemented if Gitea uses an external git backend. cmd.AddOptionValues("--ignore-revs-file", *ignoreRevsFile) } - cmd.AddDynamicArguments(commit.ID.String()). - AddDashesAndList(file). - SetDescription(fmt.Sprintf("GetBlame [repo_path: %s]", repoPath)) + cmd.AddDynamicArguments(commit.ID.String()).AddDashesAndList(file) reader, stdout, err := os.Pipe() if err != nil { if ignoreRevsFile != nil { diff --git a/modules/git/command.go b/modules/git/command.go index 22cb275ab2d..b231c3beea0 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -12,6 +12,7 @@ import ( "io" "os" "os/exec" + "path/filepath" "runtime" "strings" "time" @@ -43,18 +44,24 @@ type Command struct { prog string args []string parentContext context.Context - desc string globalArgsLength int brokenArgs []string } -func (c *Command) String() string { - return c.toString(false) +func logArgSanitize(arg string) string { + if strings.Contains(arg, "://") && strings.Contains(arg, "@") { + return util.SanitizeCredentialURLs(arg) + } else if filepath.IsAbs(arg) { + base := filepath.Base(arg) + dir := filepath.Dir(arg) + return filepath.Join(filepath.Base(dir), base) + } + return arg } -func (c *Command) toString(sanitizing bool) string { +func (c *Command) LogString() string { // WARNING: this function is for debugging purposes only. It's much better than old code (which only joins args with space), - // It's impossible to make a simple and 100% correct implementation of argument quoting for different platforms. + // It's impossible to make a simple and 100% correct implementation of argument quoting for different platforms here. debugQuote := func(s string) string { if strings.ContainsAny(s, " `'\"\t\r\n") { return fmt.Sprintf("%q", s) @@ -63,12 +70,11 @@ func (c *Command) toString(sanitizing bool) string { } a := make([]string, 0, len(c.args)+1) a = append(a, debugQuote(c.prog)) - for _, arg := range c.args { - if sanitizing && (strings.Contains(arg, "://") && strings.Contains(arg, "@")) { - a = append(a, debugQuote(util.SanitizeCredentialURLs(arg))) - } else { - a = append(a, debugQuote(arg)) - } + if c.globalArgsLength > 0 { + a = append(a, "...global...") + } + for i := c.globalArgsLength; i < len(c.args); i++ { + a = append(a, debugQuote(logArgSanitize(c.args[i]))) } return strings.Join(a, " ") } @@ -112,12 +118,6 @@ func (c *Command) SetParentContext(ctx context.Context) *Command { return c } -// SetDescription sets the description for this command which be returned on c.String() -func (c *Command) SetDescription(desc string) *Command { - c.desc = desc - return c -} - // isSafeArgumentValue checks if the argument is safe to be used as a value (not an option) func isSafeArgumentValue(s string) bool { return s == "" || s[0] != '-' @@ -271,8 +271,12 @@ var ErrBrokenCommand = errors.New("git command is broken") // Run runs the command with the RunOpts func (c *Command) Run(opts *RunOpts) error { + return c.run(1, opts) +} + +func (c *Command) run(skip int, opts *RunOpts) error { if len(c.brokenArgs) != 0 { - log.Error("git command is broken: %s, broken args: %s", c.String(), strings.Join(c.brokenArgs, " ")) + log.Error("git command is broken: %s, broken args: %s", c.LogString(), strings.Join(c.brokenArgs, " ")) return ErrBrokenCommand } if opts == nil { @@ -285,20 +289,14 @@ func (c *Command) Run(opts *RunOpts) error { timeout = defaultCommandExecutionTimeout } - if len(opts.Dir) == 0 { - log.Debug("git.Command.Run: %s", c) - } else { - log.Debug("git.Command.RunDir(%s): %s", opts.Dir, c) - } - - desc := c.desc - if desc == "" { - if opts.Dir == "" { - desc = fmt.Sprintf("git: %s", c.toString(true)) - } else { - desc = fmt.Sprintf("git(dir:%s): %s", opts.Dir, c.toString(true)) - } + var desc string + callerInfo := util.CallerFuncName(1 /* util */ + 1 /* this */ + skip /* parent */) + if pos := strings.LastIndex(callerInfo, "/"); pos >= 0 { + callerInfo = callerInfo[pos+1:] } + // these logs are for debugging purposes only, so no guarantee of correctness or stability + desc = fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), c.LogString()) + log.Debug("git.Command: %s", desc) var ctx context.Context var cancel context.CancelFunc @@ -401,7 +399,7 @@ func IsErrorExitCode(err error, code int) bool { // RunStdString runs the command with options and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr). func (c *Command) RunStdString(opts *RunOpts) (stdout, stderr string, runErr RunStdError) { - stdoutBytes, stderrBytes, err := c.RunStdBytes(opts) + stdoutBytes, stderrBytes, err := c.runStdBytes(opts) stdout = util.UnsafeBytesToString(stdoutBytes) stderr = util.UnsafeBytesToString(stderrBytes) if err != nil { @@ -413,6 +411,10 @@ func (c *Command) RunStdString(opts *RunOpts) (stdout, stderr string, runErr Run // RunStdBytes runs the command with options and returns stdout/stderr as bytes. and store stderr to returned error (err combined with stderr). func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) { + return c.runStdBytes(opts) +} + +func (c *Command) runStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) { if opts == nil { opts = &RunOpts{} } @@ -435,7 +437,7 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS PipelineFunc: opts.PipelineFunc, } - err := c.Run(newOpts) + err := c.run(2, newOpts) stderr = stderrBuf.Bytes() if err != nil { return nil, stderr, &runStdError{err: err, stderr: util.UnsafeBytesToString(stderr)} diff --git a/modules/git/command_test.go b/modules/git/command_test.go index 9a6228c9ad0..0823afd7f76 100644 --- a/modules/git/command_test.go +++ b/modules/git/command_test.go @@ -55,8 +55,8 @@ func TestGitArgument(t *testing.T) { func TestCommandString(t *testing.T) { cmd := NewCommandContextNoGlobals(context.Background(), "a", "-m msg", "it's a test", `say "hello"`) - assert.EqualValues(t, cmd.prog+` a "-m msg" "it's a test" "say \"hello\""`, cmd.String()) + assert.EqualValues(t, cmd.prog+` a "-m msg" "it's a test" "say \"hello\""`, cmd.LogString()) - cmd = NewCommandContextNoGlobals(context.Background(), "url: https://a:b@c/") - assert.EqualValues(t, cmd.prog+` "url: https://sanitized-credential@c/"`, cmd.toString(true)) + cmd = NewCommandContextNoGlobals(context.Background(), "url: https://a:b@c/", "/root/dir-a/dir-b") + assert.EqualValues(t, cmd.prog+` "url: https://sanitized-credential@c/" dir-a/dir-b`, cmd.LogString()) } diff --git a/modules/git/commit_sha256_test.go b/modules/git/commit_sha256_test.go index 3b8b6d3763a..2184a9c47cf 100644 --- a/modules/git/commit_sha256_test.go +++ b/modules/git/commit_sha256_test.go @@ -146,7 +146,7 @@ func TestHasPreviousCommitSha256(t *testing.T) { parentSHA := MustIDFromString("b0ec7af4547047f12d5093e37ef8f1b3b5415ed8ee17894d43a34d7d34212e9c") notParentSHA := MustIDFromString("42e334efd04cd36eea6da0599913333c26116e1a537ca76e5b6e4af4dda00236") assert.Equal(t, objectFormat, parentSHA.Type()) - assert.Equal(t, objectFormat.Name(), "sha256") + assert.Equal(t, "sha256", objectFormat.Name()) haz, err := commit.HasPreviousCommit(parentSHA) assert.NoError(t, err) diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go index bf381a53501..6ac65564dc1 100644 --- a/modules/git/commit_test.go +++ b/modules/git/commit_test.go @@ -343,9 +343,9 @@ func TestGetCommitFileStatusMerges(t *testing.T) { }, } - assert.Equal(t, commitFileStatus.Added, expected.Added) - assert.Equal(t, commitFileStatus.Removed, expected.Removed) - assert.Equal(t, commitFileStatus.Modified, expected.Modified) + assert.Equal(t, expected.Added, commitFileStatus.Added) + assert.Equal(t, expected.Removed, commitFileStatus.Removed) + assert.Equal(t, expected.Modified, commitFileStatus.Modified) } func Test_GetCommitBranchStart(t *testing.T) { diff --git a/modules/git/grep_test.go b/modules/git/grep_test.go index 6a99f804070..005d5397267 100644 --- a/modules/git/grep_test.go +++ b/modules/git/grep_test.go @@ -73,9 +73,9 @@ func TestGrepSearch(t *testing.T) { res, err = GrepSearch(context.Background(), repo, "no-such-content", GrepOptions{}) assert.NoError(t, err) - assert.Len(t, res, 0) + assert.Empty(t, res) res, err = GrepSearch(context.Background(), &Repository{Path: "no-such-git-repo"}, "no-such-content", GrepOptions{}) assert.Error(t, err) - assert.Len(t, res, 0) + assert.Empty(t, res) } diff --git a/modules/git/parse_nogogit_test.go b/modules/git/parse_nogogit_test.go index 23fddb014c1..a4436ce499a 100644 --- a/modules/git/parse_nogogit_test.go +++ b/modules/git/parse_nogogit_test.go @@ -100,5 +100,5 @@ func TestParseTreeEntriesInvalid(t *testing.T) { // there was a panic: "runtime error: slice bounds out of range" when the input was invalid: #20315 entries, err := ParseTreeEntries([]byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af")) assert.Error(t, err) - assert.Len(t, entries, 0) + assert.Empty(t, entries) } diff --git a/modules/git/remote.go b/modules/git/remote.go index 7b10e6b6631..de8d74eded7 100644 --- a/modules/git/remote.go +++ b/modules/git/remote.go @@ -5,8 +5,12 @@ package git import ( "context" + "fmt" + "net/url" + "strings" giturl "code.gitea.io/gitea/modules/git/url" + "code.gitea.io/gitea/modules/util" ) // GetRemoteAddress returns remote url of git repository in the repoPath with special remote name @@ -37,3 +41,61 @@ func GetRemoteURL(ctx context.Context, repoPath, remoteName string) (*giturl.Git } return giturl.Parse(addr) } + +// ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error. +type ErrInvalidCloneAddr struct { + Host string + IsURLError bool + IsInvalidPath bool + IsProtocolInvalid bool + IsPermissionDenied bool + LocalPath bool +} + +// IsErrInvalidCloneAddr checks if an error is a ErrInvalidCloneAddr. +func IsErrInvalidCloneAddr(err error) bool { + _, ok := err.(*ErrInvalidCloneAddr) + return ok +} + +func (err *ErrInvalidCloneAddr) Error() string { + if err.IsInvalidPath { + return fmt.Sprintf("migration/cloning from '%s' is not allowed: the provided path is invalid", err.Host) + } + if err.IsProtocolInvalid { + return fmt.Sprintf("migration/cloning from '%s' is not allowed: the provided url protocol is not allowed", err.Host) + } + if err.IsPermissionDenied { + return fmt.Sprintf("migration/cloning from '%s' is not allowed.", err.Host) + } + if err.IsURLError { + return fmt.Sprintf("migration/cloning from '%s' is not allowed: the provided url is invalid", err.Host) + } + + return fmt.Sprintf("migration/cloning from '%s' is not allowed", err.Host) +} + +func (err *ErrInvalidCloneAddr) Unwrap() error { + return util.ErrInvalidArgument +} + +// ParseRemoteAddr checks if given remote address is valid, +// and returns composed URL with needed username and password. +func ParseRemoteAddr(remoteAddr, authUsername, authPassword string) (string, error) { + remoteAddr = strings.TrimSpace(remoteAddr) + // Remote address can be HTTP/HTTPS/Git URL or local path. + if strings.HasPrefix(remoteAddr, "http://") || + strings.HasPrefix(remoteAddr, "https://") || + strings.HasPrefix(remoteAddr, "git://") { + u, err := url.Parse(remoteAddr) + if err != nil { + return "", &ErrInvalidCloneAddr{IsURLError: true, Host: remoteAddr} + } + if len(authUsername)+len(authPassword) > 0 { + u.User = url.UserPassword(authUsername, authPassword) + } + remoteAddr = u.String() + } + + return remoteAddr, nil +} diff --git a/modules/git/repo.go b/modules/git/repo.go index fc6e6e7accb..0993a4ac37d 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -18,7 +18,6 @@ import ( "time" "code.gitea.io/gitea/modules/proxy" - "code.gitea.io/gitea/modules/util" ) // GPGSettings represents the default GPG settings for this repository @@ -160,12 +159,6 @@ func CloneWithArgs(ctx context.Context, args TrustedCmdArgs, from, to string, op } cmd.AddDashesAndList(from, to) - if strings.Contains(from, "://") && strings.Contains(from, "@") { - cmd.SetDescription(fmt.Sprintf("clone branch %s from %s to %s (shared: %t, mirror: %t, depth: %d)", opts.Branch, util.SanitizeCredentialURLs(from), to, opts.Shared, opts.Mirror, opts.Depth)) - } else { - cmd.SetDescription(fmt.Sprintf("clone branch %s from %s to %s (shared: %t, mirror: %t, depth: %d)", opts.Branch, from, to, opts.Shared, opts.Mirror, opts.Depth)) - } - if opts.Timeout <= 0 { opts.Timeout = -1 } @@ -213,12 +206,6 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error { } cmd.AddDashesAndList(remoteBranchArgs...) - if strings.Contains(opts.Remote, "://") && strings.Contains(opts.Remote, "@") { - cmd.SetDescription(fmt.Sprintf("push branch %s to %s (force: %t, mirror: %t)", opts.Branch, util.SanitizeCredentialURLs(opts.Remote), opts.Force, opts.Mirror)) - } else { - cmd.SetDescription(fmt.Sprintf("push branch %s to %s (force: %t, mirror: %t)", opts.Branch, opts.Remote, opts.Force, opts.Mirror)) - } - stdout, stderr, err := cmd.RunStdString(&RunOpts{Env: opts.Env, Timeout: opts.Timeout, Dir: repoPath}) if err != nil { if strings.Contains(stderr, "non-fast-forward") { diff --git a/modules/git/repo_branch_test.go b/modules/git/repo_branch_test.go index 009c545832a..5d3b8abb3a8 100644 --- a/modules/git/repo_branch_test.go +++ b/modules/git/repo_branch_test.go @@ -34,7 +34,7 @@ func TestRepository_GetBranches(t *testing.T) { branches, countAll, err = bareRepo1.GetBranchNames(5, 1) assert.NoError(t, err) - assert.Len(t, branches, 0) + assert.Empty(t, branches) assert.EqualValues(t, 3, countAll) assert.ElementsMatch(t, []string{}, branches) } @@ -66,7 +66,7 @@ func TestGetRefsBySha(t *testing.T) { // do not exist branches, err := bareRepo5.GetRefsBySha("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", "") assert.NoError(t, err) - assert.Len(t, branches, 0) + assert.Empty(t, branches) // refs/pull/1/head branches, err = bareRepo5.GetRefsBySha("c83380d7056593c51a699d12b9c00627bd5743e9", PullPrefix) diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 9405634df12..9ffadb833d8 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -465,15 +465,15 @@ func (repo *Repository) getBranches(env []string, commitID string, limit int) ([ refs := strings.Split(stdout, "\n") - var max int + var maxNum int if len(refs) > limit { - max = limit + maxNum = limit } else { - max = len(refs) - 1 + maxNum = len(refs) - 1 } - branches := make([]string, max) - for i, ref := range refs[:max] { + branches := make([]string, maxNum) + for i, ref := range refs[:maxNum] { parts := strings.Fields(ref) branches[i] = parts[len(parts)-1] diff --git a/modules/git/repo_compare_test.go b/modules/git/repo_compare_test.go index 99838731867..454ed6b9f85 100644 --- a/modules/git/repo_compare_test.go +++ b/modules/git/repo_compare_test.go @@ -72,7 +72,7 @@ func TestReadPatch(t *testing.T) { assert.Empty(t, noFile) assert.Empty(t, noCommit) assert.Len(t, oldCommit, 40) - assert.True(t, oldCommit == "6e8e2a6f9efd71dbe6917816343ed8415ad696c3") + assert.Equal(t, "6e8e2a6f9efd71dbe6917816343ed8415ad696c3", oldCommit) } func TestReadWritePullHead(t *testing.T) { @@ -113,7 +113,7 @@ func TestReadWritePullHead(t *testing.T) { } assert.Len(t, headContents, 40) - assert.True(t, headContents == newCommit) + assert.Equal(t, headContents, newCommit) // Remove file after the test err = repo.RemoveReference(PullPrefix + "1/head") diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go index 3f1115066a0..433e8c4c27c 100644 --- a/modules/graceful/manager.go +++ b/modules/graceful/manager.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "code.gitea.io/gitea/modules/gtprof" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" @@ -136,7 +137,7 @@ func (g *Manager) doShutdown() { } g.lock.Lock() g.shutdownCtxCancel() - atShutdownCtx := pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "post-shutdown")) + atShutdownCtx := pprof.WithLabels(g.hammerCtx, pprof.Labels(gtprof.LabelGracefulLifecycle, "post-shutdown")) pprof.SetGoroutineLabels(atShutdownCtx) for _, fn := range g.toRunAtShutdown { go fn() @@ -167,7 +168,7 @@ func (g *Manager) doHammerTime(d time.Duration) { default: log.Warn("Setting Hammer condition") g.hammerCtxCancel() - atHammerCtx := pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "post-hammer")) + atHammerCtx := pprof.WithLabels(g.terminateCtx, pprof.Labels(gtprof.LabelGracefulLifecycle, "post-hammer")) pprof.SetGoroutineLabels(atHammerCtx) } g.lock.Unlock() @@ -183,7 +184,7 @@ func (g *Manager) doTerminate() { default: log.Warn("Terminating") g.terminateCtxCancel() - atTerminateCtx := pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "post-terminate")) + atTerminateCtx := pprof.WithLabels(g.managerCtx, pprof.Labels(gtprof.LabelGracefulLifecycle, "post-terminate")) pprof.SetGoroutineLabels(atTerminateCtx) for _, fn := range g.toRunAtTerminate { @@ -218,13 +219,13 @@ func (g *Manager) ServerDone() { g.runningServerWaitGroup.Done() } -func (g *Manager) setStateTransition(old, new state) bool { +func (g *Manager) setStateTransition(oldState, newState state) bool { g.lock.Lock() - if g.state != old { + if g.state != oldState { g.lock.Unlock() return false } - g.state = new + g.state = newState g.lock.Unlock() return true } diff --git a/modules/graceful/manager_common.go b/modules/graceful/manager_common.go index f6dbcc748dc..7cfbdfbeb0e 100644 --- a/modules/graceful/manager_common.go +++ b/modules/graceful/manager_common.go @@ -8,6 +8,8 @@ import ( "runtime/pprof" "sync" "time" + + "code.gitea.io/gitea/modules/gtprof" ) // FIXME: it seems that there is a bug when using systemd Type=notify: the "Install Page" (INSTALL_LOCK=false) doesn't notify properly. @@ -65,10 +67,10 @@ func (g *Manager) prepare(ctx context.Context) { g.hammerCtx, g.hammerCtxCancel = context.WithCancel(ctx) g.managerCtx, g.managerCtxCancel = context.WithCancel(ctx) - g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate")) - g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown")) - g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer")) - g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager")) + g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels(gtprof.LabelGracefulLifecycle, "with-terminate")) + g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels(gtprof.LabelGracefulLifecycle, "with-shutdown")) + g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels(gtprof.LabelGracefulLifecycle, "with-hammer")) + g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels(gtprof.LabelGracefulLifecycle, "with-manager")) if !g.setStateTransition(stateInit, stateRunning) { panic("invalid graceful manager state: transition from init to running failed") diff --git a/modules/gtprof/gtprof.go b/modules/gtprof/gtprof.go new file mode 100644 index 00000000000..974b2c97574 --- /dev/null +++ b/modules/gtprof/gtprof.go @@ -0,0 +1,25 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gtprof + +// This is a Gitea-specific profiling package, +// the name is chosen to distinguish it from the standard pprof tool and "GNU gprof" + +// LabelGracefulLifecycle is a label marking manager lifecycle phase +// Making it compliant with prometheus key regex https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels +// would enable someone interested to be able to continuously gather profiles into pyroscope. +// Other labels for pprof should also follow this rule. +const LabelGracefulLifecycle = "graceful_lifecycle" + +// LabelPid is a label set on goroutines that have a process attached +const LabelPid = "pid" + +// LabelPpid is a label set on goroutines that have a process attached +const LabelPpid = "ppid" + +// LabelProcessType is a label set on goroutines that have a process attached +const LabelProcessType = "process_type" + +// LabelProcessDescription is a label set on goroutines that have a process attached +const LabelProcessDescription = "process_description" diff --git a/modules/indexer/internal/bleve/query.go b/modules/indexer/internal/bleve/query.go index 21422b281c4..1b18ca1a779 100644 --- a/modules/indexer/internal/bleve/query.go +++ b/modules/indexer/internal/bleve/query.go @@ -35,18 +35,18 @@ func BoolFieldQuery(value bool, field string) *query.BoolFieldQuery { return q } -func NumericRangeInclusiveQuery(min, max optional.Option[int64], field string) *query.NumericRangeQuery { +func NumericRangeInclusiveQuery(minOption, maxOption optional.Option[int64], field string) *query.NumericRangeQuery { var minF, maxF *float64 var minI, maxI *bool - if min.Has() { + if minOption.Has() { minF = new(float64) - *minF = float64(min.Value()) + *minF = float64(minOption.Value()) minI = new(bool) *minI = true } - if max.Has() { + if maxOption.Has() { maxF = new(float64) - *maxF = float64(max.Value()) + *maxF = float64(maxOption.Value()) maxI = new(bool) *maxI = true } diff --git a/modules/indexer/internal/paginator.go b/modules/indexer/internal/paginator.go index ee204bf0471..f1e19740eb7 100644 --- a/modules/indexer/internal/paginator.go +++ b/modules/indexer/internal/paginator.go @@ -10,12 +10,12 @@ import ( ) // ParsePaginator parses a db.Paginator into a skip and limit -func ParsePaginator(paginator *db.ListOptions, max ...int) (int, int) { +func ParsePaginator(paginator *db.ListOptions, maxNums ...int) (int, int) { // Use a very large number to indicate no limit unlimited := math.MaxInt32 - if len(max) > 0 { + if len(maxNums) > 0 { // Some indexer engines have a limit on the page size, respect that - unlimited = max[0] + unlimited = maxNums[0] } if paginator == nil || paginator.IsListAll() { diff --git a/modules/indexer/issues/internal/tests/tests.go b/modules/indexer/issues/internal/tests/tests.go index 16f0a78ec04..94ce8520bf6 100644 --- a/modules/indexer/issues/internal/tests/tests.go +++ b/modules/indexer/issues/internal/tests/tests.go @@ -113,7 +113,7 @@ var cases = []*testIndexerCase{ }, }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) assert.Equal(t, len(data), int(result.Total)) }, }, @@ -176,7 +176,7 @@ var cases = []*testIndexerCase{ IsPull: optional.Some(false), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.False(t, data[v.ID].IsPull) } @@ -192,7 +192,7 @@ var cases = []*testIndexerCase{ IsPull: optional.Some(true), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.True(t, data[v.ID].IsPull) } @@ -208,7 +208,7 @@ var cases = []*testIndexerCase{ IsClosed: optional.Some(false), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.False(t, data[v.ID].IsClosed) } @@ -224,7 +224,7 @@ var cases = []*testIndexerCase{ IsClosed: optional.Some(true), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.True(t, data[v.ID].IsClosed) } @@ -274,7 +274,7 @@ var cases = []*testIndexerCase{ MilestoneIDs: []int64{1, 2, 6}, }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.Contains(t, []int64{1, 2, 6}, data[v.ID].MilestoneID) } @@ -292,7 +292,7 @@ var cases = []*testIndexerCase{ MilestoneIDs: []int64{0}, }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.Equal(t, int64(0), data[v.ID].MilestoneID) } @@ -310,7 +310,7 @@ var cases = []*testIndexerCase{ ProjectID: optional.Some(int64(1)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.Equal(t, int64(1), data[v.ID].ProjectID) } @@ -328,7 +328,7 @@ var cases = []*testIndexerCase{ ProjectID: optional.Some(int64(0)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.Equal(t, int64(0), data[v.ID].ProjectID) } @@ -346,7 +346,7 @@ var cases = []*testIndexerCase{ ProjectColumnID: optional.Some(int64(1)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.Equal(t, int64(1), data[v.ID].ProjectColumnID) } @@ -364,7 +364,7 @@ var cases = []*testIndexerCase{ ProjectColumnID: optional.Some(int64(0)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.Equal(t, int64(0), data[v.ID].ProjectColumnID) } @@ -382,7 +382,7 @@ var cases = []*testIndexerCase{ PosterID: optional.Some(int64(1)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.Equal(t, int64(1), data[v.ID].PosterID) } @@ -400,7 +400,7 @@ var cases = []*testIndexerCase{ AssigneeID: optional.Some(int64(1)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.Equal(t, int64(1), data[v.ID].AssigneeID) } @@ -418,7 +418,7 @@ var cases = []*testIndexerCase{ AssigneeID: optional.Some(int64(0)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.Equal(t, int64(0), data[v.ID].AssigneeID) } @@ -436,7 +436,7 @@ var cases = []*testIndexerCase{ MentionID: optional.Some(int64(1)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.Contains(t, data[v.ID].MentionIDs, int64(1)) } @@ -454,7 +454,7 @@ var cases = []*testIndexerCase{ ReviewedID: optional.Some(int64(1)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.Contains(t, data[v.ID].ReviewedIDs, int64(1)) } @@ -472,7 +472,7 @@ var cases = []*testIndexerCase{ ReviewRequestedID: optional.Some(int64(1)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.Contains(t, data[v.ID].ReviewRequestedIDs, int64(1)) } @@ -490,7 +490,7 @@ var cases = []*testIndexerCase{ SubscriberID: optional.Some(int64(1)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.Contains(t, data[v.ID].SubscriberIDs, int64(1)) } @@ -509,7 +509,7 @@ var cases = []*testIndexerCase{ UpdatedBeforeUnix: optional.Some(int64(30)), }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, 5, len(result.Hits)) + assert.Len(t, result.Hits, 5) for _, v := range result.Hits { assert.GreaterOrEqual(t, data[v.ID].UpdatedUnix, int64(20)) assert.LessOrEqual(t, data[v.ID].UpdatedUnix, int64(30)) diff --git a/modules/lfs/http_client.go b/modules/lfs/http_client.go index 50f0e7a8d88..3acd23b8f73 100644 --- a/modules/lfs/http_client.go +++ b/modules/lfs/http_client.go @@ -72,7 +72,10 @@ func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Poin url := fmt.Sprintf("%s/objects/batch", c.endpoint) - request := &BatchRequest{operation, c.transferNames(), nil, objects} + // `ref` is an "optional object describing the server ref that the objects belong to" + // but some (incorrect) lfs servers require it, so maybe adding an empty ref here doesn't break the correct ones. + // https://github.com/git-lfs/git-lfs/blob/a32a02b44bf8a511aa14f047627c49e1a7fd5021/docs/api/batch.md?plain=1#L37 + request := &BatchRequest{operation, c.transferNames(), &Reference{}, objects} payload := new(bytes.Buffer) err := json.NewEncoder(payload).Encode(request) if err != nil { diff --git a/modules/lfs/shared.go b/modules/lfs/shared.go index 40ad789c1d9..cd9488e3dbf 100644 --- a/modules/lfs/shared.go +++ b/modules/lfs/shared.go @@ -14,9 +14,12 @@ import ( const ( // MediaType contains the media type for LFS server requests MediaType = "application/vnd.git-lfs+json" - // Some LFS servers offer content with other types, so fallback to '*/*' if application/vnd.git-lfs+json cannot be served - AcceptHeader = "application/vnd.git-lfs+json;q=0.9, */*;q=0.8" - UserAgentHeader = "git-lfs" + // AcceptHeader Some LFS servers offer content with other types, so fallback to '*/*' if application/vnd.git-lfs+json cannot be served + AcceptHeader = "application/vnd.git-lfs+json;q=0.9, */*;q=0.8" + // UserAgentHeader Add User-Agent for gitea's self-implemented lfs client, + // and the version is consistent with the latest version of git lfs can be avoided incompatibilities. + // Some lfs servers will check this + UserAgentHeader = "git-lfs/3.6.0 (Gitea)" ) // BatchRequest contains multiple requests processed in one batch operation. diff --git a/modules/lfs/transferadapter_test.go b/modules/lfs/transferadapter_test.go index 7fec137efe5..a430b71a5f1 100644 --- a/modules/lfs/transferadapter_test.go +++ b/modules/lfs/transferadapter_test.go @@ -96,7 +96,7 @@ func TestBasicTransferAdapter(t *testing.T) { for n, c := range cases { _, err := a.Download(context.Background(), c.link) if len(c.expectederror) > 0 { - assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) + assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) } else { assert.NoError(t, err, "case %d", n) } @@ -129,7 +129,7 @@ func TestBasicTransferAdapter(t *testing.T) { for n, c := range cases { err := a.Upload(context.Background(), c.link, p, bytes.NewBufferString("dummy")) if len(c.expectederror) > 0 { - assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) + assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) } else { assert.NoError(t, err, "case %d", n) } @@ -162,7 +162,7 @@ func TestBasicTransferAdapter(t *testing.T) { for n, c := range cases { err := a.Verify(context.Background(), c.link, p) if len(c.expectederror) > 0 { - assert.True(t, strings.Contains(err.Error(), c.expectederror), "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) + assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) } else { assert.NoError(t, err, "case %d", n) } diff --git a/modules/log/event_format.go b/modules/log/event_format.go index d9dbebf8315..8fda0a49807 100644 --- a/modules/log/event_format.go +++ b/modules/log/event_format.go @@ -13,10 +13,9 @@ import ( type Event struct { Time time.Time - GoroutinePid string - Caller string - Filename string - Line int + Caller string + Filename string + Line int Level Level @@ -110,10 +109,10 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms buf = append(buf, ' ') } if flags&(Ltime|Lmicroseconds) != 0 { - hour, min, sec := t.Clock() + hour, minNum, sec := t.Clock() buf = itoa(buf, hour, 2) buf = append(buf, ':') - buf = itoa(buf, min, 2) + buf = itoa(buf, minNum, 2) buf = append(buf, ':') buf = itoa(buf, sec, 2) if flags&Lmicroseconds != 0 { @@ -218,17 +217,16 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms } if flags&Lgopid == Lgopid { - if event.GoroutinePid != "" { - buf = append(buf, '[') - if mode.Colorize { - buf = append(buf, ColorBytes(FgHiYellow)...) - } - buf = append(buf, event.GoroutinePid...) - if mode.Colorize { - buf = append(buf, resetBytes...) - } - buf = append(buf, ']', ' ') + deprecatedGoroutinePid := "no-gopid" // use a dummy value to avoid breaking the log format + buf = append(buf, '[') + if mode.Colorize { + buf = append(buf, ColorBytes(FgHiYellow)...) } + buf = append(buf, deprecatedGoroutinePid...) + if mode.Colorize { + buf = append(buf, resetBytes...) + } + buf = append(buf, ']', ' ') } buf = append(buf, msg...) diff --git a/modules/log/event_format_test.go b/modules/log/event_format_test.go index 7c299a607d8..6fd0f36d485 100644 --- a/modules/log/event_format_test.go +++ b/modules/log/event_format_test.go @@ -24,34 +24,32 @@ func TestItoa(t *testing.T) { func TestEventFormatTextMessage(t *testing.T) { res := EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: false, Flags: Flags{defined: true, flags: 0xffffffff}}, &Event{ - Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), - Caller: "caller", - Filename: "filename", - Line: 123, - GoroutinePid: "pid", - Level: ERROR, - Stacktrace: "stacktrace", + Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), + Caller: "caller", + Filename: "filename", + Line: 123, + Level: ERROR, + Stacktrace: "stacktrace", }, "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue), ) - assert.Equal(t, `[PREFIX] 2020/01/02 03:04:05.000000 filename:123:caller [E] [pid] msg format: arg0 arg1 + assert.Equal(t, `[PREFIX] 2020/01/02 03:04:05.000000 filename:123:caller [E] [no-gopid] msg format: arg0 arg1 stacktrace `, string(res)) res = EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: true, Flags: Flags{defined: true, flags: 0xffffffff}}, &Event{ - Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), - Caller: "caller", - Filename: "filename", - Line: 123, - GoroutinePid: "pid", - Level: ERROR, - Stacktrace: "stacktrace", + Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), + Caller: "caller", + Filename: "filename", + Line: 123, + Level: ERROR, + Stacktrace: "stacktrace", }, "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue), ) - assert.Equal(t, "[PREFIX] \x1b[36m2020/01/02 03:04:05.000000 \x1b[0m\x1b[32mfilename:123:\x1b[32mcaller\x1b[0m \x1b[1;31m[E]\x1b[0m [\x1b[93mpid\x1b[0m] msg format: arg0 \x1b[34marg1\x1b[0m\n\tstacktrace\n\n", string(res)) + assert.Equal(t, "[PREFIX] \x1b[36m2020/01/02 03:04:05.000000 \x1b[0m\x1b[32mfilename:123:\x1b[32mcaller\x1b[0m \x1b[1;31m[E]\x1b[0m [\x1b[93mno-gopid\x1b[0m] msg format: arg0 \x1b[34marg1\x1b[0m\n\tstacktrace\n\n", string(res)) } diff --git a/modules/log/flags.go b/modules/log/flags.go index f025159d539..8064c917450 100644 --- a/modules/log/flags.go +++ b/modules/log/flags.go @@ -30,7 +30,7 @@ const ( LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone Llevelinitial // Initial character of the provided level in brackets, eg. [I] for info Llevel // Provided level in brackets [INFO] - Lgopid // the Goroutine-PID of the context + Lgopid // the Goroutine-PID of the context, deprecated and it is always a const value Lmedfile = Lshortfile | Llongfile // last 20 characters of the filename LstdFlags = Ldate | Ltime | Lmedfile | Lshortfuncname | Llevelinitial // default diff --git a/modules/log/groutinelabel.go b/modules/log/groutinelabel.go deleted file mode 100644 index 56d7af42da3..00000000000 --- a/modules/log/groutinelabel.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package log - -import "unsafe" - -//go:linkname runtime_getProfLabel runtime/pprof.runtime_getProfLabel -func runtime_getProfLabel() unsafe.Pointer //nolint - -type labelMap map[string]string - -func getGoroutineLabels() map[string]string { - l := (*labelMap)(runtime_getProfLabel()) - if l == nil { - return nil - } - return *l -} diff --git a/modules/log/groutinelabel_test.go b/modules/log/groutinelabel_test.go deleted file mode 100644 index 34e99653d6a..00000000000 --- a/modules/log/groutinelabel_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package log - -import ( - "context" - "runtime/pprof" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_getGoroutineLabels(t *testing.T) { - pprof.Do(context.Background(), pprof.Labels(), func(ctx context.Context) { - currentLabels := getGoroutineLabels() - pprof.ForLabels(ctx, func(key, value string) bool { - assert.EqualValues(t, value, currentLabels[key]) - return true - }) - - pprof.Do(ctx, pprof.Labels("Test_getGoroutineLabels", "Test_getGoroutineLabels_child1"), func(ctx context.Context) { - currentLabels := getGoroutineLabels() - pprof.ForLabels(ctx, func(key, value string) bool { - assert.EqualValues(t, value, currentLabels[key]) - return true - }) - if assert.NotNil(t, currentLabels) { - assert.EqualValues(t, "Test_getGoroutineLabels_child1", currentLabels["Test_getGoroutineLabels"]) - } - }) - }) -} diff --git a/modules/log/logger_impl.go b/modules/log/logger_impl.go index d38c6516ed5..76dd5f43fbc 100644 --- a/modules/log/logger_impl.go +++ b/modules/log/logger_impl.go @@ -200,11 +200,6 @@ func (l *LoggerImpl) Log(skip int, level Level, format string, logArgs ...any) { event.Stacktrace = Stack(skip + 1) } - labels := getGoroutineLabels() - if labels != nil { - event.GoroutinePid = labels["pid"] - } - // get a simple text message without color msgArgs := make([]any, len(logArgs)) copy(msgArgs, logArgs) diff --git a/modules/log/logger_test.go b/modules/log/logger_test.go index 70222f64f5c..0de14eb411e 100644 --- a/modules/log/logger_test.go +++ b/modules/log/logger_test.go @@ -56,7 +56,7 @@ func TestLogger(t *testing.T) { logger := NewLoggerWithWriters(context.Background(), "test") dump := logger.DumpWriters() - assert.EqualValues(t, 0, len(dump)) + assert.Empty(t, dump) assert.EqualValues(t, NONE, logger.GetLevel()) assert.False(t, logger.IsEnabled()) @@ -69,7 +69,7 @@ func TestLogger(t *testing.T) { assert.EqualValues(t, DEBUG, logger.GetLevel()) dump = logger.DumpWriters() - assert.EqualValues(t, 2, len(dump)) + assert.Len(t, dump, 2) logger.Trace("trace-level") // this level is not logged logger.Debug("debug-level") diff --git a/modules/markup/common/linkify.go b/modules/markup/common/linkify.go index be6ab22b55f..52888958faf 100644 --- a/modules/markup/common/linkify.go +++ b/modules/markup/common/linkify.go @@ -24,7 +24,7 @@ type GlobalVarsType struct { LinkRegex *regexp.Regexp // fast matching a URL link, no any extra validation. } -var GlobalVars = sync.OnceValue[*GlobalVarsType](func() *GlobalVarsType { +var GlobalVars = sync.OnceValue(func() *GlobalVarsType { v := &GlobalVarsType{} v.wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`) v.LinkRegex, _ = xurls.StrictMatchingScheme("https?://") diff --git a/modules/markup/html.go b/modules/markup/html.go index 25628db39e4..bb12febf277 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -42,7 +42,7 @@ type globalVarsType struct { nulCleaner *strings.Replacer } -var globalVars = sync.OnceValue[*globalVarsType](func() *globalVarsType { +var globalVars = sync.OnceValue(func() *globalVarsType { v := &globalVarsType{} // NOTE: All below regex matching do not perform any extra validation. // Thus a link is produced even if the linked entity does not exist. diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 9419350e615..159d7129557 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -278,12 +278,12 @@ func TestRender_AutoLink(t *testing.T) { test := func(input, expected string) { var buffer strings.Builder err := PostProcessDefault(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer) - assert.Equal(t, err, nil) + assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) buffer.Reset() err = PostProcessDefault(NewTestRenderContext(localMetas), strings.NewReader(input), &buffer) - assert.Equal(t, err, nil) + assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) } diff --git a/modules/markup/internal/renderinternal.go b/modules/markup/internal/renderinternal.go index 4775fecfc70..4d58f160a92 100644 --- a/modules/markup/internal/renderinternal.go +++ b/modules/markup/internal/renderinternal.go @@ -17,7 +17,7 @@ import ( "golang.org/x/net/html" ) -var reAttrClass = sync.OnceValue[*regexp.Regexp](func() *regexp.Regexp { +var reAttrClass = sync.OnceValue(func() *regexp.Regexp { // TODO: it isn't a problem at the moment because our HTML contents are always well constructed return regexp.MustCompile(`(<[^>]+)\s+class="([^"]+)"([^>]*>)`) }) diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index 620a39ebfde..69c2a96ff1f 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -112,7 +112,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa } // it is copied from old code, which is quite doubtful whether it is correct -var reValidIconName = sync.OnceValue[*regexp.Regexp](func() *regexp.Regexp { +var reValidIconName = sync.OnceValue(func() *regexp.Regexp { return regexp.MustCompile(`^[-\w]+$`) // old: regexp.MustCompile("^[a-z ]+$") }) diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index f77db9eb38e..b5fffccdb98 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -78,26 +78,23 @@ func (r *GlodmarkRender) Renderer() renderer.Renderer { func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.CodeBlockContext, entering bool) { if entering { - language, _ := c.Language() - if language == nil { - language = []byte("text") - } + languageBytes, _ := c.Language() + languageStr := giteautil.IfZero(string(languageBytes), "text") - languageStr := string(language) - - preClasses := []string{"code-block"} + preClasses := "code-block" if languageStr == "mermaid" || languageStr == "math" { - preClasses = append(preClasses, "is-loading") + preClasses += " is-loading" } - err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `
`, strings.Join(preClasses, " "))
+		err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `
`, preClasses)
 		if err != nil {
 			return
 		}
 
-		// include language-x class as part of commonmark spec
-		// the "display" class is used by "js/markup/math.js" to render the code element as a block
-		err = r.ctx.RenderInternal.FormatWithSafeAttrs(w, ``, string(language))
+		// include language-x class as part of commonmark spec, "chroma" class is used to highlight the code
+		// the "display" class is used by "js/markup/math.ts" to render the code element as a block
+		// the "math.ts" strictly depends on the structure: 
...
+ err = r.ctx.RenderInternal.FormatWithSafeAttrs(w, ``, languageStr) if err != nil { return } @@ -128,7 +125,13 @@ func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender { ), highlighting.WithWrapperRenderer(r.highlightingRenderer), ), - math.NewExtension(&ctx.RenderInternal, math.Enabled(setting.Markdown.EnableMath)), + math.NewExtension(&ctx.RenderInternal, math.Options{ + Enabled: setting.Markdown.EnableMath, + ParseDollarInline: true, + ParseDollarBlock: true, + ParseSquareBlock: true, // TODO: this is a bad syntax "\[ ... \]", it conflicts with normal markdown escaping, it should be deprecated in the future (by some config options) + // ParseBracketInline: true, // TODO: this is also a bad syntax "\( ... \)", it also conflicts, it should be deprecated in the future + }), meta.Meta, ), goldmark.WithParserOptions( diff --git a/modules/markup/markdown/markdown_math_test.go b/modules/markup/markdown/markdown_math_test.go index e371b1c74ab..813f050965a 100644 --- a/modules/markup/markdown/markdown_math_test.go +++ b/modules/markup/markdown/markdown_math_test.go @@ -12,31 +12,32 @@ import ( "github.com/stretchr/testify/assert" ) +const nl = "\n" + func TestMathRender(t *testing.T) { - const nl = "\n" testcases := []struct { testcase string expected string }{ { "$a$", - `

a

` + nl, + `

a

` + nl, }, { "$ a $", - `

a

` + nl, + `

a

` + nl, }, { "$a$ $b$", - `

a b

` + nl, + `

a b

` + nl, }, { `\(a\) \(b\)`, - `

a b

` + nl, + `

a b

` + nl, }, { `$a$.`, - `

a.

` + nl, + `

a.

` + nl, }, { `.$a$`, @@ -64,27 +65,39 @@ func TestMathRender(t *testing.T) { }, { "$a$ ($b$) [$c$] {$d$}", - `

a (b) [$c$] {$d$}

` + nl, + `

a (b) [$c$] {$d$}

` + nl, }, { "$$a$$", - `a` + nl, + `a` + nl, }, { "$$a$$ test", - `

a test

` + nl, + `

a test

` + nl, }, { "test $$a$$", - `

test a

` + nl, + `

test a

` + nl, }, { `foo $x=\$$ bar`, - `

foo x=\$ bar

` + nl, + `

foo x=\$ bar

` + nl, }, { `$\text{$b$}$`, - `

\text{$b$}

` + nl, + `

\text{$b$}

` + nl, + }, + { + "a$`b`$c", + `

abc

` + nl, + }, + { + "a $`b`$ c", + `

a b c

` + nl, + }, + { + "a$``b``$c x$```y```$z", + `

abc xyz

` + nl, }, } @@ -110,7 +123,7 @@ func TestMathRenderBlockIndent(t *testing.T) { \alpha \] `, - `

+			`

 \alpha
 
`, @@ -122,7 +135,7 @@ func TestMathRenderBlockIndent(t *testing.T) { \alpha \] `, - `

+			`

 \alpha
 
`, @@ -137,7 +150,7 @@ a d \] `, - `

+			`

 a
 b
 c
@@ -154,7 +167,7 @@ c
   c
   \]
 `,
-			`

+			`

 a
  b
 c
@@ -165,7 +178,7 @@ c
 			"indent-0-oneline",
 			`$$ x $$
 foo`,
-			` x 
+			` x 
 

foo

`, }, @@ -173,7 +186,7 @@ foo`, "indent-3-oneline", ` $$ x $$ foo`, - ` x + ` x

foo

`, }, @@ -188,10 +201,10 @@ foo`, > \] `, `
-

+

 a
 
-

+

 b
 
@@ -207,7 +220,7 @@ b 2. b`, `
  1. a -
    
    +
    
     x
     
  2. @@ -215,6 +228,11 @@ x
`, }, + { + "inline-non-math", + `\[x]`, + `

[x]

` + nl, + }, } for _, test := range testcases { diff --git a/modules/markup/markdown/math/block_parser.go b/modules/markup/markdown/math/block_parser.go index 3f37ce83332..2c5553550a7 100644 --- a/modules/markup/markdown/math/block_parser.go +++ b/modules/markup/markdown/math/block_parser.go @@ -16,16 +16,18 @@ import ( type blockParser struct { parseDollars bool + parseSquare bool endBytesDollars []byte - endBytesBracket []byte + endBytesSquare []byte } // NewBlockParser creates a new math BlockParser -func NewBlockParser(parseDollarBlocks bool) parser.BlockParser { +func NewBlockParser(parseDollars, parseSquare bool) parser.BlockParser { return &blockParser{ - parseDollars: parseDollarBlocks, + parseDollars: parseDollars, + parseSquare: parseSquare, endBytesDollars: []byte{'$', '$'}, - endBytesBracket: []byte{'\\', ']'}, + endBytesSquare: []byte{'\\', ']'}, } } @@ -40,7 +42,7 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex var dollars bool if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' { dollars = true - } else if line[pos] == '\\' && line[pos+1] == '[' { + } else if b.parseSquare && line[pos] == '\\' && line[pos+1] == '[' { if len(line[pos:]) >= 3 && line[pos+2] == '!' && bytes.Contains(line[pos:], []byte(`\]`)) { // do not process escaped attention block: "> \[!NOTE\]" return nil, parser.NoChildren @@ -53,10 +55,10 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex node := NewBlock(dollars, pos) // Now we need to check if the ending block is on the segment... - endBytes := giteaUtil.Iif(dollars, b.endBytesDollars, b.endBytesBracket) + endBytes := giteaUtil.Iif(dollars, b.endBytesDollars, b.endBytesSquare) idx := bytes.Index(line[pos+2:], endBytes) if idx >= 0 { - // for case $$ ... $$ any other text + // for case: "$$ ... $$ any other text" (this case will be handled by the inline parser) for i := pos + 2 + idx + 2; i < len(line); i++ { if line[i] != ' ' && line[i] != '\n' { return nil, parser.NoChildren @@ -70,6 +72,13 @@ func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Contex return node, parser.Close | parser.NoChildren } + // for case "\[ ... ]" (no close marker on the same line) + for i := pos + 2 + idx + 2; i < len(line); i++ { + if line[i] != ' ' && line[i] != '\n' { + return nil, parser.NoChildren + } + } + segment.Start += pos + 2 node.Lines().Append(segment) return node, parser.NoChildren @@ -85,7 +94,7 @@ func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Cont line, segment := reader.PeekLine() w, pos := util.IndentWidth(line, reader.LineOffset()) if w < 4 { - endBytes := giteaUtil.Iif(block.Dollars, b.endBytesDollars, b.endBytesBracket) + endBytes := giteaUtil.Iif(block.Dollars, b.endBytesDollars, b.endBytesSquare) if bytes.HasPrefix(line[pos:], endBytes) && util.IsBlank(line[pos+len(endBytes):]) { if util.IsBlank(line[pos+len(endBytes):]) { newline := giteaUtil.Iif(line[len(line)-1] != '\n', 0, 1) diff --git a/modules/markup/markdown/math/block_renderer.go b/modules/markup/markdown/math/block_renderer.go index a770efa01c7..c29f0618821 100644 --- a/modules/markup/markdown/math/block_renderer.go +++ b/modules/markup/markdown/math/block_renderer.go @@ -12,6 +12,17 @@ import ( "github.com/yuin/goldmark/util" ) +// Block render output: +//
...
+// +// Keep in mind that there is another "code block" render in "func (r *GlodmarkRender) highlightingRenderer" +// "highlightingRenderer" outputs the math block with extra "chroma" class: +//
...
+// +// Special classes: +// * "is-loading": show a loading indicator +// * "display": used by JS to decide to render as a block, otherwise render as inline + // BlockRenderer represents a renderer for math Blocks type BlockRenderer struct { renderInternal *internal.RenderInternal @@ -38,7 +49,7 @@ func (r *BlockRenderer) writeLines(w util.BufWriter, source []byte, n gast.Node) func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) { n := node.(*Block) if entering { - code := giteaUtil.Iif(n.Inline, "", `
`) + ``
+		code := giteaUtil.Iif(n.Inline, "", `
`) + ``
 		_ = r.renderInternal.FormatWithSafeAttrs(w, code)
 		r.writeLines(w, source, n)
 	} else {
diff --git a/modules/markup/markdown/math/inline_block_node.go b/modules/markup/markdown/math/inline_block_node.go
deleted file mode 100644
index c92d0c8d84b..00000000000
--- a/modules/markup/markdown/math/inline_block_node.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2024 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package math
-
-import (
-	"github.com/yuin/goldmark/ast"
-)
-
-// InlineBlock represents inline math e.g. $$...$$
-type InlineBlock struct {
-	Inline
-}
-
-// InlineBlock implements InlineBlock.
-func (n *InlineBlock) InlineBlock() {}
-
-// KindInlineBlock is the kind for math inline block
-var KindInlineBlock = ast.NewNodeKind("MathInlineBlock")
-
-// Kind returns KindInlineBlock
-func (n *InlineBlock) Kind() ast.NodeKind {
-	return KindInlineBlock
-}
-
-// NewInlineBlock creates a new ast math inline block node
-func NewInlineBlock() *InlineBlock {
-	return &InlineBlock{
-		Inline{},
-	}
-}
diff --git a/modules/markup/markdown/math/inline_node.go b/modules/markup/markdown/math/inline_node.go
index 2221a251bf1..1e4034d54b9 100644
--- a/modules/markup/markdown/math/inline_node.go
+++ b/modules/markup/markdown/math/inline_node.go
@@ -8,7 +8,7 @@ import (
 	"github.com/yuin/goldmark/util"
 )
 
-// Inline represents inline math e.g. $...$ or \(...\)
+// Inline struct represents inline math e.g. $...$ or \(...\)
 type Inline struct {
 	ast.BaseInline
 }
diff --git a/modules/markup/markdown/math/inline_parser.go b/modules/markup/markdown/math/inline_parser.go
index 191d1e5a315..a57abe9f9b0 100644
--- a/modules/markup/markdown/math/inline_parser.go
+++ b/modules/markup/markdown/math/inline_parser.go
@@ -12,31 +12,25 @@ import (
 )
 
 type inlineParser struct {
-	start []byte
-	end   []byte
+	trigger              []byte
+	endBytesSingleDollar []byte
+	endBytesDoubleDollar []byte
+	endBytesBracket      []byte
 }
 
 var defaultInlineDollarParser = &inlineParser{
-	start: []byte{'$'},
-	end:   []byte{'$'},
-}
-
-var defaultDualDollarParser = &inlineParser{
-	start: []byte{'$', '$'},
-	end:   []byte{'$', '$'},
+	trigger:              []byte{'$'},
+	endBytesSingleDollar: []byte{'$'},
+	endBytesDoubleDollar: []byte{'$', '$'},
 }
 
 func NewInlineDollarParser() parser.InlineParser {
 	return defaultInlineDollarParser
 }
 
-func NewInlineDualDollarParser() parser.InlineParser {
-	return defaultDualDollarParser
-}
-
 var defaultInlineBracketParser = &inlineParser{
-	start: []byte{'\\', '('},
-	end:   []byte{'\\', ')'},
+	trigger:         []byte{'\\', '('},
+	endBytesBracket: []byte{'\\', ')'},
 }
 
 func NewInlineBracketParser() parser.InlineParser {
@@ -45,7 +39,7 @@ func NewInlineBracketParser() parser.InlineParser {
 
 // Trigger triggers this parser on $ or \
 func (parser *inlineParser) Trigger() []byte {
-	return parser.start
+	return parser.trigger
 }
 
 func isPunctuation(b byte) bool {
@@ -64,33 +58,60 @@ func isAlphanumeric(b byte) bool {
 func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
 	line, _ := block.PeekLine()
 
-	if !bytes.HasPrefix(line, parser.start) {
+	if !bytes.HasPrefix(line, parser.trigger) {
 		// We'll catch this one on the next time round
 		return nil
 	}
 
-	precedingCharacter := block.PrecendingCharacter()
-	if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
-		// need to exclude things like `a$` from being considered a start
-		return nil
+	var startMarkLen int
+	var stopMark []byte
+	checkSurrounding := true
+	if line[0] == '$' {
+		startMarkLen = 1
+		stopMark = parser.endBytesSingleDollar
+		if len(line) > 1 {
+			if line[1] == '$' {
+				startMarkLen = 2
+				stopMark = parser.endBytesDoubleDollar
+			} else if line[1] == '`' {
+				pos := 1
+				for ; pos < len(line) && line[pos] == '`'; pos++ {
+				}
+				startMarkLen = pos
+				stopMark = bytes.Repeat([]byte{'`'}, pos)
+				stopMark[len(stopMark)-1] = '$'
+				checkSurrounding = false
+			}
+		}
+	} else {
+		startMarkLen = 2
+		stopMark = parser.endBytesBracket
+	}
+
+	if checkSurrounding {
+		precedingCharacter := block.PrecendingCharacter()
+		if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
+			// need to exclude things like `a$` from being considered a start
+			return nil
+		}
 	}
 
 	// move the opener marker point at the start of the text
-	opener := len(parser.start)
+	opener := startMarkLen
 
 	// Now look for an ending line
 	depth := 0
 	ender := -1
 	for i := opener; i < len(line); i++ {
-		if depth == 0 && bytes.HasPrefix(line[i:], parser.end) {
+		if depth == 0 && bytes.HasPrefix(line[i:], stopMark) {
 			succeedingCharacter := byte(0)
-			if i+len(parser.end) < len(line) {
-				succeedingCharacter = line[i+len(parser.end)]
+			if i+len(stopMark) < len(line) {
+				succeedingCharacter = line[i+len(stopMark)]
 			}
 			// check valid ending character
 			isValidEndingChar := isPunctuation(succeedingCharacter) || isBracket(succeedingCharacter) ||
 				succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0
-			if !isValidEndingChar {
+			if checkSurrounding && !isValidEndingChar {
 				break
 			}
 			ender = i
@@ -112,21 +133,12 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
 
 	block.Advance(opener)
 	_, pos := block.Position()
-	var node ast.Node
-	if parser == defaultDualDollarParser {
-		node = NewInlineBlock()
-	} else {
-		node = NewInline()
-	}
+	node := NewInline()
+
 	segment := pos.WithStop(pos.Start + ender - opener)
 	node.AppendChild(node, ast.NewRawTextSegment(segment))
-	block.Advance(ender - opener + len(parser.end))
-
-	if parser == defaultDualDollarParser {
-		trimBlock(&(node.(*InlineBlock)).Inline, block)
-	} else {
-		trimBlock(node.(*Inline), block)
-	}
+	block.Advance(ender - opener + len(stopMark))
+	trimBlock(node, block)
 	return node
 }
 
diff --git a/modules/markup/markdown/math/inline_renderer.go b/modules/markup/markdown/math/inline_renderer.go
index 0cff4f1e74e..d000a7b317a 100644
--- a/modules/markup/markdown/math/inline_renderer.go
+++ b/modules/markup/markdown/math/inline_renderer.go
@@ -13,6 +13,9 @@ import (
 	"github.com/yuin/goldmark/util"
 )
 
+// Inline render output:
+// ...
+
 // InlineRenderer is an inline renderer
 type InlineRenderer struct {
 	renderInternal *internal.RenderInternal
@@ -25,11 +28,7 @@ func NewInlineRenderer(renderInternal *internal.RenderInternal) renderer.NodeRen
 
 func (r *InlineRenderer) renderInline(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
 	if entering {
-		extraClass := ""
-		if _, ok := n.(*InlineBlock); ok {
-			extraClass = "display "
-		}
-		_ = r.renderInternal.FormatWithSafeAttrs(w, ``, extraClass)
+		_ = r.renderInternal.FormatWithSafeAttrs(w, ``)
 		for c := n.FirstChild(); c != nil; c = c.NextSibling() {
 			segment := c.(*ast.Text).Segment
 			value := util.EscapeHTML(segment.Value(source))
@@ -51,5 +50,4 @@ func (r *InlineRenderer) renderInline(w util.BufWriter, source []byte, n ast.Nod
 // RegisterFuncs registers the renderer for inline math nodes
 func (r *InlineRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
 	reg.Register(KindInline, r.renderInline)
-	reg.Register(KindInlineBlock, r.renderInline)
 }
diff --git a/modules/markup/markdown/math/math.go b/modules/markup/markdown/math/math.go
index 7e8defcd4a1..a6ff593d626 100644
--- a/modules/markup/markdown/math/math.go
+++ b/modules/markup/markdown/math/math.go
@@ -5,6 +5,7 @@ package math
 
 import (
 	"code.gitea.io/gitea/modules/markup/internal"
+	giteaUtil "code.gitea.io/gitea/modules/util"
 
 	"github.com/yuin/goldmark"
 	"github.com/yuin/goldmark/parser"
@@ -12,70 +13,45 @@ import (
 	"github.com/yuin/goldmark/util"
 )
 
+type Options struct {
+	Enabled           bool
+	ParseDollarInline bool
+	ParseDollarBlock  bool
+	ParseSquareBlock  bool
+}
+
 // Extension is a math extension
 type Extension struct {
-	renderInternal    *internal.RenderInternal
-	enabled           bool
-	parseDollarInline bool
-	parseDollarBlock  bool
-}
-
-// Option is the interface Options should implement
-type Option interface {
-	SetOption(e *Extension)
-}
-
-type extensionFunc func(e *Extension)
-
-func (fn extensionFunc) SetOption(e *Extension) {
-	fn(e)
-}
-
-// Enabled enables or disables this extension
-func Enabled(enable ...bool) Option {
-	value := true
-	if len(enable) > 0 {
-		value = enable[0]
-	}
-	return extensionFunc(func(e *Extension) {
-		e.enabled = value
-	})
+	renderInternal *internal.RenderInternal
+	options        Options
 }
 
 // NewExtension creates a new math extension with the provided options
-func NewExtension(renderInternal *internal.RenderInternal, opts ...Option) *Extension {
+func NewExtension(renderInternal *internal.RenderInternal, opts ...Options) *Extension {
+	opt := giteaUtil.OptionalArg(opts)
 	r := &Extension{
-		renderInternal:    renderInternal,
-		enabled:           true,
-		parseDollarBlock:  true,
-		parseDollarInline: true,
-	}
-
-	for _, o := range opts {
-		o.SetOption(r)
+		renderInternal: renderInternal,
+		options:        opt,
 	}
 	return r
 }
 
 // Extend extends goldmark with our parsers and renderers
 func (e *Extension) Extend(m goldmark.Markdown) {
-	if !e.enabled {
+	if !e.options.Enabled {
 		return
 	}
 
-	m.Parser().AddOptions(parser.WithBlockParsers(
-		util.Prioritized(NewBlockParser(e.parseDollarBlock), 701),
-	))
-
-	inlines := []util.PrioritizedValue{
-		util.Prioritized(NewInlineBracketParser(), 501),
-	}
-	if e.parseDollarInline {
-		inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 503),
-			util.Prioritized(NewInlineDualDollarParser(), 502))
+	inlines := []util.PrioritizedValue{util.Prioritized(NewInlineBracketParser(), 501)}
+	if e.options.ParseDollarInline {
+		inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 502))
 	}
 	m.Parser().AddOptions(parser.WithInlineParsers(inlines...))
 
+	m.Parser().AddOptions(parser.WithBlockParsers(
+		util.Prioritized(NewBlockParser(e.options.ParseDollarBlock, e.options.ParseSquareBlock), 701),
+	))
+
 	m.Renderer().AddOptions(renderer.WithNodeRenderers(
 		util.Prioritized(NewBlockRenderer(e.renderInternal), 501),
 		util.Prioritized(NewInlineRenderer(e.renderInternal), 502),
diff --git a/modules/packages/arch/metadata.go b/modules/packages/arch/metadata.go
index e1e79c60e0d..3d7bdf3997f 100644
--- a/modules/packages/arch/metadata.go
+++ b/modules/packages/arch/metadata.go
@@ -43,8 +43,9 @@ var (
 	ErrInvalidArchitecture = util.NewInvalidArgumentErrorf("package architecture is invalid")
 
 	// https://man.archlinux.org/man/PKGBUILD.5
-	namePattern    = regexp.MustCompile(`\A[a-zA-Z0-9@._+-]+\z`)
-	versionPattern = regexp.MustCompile(`\A(?:[0-9]:)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`)
+	namePattern = regexp.MustCompile(`\A[a-zA-Z0-9@._+-]+\z`)
+	// (epoch:pkgver-pkgrel)
+	versionPattern = regexp.MustCompile(`\A(?:\d:)?[\w.+~]+(?:-[-\w.+~]+)?\z`)
 )
 
 type Package struct {
@@ -69,10 +70,12 @@ type FileMetadata struct {
 	Packager      string   `json:"packager,omitempty"`
 	Groups        []string `json:"groups,omitempty"`
 	Provides      []string `json:"provides,omitempty"`
+	Replaces      []string `json:"replaces,omitempty"`
 	Depends       []string `json:"depends,omitempty"`
 	OptDepends    []string `json:"opt_depends,omitempty"`
 	MakeDepends   []string `json:"make_depends,omitempty"`
 	CheckDepends  []string `json:"check_depends,omitempty"`
+	Conflicts     []string `json:"conflicts,omitempty"`
 	XData         []string `json:"xdata,omitempty"`
 	Backup        []string `json:"backup,omitempty"`
 	Files         []string `json:"files,omitempty"`
@@ -201,12 +204,16 @@ func ParsePackageInfo(r io.Reader) (*Package, error) {
 			p.FileMetadata.Provides = append(p.FileMetadata.Provides, value)
 		case "depend":
 			p.FileMetadata.Depends = append(p.FileMetadata.Depends, value)
+		case "replaces":
+			p.FileMetadata.Replaces = append(p.FileMetadata.Replaces, value)
 		case "optdepend":
 			p.FileMetadata.OptDepends = append(p.FileMetadata.OptDepends, value)
 		case "makedepend":
 			p.FileMetadata.MakeDepends = append(p.FileMetadata.MakeDepends, value)
 		case "checkdepend":
 			p.FileMetadata.CheckDepends = append(p.FileMetadata.CheckDepends, value)
+		case "conflict":
+			p.FileMetadata.Conflicts = append(p.FileMetadata.Conflicts, value)
 		case "backup":
 			p.FileMetadata.Backup = append(p.FileMetadata.Backup, value)
 		case "group":
diff --git a/modules/packages/arch/metadata_test.go b/modules/packages/arch/metadata_test.go
index f611ef5e845..5bdf63a3ee3 100644
--- a/modules/packages/arch/metadata_test.go
+++ b/modules/packages/arch/metadata_test.go
@@ -42,8 +42,10 @@ depend = gitea
 provides = common
 provides = gitea
 optdepend = hex
+replaces = gogs
 checkdepend = common
 makedepend = cmake
+conflict = ninja
 backup = usr/bin/paket1`)
 }
 
@@ -120,6 +122,14 @@ func TestParsePackageInfo(t *testing.T) {
 		assert.ErrorIs(t, err, ErrInvalidName)
 	})
 
+	t.Run("Regexp", func(t *testing.T) {
+		assert.Regexp(t, versionPattern, "1.2_3~4+5")
+		assert.Regexp(t, versionPattern, "1:2_3~4+5")
+		assert.NotRegexp(t, versionPattern, "a:1.0.0-1")
+		assert.NotRegexp(t, versionPattern, "0.0.1/1-1")
+		assert.NotRegexp(t, versionPattern, "1.0.0 -1")
+	})
+
 	t.Run("InvalidVersion", func(t *testing.T) {
 		data := createPKGINFOContent(packageName, "")
 
@@ -149,8 +159,10 @@ func TestParsePackageInfo(t *testing.T) {
 		assert.ElementsMatch(t, []string{"group"}, p.FileMetadata.Groups)
 		assert.ElementsMatch(t, []string{"common", "gitea"}, p.FileMetadata.Provides)
 		assert.ElementsMatch(t, []string{"common", "gitea"}, p.FileMetadata.Depends)
+		assert.ElementsMatch(t, []string{"gogs"}, p.FileMetadata.Replaces)
 		assert.ElementsMatch(t, []string{"hex"}, p.FileMetadata.OptDepends)
 		assert.ElementsMatch(t, []string{"common"}, p.FileMetadata.CheckDepends)
+		assert.ElementsMatch(t, []string{"ninja"}, p.FileMetadata.Conflicts)
 		assert.ElementsMatch(t, []string{"cmake"}, p.FileMetadata.MakeDepends)
 		assert.ElementsMatch(t, []string{"usr/bin/paket1"}, p.FileMetadata.Backup)
 	})
diff --git a/modules/packages/conan/conanfile_parser_test.go b/modules/packages/conan/conanfile_parser_test.go
index 58015701841..aabafd5f64f 100644
--- a/modules/packages/conan/conanfile_parser_test.go
+++ b/modules/packages/conan/conanfile_parser_test.go
@@ -40,7 +40,7 @@ class ConanPackageConan(ConanFile):
 
 func TestParseConanfile(t *testing.T) {
 	metadata, err := ParseConanfile(strings.NewReader(contentConanfile))
-	assert.Nil(t, err)
+	assert.NoError(t, err)
 	assert.Equal(t, license, metadata.License)
 	assert.Equal(t, author, metadata.Author)
 	assert.Equal(t, homepage, metadata.ProjectURL)
diff --git a/modules/packages/conan/conaninfo_parser_test.go b/modules/packages/conan/conaninfo_parser_test.go
index 556a4b939ee..f6510ca6670 100644
--- a/modules/packages/conan/conaninfo_parser_test.go
+++ b/modules/packages/conan/conaninfo_parser_test.go
@@ -50,7 +50,7 @@ const (
 func TestParseConaninfo(t *testing.T) {
 	info, err := ParseConaninfo(strings.NewReader(contentConaninfo))
 	assert.NotNil(t, info)
-	assert.Nil(t, err)
+	assert.NoError(t, err)
 	assert.Equal(
 		t,
 		map[string]string{
diff --git a/modules/process/context.go b/modules/process/context.go
index 26a80ebd620..1854988bce2 100644
--- a/modules/process/context.go
+++ b/modules/process/context.go
@@ -32,7 +32,7 @@ func (c *Context) Value(key any) any {
 }
 
 // ProcessContextKey is the key under which process contexts are stored
-var ProcessContextKey any = "process-context"
+var ProcessContextKey any = "process_context"
 
 // GetContext will return a process context if one exists
 func GetContext(ctx context.Context) *Context {
diff --git a/modules/process/manager.go b/modules/process/manager.go
index bdc4931810d..661511ce8d6 100644
--- a/modules/process/manager.go
+++ b/modules/process/manager.go
@@ -11,6 +11,8 @@ import (
 	"sync"
 	"sync/atomic"
 	"time"
+
+	"code.gitea.io/gitea/modules/gtprof"
 )
 
 // TODO: This packages still uses a singleton for the Manager.
@@ -25,18 +27,6 @@ var (
 	DefaultContext = context.Background()
 )
 
-// DescriptionPProfLabel is a label set on goroutines that have a process attached
-const DescriptionPProfLabel = "process-description"
-
-// PIDPProfLabel is a label set on goroutines that have a process attached
-const PIDPProfLabel = "pid"
-
-// PPIDPProfLabel is a label set on goroutines that have a process attached
-const PPIDPProfLabel = "ppid"
-
-// ProcessTypePProfLabel is a label set on goroutines that have a process attached
-const ProcessTypePProfLabel = "process-type"
-
 // IDType is a pid type
 type IDType string
 
@@ -187,7 +177,12 @@ func (pm *Manager) Add(ctx context.Context, description string, cancel context.C
 
 	Trace(true, pid, description, parentPID, processType)
 
-	pprofCtx := pprof.WithLabels(ctx, pprof.Labels(DescriptionPProfLabel, description, PPIDPProfLabel, string(parentPID), PIDPProfLabel, string(pid), ProcessTypePProfLabel, processType))
+	pprofCtx := pprof.WithLabels(ctx, pprof.Labels(
+		gtprof.LabelProcessDescription, description,
+		gtprof.LabelPpid, string(parentPID),
+		gtprof.LabelPid, string(pid),
+		gtprof.LabelProcessType, processType,
+	))
 	if currentlyRunning {
 		pprof.SetGoroutineLabels(pprofCtx)
 	}
diff --git a/modules/process/manager_stacktraces.go b/modules/process/manager_stacktraces.go
index e2608931131..d83060f6eec 100644
--- a/modules/process/manager_stacktraces.go
+++ b/modules/process/manager_stacktraces.go
@@ -10,6 +10,8 @@ import (
 	"sort"
 	"time"
 
+	"code.gitea.io/gitea/modules/gtprof"
+
 	"github.com/google/pprof/profile"
 )
 
@@ -202,7 +204,7 @@ func (pm *Manager) ProcessStacktraces(flat, noSystem bool) ([]*Process, int, int
 
 		// Add the non-process associated labels from the goroutine sample to the Stack
 		for name, value := range sample.Label {
-			if name == DescriptionPProfLabel || name == PIDPProfLabel || (!flat && name == PPIDPProfLabel) || name == ProcessTypePProfLabel {
+			if name == gtprof.LabelProcessDescription || name == gtprof.LabelPid || (!flat && name == gtprof.LabelPpid) || name == gtprof.LabelProcessType {
 				continue
 			}
 
@@ -224,7 +226,7 @@ func (pm *Manager) ProcessStacktraces(flat, noSystem bool) ([]*Process, int, int
 		var process *Process
 
 		// Try to get the PID from the goroutine labels
-		if pidvalue, ok := sample.Label[PIDPProfLabel]; ok && len(pidvalue) == 1 {
+		if pidvalue, ok := sample.Label[gtprof.LabelPid]; ok && len(pidvalue) == 1 {
 			pid := IDType(pidvalue[0])
 
 			// Now try to get the process from our map
@@ -238,20 +240,20 @@ func (pm *Manager) ProcessStacktraces(flat, noSystem bool) ([]*Process, int, int
 
 				// get the parent PID
 				ppid := IDType("")
-				if value, ok := sample.Label[PPIDPProfLabel]; ok && len(value) == 1 {
+				if value, ok := sample.Label[gtprof.LabelPpid]; ok && len(value) == 1 {
 					ppid = IDType(value[0])
 				}
 
 				// format the description
 				description := "(dead process)"
-				if value, ok := sample.Label[DescriptionPProfLabel]; ok && len(value) == 1 {
+				if value, ok := sample.Label[gtprof.LabelProcessDescription]; ok && len(value) == 1 {
 					description = value[0] + " " + description
 				}
 
 				// override the type of the process to "code" but add the old type as a label on the first stack
 				ptype := NoneProcessType
-				if value, ok := sample.Label[ProcessTypePProfLabel]; ok && len(value) == 1 {
-					stack.Labels = append(stack.Labels, &Label{Name: ProcessTypePProfLabel, Value: value[0]})
+				if value, ok := sample.Label[gtprof.LabelProcessType]; ok && len(value) == 1 {
+					stack.Labels = append(stack.Labels, &Label{Name: gtprof.LabelProcessType, Value: value[0]})
 				}
 				process = &Process{
 					PID:         pid,
diff --git a/modules/queue/base_test.go b/modules/queue/base_test.go
index c5bf526ae67..01b52b3c16e 100644
--- a/modules/queue/base_test.go
+++ b/modules/queue/base_test.go
@@ -46,10 +46,10 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error)
 		assert.NoError(t, err)
 		if !isUnique {
 			assert.EqualValues(t, 2, cnt)
-			assert.EqualValues(t, false, has) // non-unique queues don't check for duplicates
+			assert.False(t, has) // non-unique queues don't check for duplicates
 		} else {
 			assert.EqualValues(t, 1, cnt)
-			assert.EqualValues(t, true, has)
+			assert.True(t, has)
 		}
 
 		// push another item
@@ -101,7 +101,7 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error)
 		pushBlockTime = 30 * time.Millisecond
 		err = q.PushItem(ctx, []byte("item-full"))
 		assert.ErrorIs(t, err, context.DeadlineExceeded)
-		assert.True(t, time.Since(timeStart) >= pushBlockTime*2/3)
+		assert.GreaterOrEqual(t, time.Since(timeStart), pushBlockTime*2/3)
 		pushBlockTime = oldPushBlockTime
 
 		// remove all
diff --git a/modules/queue/workerqueue.go b/modules/queue/workerqueue.go
index 672e9a4114c..0f5b105551d 100644
--- a/modules/queue/workerqueue.go
+++ b/modules/queue/workerqueue.go
@@ -6,6 +6,7 @@ package queue
 import (
 	"context"
 	"fmt"
+	"runtime/pprof"
 	"sync"
 	"sync/atomic"
 	"time"
@@ -241,6 +242,9 @@ func NewWorkerPoolQueueWithContext[T any](ctx context.Context, name string, queu
 	w.origHandler = handler
 	w.safeHandler = func(t ...T) (unhandled []T) {
 		defer func() {
+			// FIXME: there is no ctx support in the handler, so process manager is unable to restore the labels
+			// so here we explicitly set the "queue ctx" labels again after the handler is done
+			pprof.SetGoroutineLabels(w.ctxRun)
 			err := recover()
 			if err != nil {
 				log.Error("Recovered from panic in queue %q handler: %v\n%s", name, err, log.Stack(2))
diff --git a/modules/queue/workerqueue_test.go b/modules/queue/workerqueue_test.go
index d66253ff664..c0841a1752a 100644
--- a/modules/queue/workerqueue_test.go
+++ b/modules/queue/workerqueue_test.go
@@ -172,8 +172,8 @@ func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSett
 
 	q2() // restart the queue to continue to execute the tasks in it
 
-	assert.NotZero(t, len(tasksQ1))
-	assert.NotZero(t, len(tasksQ2))
+	assert.NotEmpty(t, tasksQ1)
+	assert.NotEmpty(t, tasksQ2)
 	assert.EqualValues(t, testCount, len(tasksQ1)+len(tasksQ2))
 }
 
diff --git a/modules/references/references.go b/modules/references/references.go
index 2889430bcf6..6e549cb8758 100644
--- a/modules/references/references.go
+++ b/modules/references/references.go
@@ -164,9 +164,9 @@ func newKeywords() {
 	})
 }
 
-func doNewKeywords(close, reopen []string) {
-	issueCloseKeywordsPat = makeKeywordsPat(close)
-	issueReopenKeywordsPat = makeKeywordsPat(reopen)
+func doNewKeywords(closeKeywords, reopenKeywords []string) {
+	issueCloseKeywordsPat = makeKeywordsPat(closeKeywords)
+	issueReopenKeywordsPat = makeKeywordsPat(reopenKeywords)
 }
 
 // getGiteaHostName returns a normalized string with the local host name, with no scheme or port information
diff --git a/modules/references/references_test.go b/modules/references/references_test.go
index e5a0d60fe3b..e224c919e92 100644
--- a/modules/references/references_test.go
+++ b/modules/references/references_test.go
@@ -526,7 +526,7 @@ func TestCustomizeCloseKeywords(t *testing.T) {
 
 func TestParseCloseKeywords(t *testing.T) {
 	// Test parsing of CloseKeywords and ReopenKeywords
-	assert.Len(t, parseKeywords([]string{""}), 0)
+	assert.Empty(t, parseKeywords([]string{""}))
 	assert.Len(t, parseKeywords([]string{"  aa  ", " bb  ", "99", "#", "", "this is", "cc"}), 3)
 
 	for _, test := range []struct {
diff --git a/modules/repository/fork.go b/modules/repository/fork.go
index fbf00087167..84ed4b23d67 100644
--- a/modules/repository/fork.go
+++ b/modules/repository/fork.go
@@ -9,14 +9,25 @@ import (
 	"code.gitea.io/gitea/models/organization"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/setting"
 )
 
+// CanUserForkBetweenOwners returns true if user can fork between owners.
+// By default, a user can fork a repository from another owner, but not from themselves.
+// Many users really like to fork their own repositories, so add an experimental setting to allow this.
+func CanUserForkBetweenOwners(id1, id2 int64) bool {
+	if id1 != id2 {
+		return true
+	}
+	return setting.Repository.AllowForkIntoSameOwner
+}
+
 // CanUserForkRepo returns true if specified user can fork repository.
 func CanUserForkRepo(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (bool, error) {
 	if user == nil {
 		return false, nil
 	}
-	if repo.OwnerID != user.ID && !repo_model.HasForkedRepo(ctx, user.ID, repo.ID) {
+	if CanUserForkBetweenOwners(repo.OwnerID, user.ID) && !repo_model.HasForkedRepo(ctx, user.ID, repo.ID) {
 		return true, nil
 	}
 	ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx, user.ID)
diff --git a/modules/repository/fork_test.go b/modules/repository/fork_test.go
new file mode 100644
index 00000000000..f8c76d942d1
--- /dev/null
+++ b/modules/repository/fork_test.go
@@ -0,0 +1,25 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repository
+
+import (
+	"testing"
+
+	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/test"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCanUserForkBetweenOwners(t *testing.T) {
+	defer test.MockVariableValue(&setting.Repository.AllowForkIntoSameOwner)
+
+	setting.Repository.AllowForkIntoSameOwner = true
+	assert.True(t, CanUserForkBetweenOwners(1, 1))
+	assert.True(t, CanUserForkBetweenOwners(1, 2))
+
+	setting.Repository.AllowForkIntoSameOwner = false
+	assert.False(t, CanUserForkBetweenOwners(1, 1))
+	assert.True(t, CanUserForkBetweenOwners(1, 2))
+}
diff --git a/modules/repository/repo_test.go b/modules/repository/repo_test.go
index 68980f92f94..f3e7be6d7d7 100644
--- a/modules/repository/repo_test.go
+++ b/modules/repository/repo_test.go
@@ -62,15 +62,15 @@ func Test_calcSync(t *testing.T) {
 	}
 
 	inserts, deletes, updates := calcSync(gitTags, dbReleases)
-	if assert.EqualValues(t, 1, len(inserts), "inserts") {
+	if assert.Len(t, inserts, 1, "inserts") {
 		assert.EqualValues(t, *gitTags[2], *inserts[0], "inserts equal")
 	}
 
-	if assert.EqualValues(t, 1, len(deletes), "deletes") {
+	if assert.Len(t, deletes, 1, "deletes") {
 		assert.EqualValues(t, 1, deletes[0], "deletes equal")
 	}
 
-	if assert.EqualValues(t, 1, len(updates), "updates") {
+	if assert.Len(t, updates, 1, "updates") {
 		assert.EqualValues(t, *gitTags[1], *updates[0], "updates equal")
 	}
 }
diff --git a/modules/setting/cron_test.go b/modules/setting/cron_test.go
index 3187ab18a25..55244d70757 100644
--- a/modules/setting/cron_test.go
+++ b/modules/setting/cron_test.go
@@ -38,6 +38,6 @@ EXTEND = true
 	_, err = getCronSettings(cfg, "test", extended)
 	assert.NoError(t, err)
 	assert.True(t, extended.Base)
-	assert.EqualValues(t, extended.Second, "white rabbit")
+	assert.EqualValues(t, "white rabbit", extended.Second)
 	assert.True(t, extended.Extend)
 }
diff --git a/modules/setting/oauth2_test.go b/modules/setting/oauth2_test.go
index 38ee4d248d6..d0e5ccf13d2 100644
--- a/modules/setting/oauth2_test.go
+++ b/modules/setting/oauth2_test.go
@@ -74,5 +74,5 @@ DEFAULT_APPLICATIONS = tea
 DEFAULT_APPLICATIONS =
 `)
 	loadOAuth2From(cfg)
-	assert.Nil(t, nil, OAuth2.DefaultApplications)
+	assert.Nil(t, OAuth2.DefaultApplications)
 }
diff --git a/modules/setting/repository.go b/modules/setting/repository.go
index 14cf5805c02..c5619d0f048 100644
--- a/modules/setting/repository.go
+++ b/modules/setting/repository.go
@@ -53,6 +53,7 @@ var (
 		AllowDeleteOfUnadoptedRepositories      bool
 		DisableDownloadSourceArchives           bool
 		AllowForkWithoutMaximumLimit            bool
+		AllowForkIntoSameOwner                  bool
 
 		// Repository editor settings
 		Editor struct {
diff --git a/modules/setting/storage_test.go b/modules/setting/storage_test.go
index 8ee37fd2b6d..afff85537e3 100644
--- a/modules/setting/storage_test.go
+++ b/modules/setting/storage_test.go
@@ -447,7 +447,7 @@ MINIO_USE_SSL = true
 	assert.NoError(t, loadRepoArchiveFrom(cfg))
 	assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID)
 	assert.EqualValues(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey)
-	assert.EqualValues(t, true, RepoArchive.Storage.MinioConfig.UseSSL)
+	assert.True(t, RepoArchive.Storage.MinioConfig.UseSSL)
 	assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath)
 }
 
@@ -464,7 +464,7 @@ MINIO_BASE_PATH = /prefix
 	assert.NoError(t, loadRepoArchiveFrom(cfg))
 	assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID)
 	assert.EqualValues(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey)
-	assert.EqualValues(t, true, RepoArchive.Storage.MinioConfig.UseSSL)
+	assert.True(t, RepoArchive.Storage.MinioConfig.UseSSL)
 	assert.EqualValues(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath)
 
 	cfg, err = NewConfigProviderFromData(`
@@ -477,7 +477,7 @@ MINIO_BASE_PATH = /prefix
 	assert.NoError(t, err)
 	assert.NoError(t, loadRepoArchiveFrom(cfg))
 	assert.EqualValues(t, "127.0.0.1", RepoArchive.Storage.MinioConfig.IamEndpoint)
-	assert.EqualValues(t, true, RepoArchive.Storage.MinioConfig.UseSSL)
+	assert.True(t, RepoArchive.Storage.MinioConfig.UseSSL)
 	assert.EqualValues(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath)
 
 	cfg, err = NewConfigProviderFromData(`
@@ -495,7 +495,7 @@ MINIO_BASE_PATH = /lfs
 	assert.NoError(t, loadLFSFrom(cfg))
 	assert.EqualValues(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID)
 	assert.EqualValues(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey)
-	assert.EqualValues(t, true, LFS.Storage.MinioConfig.UseSSL)
+	assert.True(t, LFS.Storage.MinioConfig.UseSSL)
 	assert.EqualValues(t, "/lfs", LFS.Storage.MinioConfig.BasePath)
 
 	cfg, err = NewConfigProviderFromData(`
@@ -513,7 +513,7 @@ MINIO_BASE_PATH = /lfs
 	assert.NoError(t, loadLFSFrom(cfg))
 	assert.EqualValues(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID)
 	assert.EqualValues(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey)
-	assert.EqualValues(t, true, LFS.Storage.MinioConfig.UseSSL)
+	assert.True(t, LFS.Storage.MinioConfig.UseSSL)
 	assert.EqualValues(t, "/lfs", LFS.Storage.MinioConfig.BasePath)
 }
 
diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go
index f8e4f569b87..7479cfbd95a 100644
--- a/modules/ssh/ssh.go
+++ b/modules/ssh/ssh.go
@@ -17,6 +17,7 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
+	"reflect"
 	"strconv"
 	"strings"
 	"sync"
@@ -33,9 +34,26 @@ import (
 	gossh "golang.org/x/crypto/ssh"
 )
 
-type contextKey string
+// The ssh auth overall works like this:
+// NewServerConn:
+//	serverHandshake+serverAuthenticate:
+//		PublicKeyCallback:
+//			PublicKeyHandler (our code):
+//				reset(ctx.Permissions) and set ctx.Permissions.giteaKeyID = keyID
+//		pubKey.Verify
+//		return ctx.Permissions // only reaches here, the pub key is really authenticated
+//	set conn.Permissions from serverAuthenticate
+//  sessionHandler(conn)
+//
+// Then sessionHandler should only use the "verified keyID" from the original ssh conn, but not the ctx one.
+// Otherwise, if a user provides 2 keys A (a correct one) and B (public key matches but no private key),
+// then only A succeeds to authenticate, sessionHandler will see B's keyID
+//
+// After x/crypto >= 0.31.0 (fix CVE-2024-45337), the PublicKeyCallback will be called again for the verified key,
+// it mitigates the misuse for most cases, it's still good for us to make sure we don't rely on that mitigation
+// and do not misuse the PublicKeyCallback: we should only use the verified keyID from the verified ssh conn.
 
-const giteaKeyID = contextKey("gitea-key-id")
+const giteaPermissionExtensionKeyID = "gitea-perm-ext-key-id"
 
 func getExitStatusFromError(err error) int {
 	if err == nil {
@@ -61,8 +79,32 @@ func getExitStatusFromError(err error) int {
 	return waitStatus.ExitStatus()
 }
 
+// sessionPartial is the private struct from "gliderlabs/ssh/session.go"
+// We need to read the original "conn" field from "ssh.Session interface" which contains the "*session pointer"
+// https://github.com/gliderlabs/ssh/blob/d137aad99cd6f2d9495bfd98c755bec4e5dffb8c/session.go#L109-L113
+// If upstream fixes the problem and/or changes the struct, we need to follow.
+// If the struct mismatches, the builtin ssh server will fail during integration tests.
+type sessionPartial struct {
+	sync.Mutex
+	gossh.Channel
+	conn *gossh.ServerConn
+}
+
+func ptr[T any](intf any) *T {
+	// https://pkg.go.dev/unsafe#Pointer
+	// (1) Conversion of a *T1 to Pointer to *T2.
+	// Provided that T2 is no larger than T1 and that the two share an equivalent memory layout,
+	// this conversion allows reinterpreting data of one type as data of another type.
+	v := reflect.ValueOf(intf)
+	p := v.UnsafePointer()
+	return (*T)(p)
+}
+
 func sessionHandler(session ssh.Session) {
-	keyID := fmt.Sprintf("%d", session.Context().Value(giteaKeyID).(int64))
+	// here can't use session.Permissions() because it only uses the value from ctx, which might not be the authenticated one.
+	// so we must use the original ssh conn, which always contains the correct (verified) keyID.
+	sshSession := ptr[sessionPartial](session)
+	keyID := sshSession.conn.Permissions.Extensions[giteaPermissionExtensionKeyID]
 
 	command := session.RawCommand()
 
@@ -164,6 +206,20 @@ func sessionHandler(session ssh.Session) {
 }
 
 func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
+	// The publicKeyHandler (PublicKeyCallback) only helps to provide the candidate keys to authenticate,
+	// It does NOT really verify here, so we could only record the related information here.
+	// After authentication (Verify), the "Permissions" will be assigned to the ssh conn,
+	// then we can use it in the "session handler"
+
+	// first, reset the ctx permissions (just like https://github.com/gliderlabs/ssh/pull/243 does)
+	// it shouldn't be reused across different ssh conn (sessions), each pub key should have its own "Permissions"
+	ctx.Permissions().Permissions = &gossh.Permissions{}
+	setPermExt := func(keyID int64) {
+		ctx.Permissions().Permissions.Extensions = map[string]string{
+			giteaPermissionExtensionKeyID: fmt.Sprint(keyID),
+		}
+	}
+
 	if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
 		log.Debug("Handle Public Key: Fingerprint: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr())
 	}
@@ -238,8 +294,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
 			if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
 				log.Debug("Successfully authenticated: %s Certificate Fingerprint: %s Principal: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key), principal)
 			}
-			ctx.SetValue(giteaKeyID, pkey.ID)
-
+			setPermExt(pkey.ID)
 			return true
 		}
 
@@ -266,8 +321,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
 	if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary
 		log.Debug("Successfully authenticated: %s Public Key Fingerprint: %s", ctx.RemoteAddr(), gossh.FingerprintSHA256(key))
 	}
-	ctx.SetValue(giteaKeyID, pkey.ID)
-
+	setPermExt(pkey.ID)
 	return true
 }
 
diff --git a/modules/templates/util_date.go b/modules/templates/util_date.go
index 66f83d23fe5..658691ee40c 100644
--- a/modules/templates/util_date.go
+++ b/modules/templates/util_date.go
@@ -53,8 +53,8 @@ func parseLegacy(datetime string) time.Time {
 	return t
 }
 
-func anyToTime(any any) (t time.Time, isZero bool) {
-	switch v := any.(type) {
+func anyToTime(value any) (t time.Time, isZero bool) {
+	switch v := value.(type) {
 	case nil:
 		// it is zero
 	case *time.Time:
@@ -72,7 +72,7 @@ func anyToTime(any any) (t time.Time, isZero bool) {
 	case int64:
 		t = timeutil.TimeStamp(v).AsTime()
 	default:
-		panic(fmt.Sprintf("Unsupported time type %T", any))
+		panic(fmt.Sprintf("Unsupported time type %T", value))
 	}
 	return t, t.IsZero() || t.Unix() == 0
 }
diff --git a/modules/templates/util_string.go b/modules/templates/util_string.go
index 479b755da1c..382e2de13f1 100644
--- a/modules/templates/util_string.go
+++ b/modules/templates/util_string.go
@@ -53,10 +53,14 @@ func (su *StringUtils) Cut(s, sep string) []any {
 	return []any{before, after, found}
 }
 
-func (su *StringUtils) EllipsisString(s string, max int) string {
-	return base.EllipsisString(s, max)
+func (su *StringUtils) EllipsisString(s string, maxLength int) string {
+	return base.EllipsisString(s, maxLength)
 }
 
 func (su *StringUtils) ToUpper(s string) string {
 	return strings.ToUpper(s)
 }
+
+func (su *StringUtils) TrimPrefix(s, prefix string) string {
+	return strings.TrimPrefix(s, prefix)
+}
diff --git a/modules/user/user_test.go b/modules/user/user_test.go
index 9129ae79a13..372a675d342 100644
--- a/modules/user/user_test.go
+++ b/modules/user/user_test.go
@@ -4,7 +4,6 @@
 package user
 
 import (
-	"os"
 	"os/exec"
 	"runtime"
 	"strings"
@@ -36,7 +35,7 @@ func TestCurrentUsername(t *testing.T) {
 	if user != whoami {
 		t.Errorf("expected %s as user, got: %s", whoami, user)
 	}
-	os.Setenv("USER", "spoofed")
+	t.Setenv("USER", "spoofed")
 	user = CurrentUsername()
 	if user != whoami {
 		t.Errorf("expected %s as user, got: %s", whoami, user)
diff --git a/modules/util/color_test.go b/modules/util/color_test.go
index be6e6b122a5..abd55512184 100644
--- a/modules/util/color_test.go
+++ b/modules/util/color_test.go
@@ -27,9 +27,9 @@ func Test_HexToRBGColor(t *testing.T) {
 	}
 	for n, c := range cases {
 		r, g, b := HexToRBGColor(c.colorString)
-		assert.Equal(t, c.expectedR, r, "case %d: error R should match: expected %f, but get %f", n, c.expectedR, r)
-		assert.Equal(t, c.expectedG, g, "case %d: error G should match: expected %f, but get %f", n, c.expectedG, g)
-		assert.Equal(t, c.expectedB, b, "case %d: error B should match: expected %f, but get %f", n, c.expectedB, b)
+		assert.InDelta(t, c.expectedR, r, 0, "case %d: error R should match: expected %f, but get %f", n, c.expectedR, r)
+		assert.InDelta(t, c.expectedG, g, 0, "case %d: error G should match: expected %f, but get %f", n, c.expectedG, g)
+		assert.InDelta(t, c.expectedB, b, 0, "case %d: error B should match: expected %f, but get %f", n, c.expectedB, b)
 	}
 }
 
diff --git a/modules/util/keypair_test.go b/modules/util/keypair_test.go
index c6f68c845a4..2bade3bb28a 100644
--- a/modules/util/keypair_test.go
+++ b/modules/util/keypair_test.go
@@ -10,7 +10,6 @@ import (
 	"crypto/sha256"
 	"crypto/x509"
 	"encoding/pem"
-	"regexp"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -23,8 +22,8 @@ func TestKeygen(t *testing.T) {
 	assert.NotEmpty(t, priv)
 	assert.NotEmpty(t, pub)
 
-	assert.Regexp(t, regexp.MustCompile("^-----BEGIN RSA PRIVATE KEY-----.*"), priv)
-	assert.Regexp(t, regexp.MustCompile("^-----BEGIN PUBLIC KEY-----.*"), pub)
+	assert.Regexp(t, "^-----BEGIN RSA PRIVATE KEY-----.*", priv)
+	assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----.*", pub)
 }
 
 func TestSignUsingKeys(t *testing.T) {
diff --git a/modules/util/runtime.go b/modules/util/runtime.go
new file mode 100644
index 00000000000..91ec3c869c3
--- /dev/null
+++ b/modules/util/runtime.go
@@ -0,0 +1,13 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package util
+
+import "runtime"
+
+func CallerFuncName(skip int) string {
+	pc := make([]uintptr, 1)
+	runtime.Callers(skip+1, pc)
+	funcName := runtime.FuncForPC(pc[0]).Name()
+	return funcName
+}
diff --git a/modules/util/runtime_test.go b/modules/util/runtime_test.go
new file mode 100644
index 00000000000..20f9063b0b6
--- /dev/null
+++ b/modules/util/runtime_test.go
@@ -0,0 +1,32 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package util
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCallerFuncName(t *testing.T) {
+	s := CallerFuncName(1)
+	assert.Equal(t, "code.gitea.io/gitea/modules/util.TestCallerFuncName", s)
+}
+
+func BenchmarkCallerFuncName(b *testing.B) {
+	// BenchmarkCaller/sprintf-12         	12744829	        95.49 ns/op
+	b.Run("sprintf", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			_ = fmt.Sprintf("aaaaaaaaaaaaaaaa %s %s %s", "bbbbbbbbbbbbbbbbbbb", b.Name(), "ccccccccccccccccccccc")
+		}
+	})
+	// BenchmarkCaller/caller-12          	10625133	       113.6 ns/op
+	// It is almost as fast as fmt.Sprintf
+	b.Run("caller", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			CallerFuncName(1)
+		}
+	})
+}
diff --git a/modules/util/time_str.go b/modules/util/time_str.go
index b56f4740af4..0fccfe82cc1 100644
--- a/modules/util/time_str.go
+++ b/modules/util/time_str.go
@@ -25,7 +25,7 @@ type timeStrGlobalVarsType struct {
 // In the future, it could be some configurable options to help users
 // to convert the working time to different units.
 
-var timeStrGlobalVars = sync.OnceValue[*timeStrGlobalVarsType](func() *timeStrGlobalVarsType {
+var timeStrGlobalVars = sync.OnceValue(func() *timeStrGlobalVarsType {
 	v := &timeStrGlobalVarsType{}
 	v.re = regexp.MustCompile(`(?i)(\d+)\s*([hms])`)
 	v.units = []struct {
diff --git a/modules/util/time_str_test.go b/modules/util/time_str_test.go
index 67b7978d0bf..8d1de51c8e6 100644
--- a/modules/util/time_str_test.go
+++ b/modules/util/time_str_test.go
@@ -27,9 +27,9 @@ func TestTimeStr(t *testing.T) {
 			t.Run(test.input, func(t *testing.T) {
 				output, err := TimeEstimateParse(test.input)
 				if test.err {
-					assert.NotNil(t, err)
+					assert.Error(t, err)
 				} else {
-					assert.Nil(t, err)
+					assert.NoError(t, err)
 				}
 				assert.Equal(t, test.output, output)
 			})
diff --git a/modules/util/util_test.go b/modules/util/util_test.go
index 9ce72fb8669..52b05acc5bc 100644
--- a/modules/util/util_test.go
+++ b/modules/util/util_test.go
@@ -122,8 +122,8 @@ func Test_NormalizeEOL(t *testing.T) {
 
 func Test_RandomInt(t *testing.T) {
 	randInt, err := CryptoRandomInt(255)
-	assert.True(t, randInt >= 0)
-	assert.True(t, randInt <= 255)
+	assert.GreaterOrEqual(t, randInt, int64(0))
+	assert.LessOrEqual(t, randInt, int64(255))
 	assert.NoError(t, err)
 }
 
@@ -223,29 +223,29 @@ func BenchmarkToUpper(b *testing.B) {
 }
 
 func TestToTitleCase(t *testing.T) {
-	assert.Equal(t, ToTitleCase(`foo bar baz`), `Foo Bar Baz`)
-	assert.Equal(t, ToTitleCase(`FOO BAR BAZ`), `Foo Bar Baz`)
+	assert.Equal(t, `Foo Bar Baz`, ToTitleCase(`foo bar baz`))
+	assert.Equal(t, `Foo Bar Baz`, ToTitleCase(`FOO BAR BAZ`))
 }
 
 func TestToPointer(t *testing.T) {
 	assert.Equal(t, "abc", *ToPointer("abc"))
 	assert.Equal(t, 123, *ToPointer(123))
 	abc := "abc"
-	assert.False(t, &abc == ToPointer(abc))
+	assert.NotSame(t, &abc, ToPointer(abc))
 	val123 := 123
-	assert.False(t, &val123 == ToPointer(val123))
+	assert.NotSame(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")
+	assert.Equal(t, "test\ndata", ReserveLineBreakForTextarea("test\r\ndata"))
+	assert.Equal(t, "test\ndata\n", ReserveLineBreakForTextarea("test\r\ndata\r\n"))
 }
 
 func TestOptionalArg(t *testing.T) {
-	foo := func(other any, optArg ...int) int {
+	foo := func(_ any, optArg ...int) int {
 		return OptionalArg(optArg)
 	}
-	bar := func(other any, optArg ...int) int {
+	bar := func(_ any, optArg ...int) int {
 		return OptionalArg(optArg, 42)
 	}
 	assert.Equal(t, 0, foo(nil))
diff --git a/options/license/MIPS b/options/license/MIPS
new file mode 100644
index 00000000000..cf57a056391
--- /dev/null
+++ b/options/license/MIPS
@@ -0,0 +1,4 @@
+Copyright (c) 1992, 1991, 1990 MIPS Computer Systems, Inc.
+MIPS Computer Systems, Inc. grants reproduction and use
+rights to all parties, PROVIDED that this comment is
+maintained in the copy.
diff --git a/options/license/ThirdEye b/options/license/ThirdEye
new file mode 100644
index 00000000000..ce75b566e37
--- /dev/null
+++ b/options/license/ThirdEye
@@ -0,0 +1,7 @@
+(C) Copyright 1984 by Third Eye Software, Inc.
+
+Third Eye Software, Inc. grants reproduction and use rights to
+all parties, PROVIDED that this comment is maintained in the copy.
+
+Third Eye makes no claims about the applicability of this
+symbol table to a particular use.
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index f50ad1f2981..92ce4f2db9f 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1680,7 +1680,6 @@ issues.timetracker_timer_stop = Stop timer
 issues.timetracker_timer_discard = Discard timer
 issues.timetracker_timer_manually_add = Add Time
 
-issues.time_estimate_placeholder = 1h 2m
 issues.time_estimate_set = Set estimated time
 issues.time_estimate_display = Estimate: %s
 issues.change_time_estimate_at = changed time estimate to %s %s
@@ -2633,6 +2632,7 @@ release.new_release = New Release
 release.draft = Draft
 release.prerelease = Pre-Release
 release.stable = Stable
+release.latest = Latest
 release.compare = Compare
 release.edit = edit
 release.ahead.commits = %d commits
diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini
index 4d4940cff5a..08a40a24949 100644
--- a/options/locale/locale_fr-FR.ini
+++ b/options/locale/locale_fr-FR.ini
@@ -1109,6 +1109,7 @@ delete_preexisting_success=Fichiers dépossédés supprimés dans %s.
 blame_prior=Voir le blame avant cette modification
 blame.ignore_revs=Les révisions dans .git-blame-ignore-revs sont ignorées. Vous pouvez quand même voir ces blâmes.
 blame.ignore_revs.failed=Impossible d'ignorer les révisions dans .git-blame-ignore-revs.
+user_search_tooltip=Affiche un maximum de 30 utilisateurs
 
 tree_path_not_found_commit=Le chemin %[1]s n’existe pas dans la révision %[2]s.
 tree_path_not_found_branch=Le chemin %[1]s n’existe pas dans la branche %[2]s.
@@ -1527,6 +1528,8 @@ issues.filter_assignee=Assigné
 issues.filter_assginee_no_select=Tous les assignés
 issues.filter_assginee_no_assignee=Aucun assigné
 issues.filter_poster=Auteur
+issues.filter_user_placeholder=Rechercher des utilisateurs
+issues.filter_user_no_select=Tous les utilisateurs
 issues.filter_type=Type
 issues.filter_type.all_issues=Tous les tickets
 issues.filter_type.assigned_to_you=Qui vous sont assignés
@@ -1676,7 +1679,6 @@ issues.timetracker_timer_stop=Arrêter le minuteur
 issues.timetracker_timer_discard=Annuler le minuteur
 issues.timetracker_timer_manually_add=Pointer du temps
 
-issues.time_estimate_placeholder=1h 2m
 issues.time_estimate_set=Définir le temps estimé
 issues.time_estimate_display=Estimation : %s
 issues.change_time_estimate_at=a changé le temps estimé à %s %s
@@ -2629,6 +2631,7 @@ release.new_release=Nouvelle publication
 release.draft=Brouillon
 release.prerelease=Pré-publication
 release.stable=Stable
+release.latest=Dernière
 release.compare=Comparer
 release.edit=Éditer
 release.ahead.commits=%d révisions
diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini
index f40e0037d2c..c37c8e0157d 100644
--- a/options/locale/locale_ga-IE.ini
+++ b/options/locale/locale_ga-IE.ini
@@ -1109,6 +1109,7 @@ delete_preexisting_success=Scriosta comhaid neamhghlactha i %s
 blame_prior=Féach ar an milleán roimh an athrú seo
 blame.ignore_revs=Ag déanamh neamhairde de leasuithe i .git-blame-ignore-revs. Cliceáil anseo chun seachaint agus an gnáth-amharc milleán a fheiceáil.
 blame.ignore_revs.failed=Theip ar neamhaird a dhéanamh ar leasuithe i .git-blame-ignore-revs.
+user_search_tooltip=Taispeáint uasmhéid de 30 úsáideoir
 
 tree_path_not_found_commit=Níl cosán %[1]s ann i dtiomantas %[2]s
 tree_path_not_found_branch=Níl cosán %[1]s ann i mbrainse %[2]s
@@ -1527,6 +1528,8 @@ issues.filter_assignee=Sannaitheoir
 issues.filter_assginee_no_select=Gach sannaithe
 issues.filter_assginee_no_assignee=Gan sannaitheoir
 issues.filter_poster=Údar
+issues.filter_user_placeholder=Cuardaigh úsáideoirí
+issues.filter_user_no_select=Gach úsáideoir
 issues.filter_type=Cineál
 issues.filter_type.all_issues=Gach saincheist
 issues.filter_type.assigned_to_you=Sannta duit
@@ -1676,7 +1679,6 @@ issues.timetracker_timer_stop=Stop an t-amadóir
 issues.timetracker_timer_discard=Déan an t-amadóir a scriosadh
 issues.timetracker_timer_manually_add=Cuir Am leis
 
-issues.time_estimate_placeholder=1u 2n
 issues.time_estimate_set=Socraigh am measta
 issues.time_estimate_display=Meastachán: %s
 issues.change_time_estimate_at=d'athraigh an meastachán ama go %s %s
@@ -2629,6 +2631,7 @@ release.new_release=Scaoileadh Nua
 release.draft=Dréacht
 release.prerelease=Réamh-eisiúint
 release.stable=Cobhsaí
+release.latest=Is déanaí
 release.compare=Déan comparáid
 release.edit=cuir in eagar
 release.ahead.commits=Geallann %d
diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini
index 237323a0fc4..00a406aae45 100644
--- a/options/locale/locale_id-ID.ini
+++ b/options/locale/locale_id-ID.ini
@@ -22,11 +22,25 @@ toc=Daftar Isi
 username=Nama Pengguna
 email=Alamat Email
 password=Kata Sandi
+re_type=Konfirmasi Kata Sandi
 captcha=CAPTCHA
 twofa=Otentikasi Dua Faktor
 twofa_scratch=Kode Awal Dua Faktor
 passcode=Kode Akses
 
+webauthn_insert_key=Masukkan kunci keamanan anda
+webauthn_sign_in=Tekan tombol pada kunci keamanan Anda. Jika kunci keamanan Anda tidak memiliki tombol, masukkan kembali.
+webauthn_press_button=Silakan tekan tombol pada kunci keamanan Anda…
+webauthn_use_twofa=Gunakan kode dua faktor dari telepon Anda
+webauthn_error=Tidak dapat membaca kunci keamanan Anda.
+webauthn_unsupported_browser=Browser Anda saat ini tidak mendukung WebAuthn.
+webauthn_error_unknown=Terdapat kesalahan yang tidak diketahui. Mohon coba lagi.
+webauthn_error_insecure=`WebAuthn hanya mendukung koneksi aman. Untuk pengujian melalui HTTP, Anda dapat menggunakan "localhost" atau "127.0.0.1"`
+webauthn_error_unable_to_process=Server tidak dapat memproses permintaan Anda.
+webauthn_error_duplicated=Kunci keamanan tidak diperbolehkan untuk permintaan ini. Pastikan bahwa kunci ini belum terdaftar sebelumnya.
+webauthn_error_empty=Anda harus menetapkan nama untuk kunci ini.
+webauthn_error_timeout=Waktu habis sebelum kunci Anda dapat dibaca. Mohon muat ulang halaman ini dan coba lagi.
+webauthn_reload=Muat ulang
 
 repository=Repositori
 organization=Organisasi
@@ -36,6 +50,8 @@ new_migrate=Migrasi Baru
 new_mirror=Duplikat Baru
 new_fork=Fork Repositori Baru
 new_org=Organisasi Baru
+new_project=Proyek Baru
+new_project_column=Kolom Baru
 manage_org=Mengelola Organisasi
 admin_panel=Administrasi Situs
 account_settings=Pengaturan Akun
@@ -55,29 +71,58 @@ pull_requests=Tarik Permintaan
 issues=Masalah
 milestones=Tonggak
 
+ok=Oke
 cancel=Batal
+retry=Coba lagi
+rerun=Jalankan ulang
+rerun_all=Jalankan ulang semua job
 save=Simpan
 add=Tambah
 add_all=Tambah Semua
 remove=Buang
 remove_all=Buang Semua
+remove_label_str=`Hapus item "%s"`
 edit=Edit
+view=Tampilan
+test=Pengujian
 
 enabled=Aktif
 disabled=Nonaktif
+locked=Terkunci
 
+copy=Salin
+copy_url=Salin URL
+copy_hash=Salin hash
+copy_content=Salin konten
+copy_branch=Salin nama branch
+copy_success=Tersalin!
+copy_error=Gagal menyalin
+copy_type_unsupported=Tipe berkas ini tidak dapat disalin
 
 write=Tulis
 preview=Pratinjau
 loading=Memuat…
 
+error=Gangguan
+error404=Halaman yang akan kamu akses tidak dapat ditemukan atau kamu tidak memiliki akses  untuk melihatnya.
+go_back=Kembali
+invalid_data=Data invalid: %v
 
+never=Tidak Pernah
+unknown=Tidak diketahui
 
+rss_feed=Umpan Berita
 
+pin=Sematkan
+unpin=Lepas sematan
 
+artifacts=Artefak
+confirm_delete_artifact=Apakah Anda yakin ingin menghapus artefak '%s' ?
 
 archived=Diarsipkan
 
+concept_system_global=Global
+concept_user_individual=Perorangan
 concept_code_repository=Repositori
 
 show_full_screen=Tampilkan layar penuh
@@ -682,13 +727,16 @@ commits.newer=Terbaru
 commits.signed_by=Ditandai oleh
 
 
+commitstatus.error=Gangguan
 
 
 projects.description_placeholder=Deskripsi
 projects.title=Judul
+projects.new=Proyek Baru
 projects.template.desc=Contoh
 projects.column.edit_title=Nama
 projects.column.new_title=Nama
+projects.column.new=Kolom Baru
 
 issues.new=Masalah Baru
 issues.new.labels=Label
diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini
index 1d8b33bef68..4d3c7118d7c 100644
--- a/options/locale/locale_ja-JP.ini
+++ b/options/locale/locale_ja-JP.ini
@@ -145,6 +145,7 @@ confirm_delete_selected=選択したすべてのアイテムを削除してよ
 
 name=名称
 value=値
+readme=Readme
 
 filter=フィルター
 filter.clear=フィルターをクリア
@@ -1043,6 +1044,8 @@ generate_repo=リポジトリの生成
 generate_from=他からの生成
 repo_desc=説明
 repo_desc_helper=簡単な説明を入力してください (オプション)
+repo_no_desc=説明が提供されていません
+repo_lang=言語
 repo_gitignore_helper=.gitignoreテンプレートを選択してください。
 repo_gitignore_helper_desc=一般的な言語のテンプレートリストから、追跡しないファイルの設定を選択します。 各言語のビルドツールが生成する典型的なファイルが、デフォルトで.gitignoreに含まれます。
 issue_labels=イシューラベル
@@ -1666,12 +1669,25 @@ issues.delete.title=このイシューを削除しますか?
 issues.delete.text=本当にこのイシューを削除しますか? (これはすべてのコンテンツを完全に削除します。 保存しておきたい場合は、代わりにクローズすることを検討してください)
 
 issues.tracker=タイムトラッカー
+issues.timetracker_timer_start=タイマー開始
+issues.timetracker_timer_stop=タイマー終了
+issues.timetracker_timer_discard=タイマー破棄
+issues.timetracker_timer_manually_add=時間を追加
 
+issues.time_estimate_set=見積時間を設定
+issues.time_estimate_display=見積時間: %s
+issues.change_time_estimate_at=が見積時間を %s に変更 %s
+issues.remove_time_estimate_at=が見積時間を削除 %s
+issues.time_estimate_invalid=見積時間のフォーマットが不正です
+issues.start_tracking_history=が作業を開始 %s
 issues.tracker_auto_close=タイマーは、このイシューがクローズされると自動的に終了します
 issues.tracking_already_started=`別のイシューで既にタイムトラッキングを開始しています!`
+issues.stop_tracking_history=が %s の作業を終了 %s
 issues.cancel_tracking_history=`がタイムトラッキングを中止 %s`
 issues.del_time=このタイムログを削除
+issues.add_time_history=が作業時間 %s を追加 %s
 issues.del_time_history=`が作業時間を削除 %s`
+issues.add_time_manually=時間の手入力
 issues.add_time_hours=時間
 issues.add_time_minutes=分
 issues.add_time_sum_to_small=時間が入力されていません。
@@ -1691,15 +1707,15 @@ issues.due_date_form_add=期日の追加
 issues.due_date_form_edit=変更
 issues.due_date_form_remove=削除
 issues.due_date_not_writer=イシューの期日を変更するには、リポジトリへの書き込み権限が必要です。
-issues.due_date_not_set=期日は未設定です。
+issues.due_date_not_set=期日は設定されていません。
 issues.due_date_added=が期日 %s を追加 %s
 issues.due_date_modified=が期日を %[2]s から %[1]s に変更 %[3]s
 issues.due_date_remove=が期日 %s を削除 %s
 issues.due_date_overdue=期日は過ぎています
 issues.due_date_invalid=期日が正しくないか範囲を超えています。 'yyyy-mm-dd' の形式で入力してください。
 issues.dependency.title=依存関係
-issues.dependency.issue_no_dependencies=依存関係が設定されていません。
-issues.dependency.pr_no_dependencies=依存関係が設定されていません。
+issues.dependency.issue_no_dependencies=依存関係は設定されていません。
+issues.dependency.pr_no_dependencies=依存関係は設定されていません。
 issues.dependency.no_permission_1=%d 個の依存関係への読み取り権限がありません
 issues.dependency.no_permission_n=%d 個の依存関係への読み取り権限がありません
 issues.dependency.no_permission.can_remove=この依存関係への読み取り権限はありませんが、この依存関係は削除できます
@@ -1924,6 +1940,10 @@ pulls.delete.title=このプルリクエストを削除しますか?
 pulls.delete.text=本当にこのプルリクエストを削除しますか? (これはすべてのコンテンツを完全に削除します。 保存しておきたい場合は、代わりにクローズすることを検討してください)
 
 pulls.recently_pushed_new_branches=%[2]s 、あなたはブランチ %[1]s にプッシュしました
+pulls.upstream_diverging_prompt_behind_1=このブランチは %[2]s よりも %[1]d コミット遅れています
+pulls.upstream_diverging_prompt_behind_n=このブランチは %[2]s よりも %[1]d コミット遅れています
+pulls.upstream_diverging_prompt_base_newer=ベースブランチ %s に新しい変更があります
+pulls.upstream_diverging_merge=フォークを同期
 
 pull.deleted_branch=(削除済み):%s
 pull.agit_documentation=AGitに関するドキュメントを確認する
diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini
index 776d2bdc2bf..b959e7fdba2 100644
--- a/options/locale/locale_pt-PT.ini
+++ b/options/locale/locale_pt-PT.ini
@@ -1679,7 +1679,6 @@ issues.timetracker_timer_stop=Parar cronómetro
 issues.timetracker_timer_discard=Descartar cronómetro
 issues.timetracker_timer_manually_add=Adicionar tempo
 
-issues.time_estimate_placeholder=1h 2m
 issues.time_estimate_set=Definir tempo estimado
 issues.time_estimate_display=Estimativa: %s
 issues.change_time_estimate_at=alterou a estimativa de tempo para %s %s
@@ -2632,6 +2631,7 @@ release.new_release=Novo lançamento
 release.draft=Rascunho
 release.prerelease=Pré-lançamento
 release.stable=Estável
+release.latest=Mais recente
 release.compare=Comparar
 release.edit=editar
 release.ahead.commits=%d cometimentos
diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index 3add7f8be38..8c6392a4dc9 100644
--- a/options/locale/locale_zh-CN.ini
+++ b/options/locale/locale_zh-CN.ini
@@ -93,6 +93,7 @@ remove_all=移除所有
 remove_label_str=`删除标签 "%s"`
 edit=编辑
 view=查看
+test=测试
 
 enabled=启用
 disabled=禁用
@@ -103,6 +104,7 @@ copy_url=复制网址
 copy_hash=复制哈希值
 copy_content=复制内容
 copy_branch=复制分支名
+copy_path=复制路径
 copy_success=复制成功!
 copy_error=复制失败
 copy_type_unsupported=无法复制此类型的文件内容
@@ -143,6 +145,7 @@ confirm_delete_selected=确认删除所有选中项目?
 
 name=名称
 value=值
+readme=自述文档
 
 filter=过滤
 filter.clear=清除筛选器
@@ -178,6 +181,7 @@ package_kind=搜索软件包...
 project_kind=搜索项目...
 branch_kind=搜索分支...
 tag_kind=搜索标签...
+tag_tooltip=搜索匹配的标签。使用“%”来匹配任何序列的数字
 commit_kind=搜索提交记录...
 runner_kind=搜索runners...
 no_results=未找到匹配结果
@@ -223,16 +227,20 @@ string.desc=Z - A
 
 [error]
 occurred=发生了一个错误
+report_message=如果您确定这是一个 Gitea bug,请在 这里 搜索问题,或在必要时创建一个新工单。
 not_found=找不到目标。
 network_error=网络错误
 
 [startpage]
 app_desc=一款极易搭建的自助 Git 服务
 install=易安装
+install_desc=通过 二进制 来运行;或者通过 Docker 来运行;或者通过 安装包 来运行。
 platform=跨平台
+platform_desc=任何 Go 语言 支持的平台都可以运行 Gitea,包括 Windows、Mac、Linux 以及 ARM。挑一个您喜欢的就行!
 lightweight=轻量级
 lightweight_desc=一个廉价的树莓派的配置足以满足 Gitea 的最低系统硬件要求。最大程度上节省您的服务器资源!
 license=开源化
+license_desc=所有的代码都开源在 %[2]s 上,赶快加入我们来共同发展这个伟大的项目!还等什么?成为贡献者吧!
 
 [install]
 install=安装页面
@@ -346,6 +354,7 @@ enable_update_checker=启用更新检查
 enable_update_checker_helper=通过连接到 gitea.io 定期检查新版本发布。
 env_config_keys=环境配置
 env_config_keys_prompt=以下环境变量也将应用于您的配置文件:
+config_write_file_prompt=这些配置选项将写入以下位置: %s
 
 [home]
 nav_menu=导航菜单
@@ -386,6 +395,7 @@ relevant_repositories=只显示相关的仓库, 显示未过滤
 
 [auth]
 create_new_account=注册帐号
+already_have_account=已有账号?
 sign_in_now=立即登录
 disable_register_prompt=对不起,注册功能已被关闭。请联系网站管理员。
 disable_register_mail=已禁用注册的电子邮件确认。
@@ -394,6 +404,7 @@ remember_me=记住此设备
 remember_me.compromised=登录令牌不再有效,因为它可能表明帐户已被破坏。请检查您的帐户是否有异常活动。
 forgot_password_title=忘记密码
 forgot_password=忘记密码?
+need_account=需要一个帐户?
 sign_up_now=还没账号?马上注册。
 sign_up_successful=帐户创建成功。欢迎!
 confirmation_mail_sent_prompt_ex=一封新的确认邮件已经发送到 %s请在下一个 %s 中检查您的收件箱以完成注册过程。 如果您的注册电子邮件地址不正确,您可以重新登录并更改它。
@@ -449,12 +460,15 @@ authorize_application=应用授权
 authorize_redirect_notice=如果您授权此应用,您将会被重定向到 %s。
 authorize_application_created_by=此应用由%s创建。
 authorize_application_description=如果您允许,它将能够读取和修改您的所有帐户信息,包括私人仓库和组织。
+authorize_application_with_scopes=范围: %s
 authorize_title=授权 %s 访问您的帐户?
 authorization_failed=授权失败
 authorization_failed_desc=因为检测到无效请求,授权失败。请尝试联系您授权应用的管理员。
 sspi_auth_failed=SSPI 认证失败
+password_pwned=此密码出现在 被盗密码 列表上并且曾经被公开。 请使用另一个密码再试一次。
 password_pwned_err=无法完成对 HaveIBeenPwned 的请求
 last_admin=您不能删除最后一个管理员。必须至少保留一个管理员。
+signin_passkey=使用密钥登录
 back_to_sign_in=返回登录页面
 
 [mail]
@@ -574,6 +588,8 @@ lang_select_error=从列表中选出语言
 
 username_been_taken=用户名已被使用。
 username_change_not_local_user=非本地用户不允许更改用户名。
+change_username_disabled=更改用户名已被禁用。
+change_full_name_disabled=更改用户全名已禁用
 username_has_not_been_changed=用户名未更改
 repo_name_been_taken=仓库名称已被使用。
 repository_force_private=“强制私有”已启用:私有仓库不能被公开。
@@ -623,6 +639,7 @@ org_still_own_repo=该组织仍然是某些仓库的拥有者,请先删除或
 org_still_own_packages=该组织仍然是一个或多个软件包的拥有者,请先删除它们。
 
 target_branch_not_exist=目标分支不存在。
+target_ref_not_exist=目标引用 %s 不存在
 
 admin_cannot_delete_self=当您是管理员时,您不能删除自己。请先移除您的管理员权限
 
@@ -688,14 +705,18 @@ applications=应用
 orgs=管理组织
 repos=仓库列表
 delete=删除帐户
+twofa=两步验证(TOTP)
 account_link=已绑定帐户
 organization=组织
 uid=UID
+webauthn=两步验证(安全密钥)
 
 public_profile=公开信息
 biography_placeholder=告诉我们一点您自己! (您可以使用Markdown)
 location_placeholder=与他人分享你的大概位置
 profile_desc=控制您的个人资料对其他用户的显示方式。您的主要电子邮件地址将用于通知、密码恢复和基于网页界面的 Git 操作
+password_username_disabled=不允许非本地用户更改他们的用户名。更多详情请联系您的系统管理员。
+password_full_name_disabled=您无权更改他们的全名。请联系您的站点管理员了解更多详情。
 full_name=自定义名称
 website=个人网站
 location=所在地区
@@ -745,6 +766,7 @@ uploaded_avatar_not_a_image=上传的文件不是一张图片。
 uploaded_avatar_is_too_big=上传的文件大小(%d KiB) 超过最大限制(%d KiB)。
 update_avatar_success=您的头像已更新。
 update_user_avatar_success=用户头像已更新。
+cropper_prompt=您可以在保存前编辑图像。编辑的图像将被保存为 PNG 格式。
 
 change_password=更新密码
 old_password=当前密码
@@ -787,6 +809,7 @@ add_email_success=新的电子邮件地址已添加。
 email_preference_set_success=电子邮件首选项已成功设置。
 add_openid_success=新的 OpenID 地址已添加。
 keep_email_private=隐藏电子邮件地址
+keep_email_private_popup=这将会隐藏您的电子邮件地址,不仅在您的个人资料中,还在您使用Web界面创建合并请求或编辑文件时。已推送的提交将不会被修改。在提交中使用 %s 以和您的账号关联。
 openid_desc=OpenID 让你可以将认证转发到外部服务。
 
 manage_ssh_keys=管理 SSH 密钥
@@ -905,6 +928,7 @@ create_oauth2_application_success=您已成功创建了一个新的 OAuth2 应
 update_oauth2_application_success=您已成功更新了此 OAuth2 应用。
 oauth2_application_name=应用名称
 oauth2_confidential_client=机密客户端。是否是能够维持凭据机密性的应用,比如网页应用程序。如果是本地应用程序请不要勾选,包括桌面和移动端应用。
+oauth2_skip_secondary_authorization=首次授权后允许公共客户端跳过授权步骤。 可能会带来安全风险。
 oauth2_redirect_uris=重定向 URI。每行一个 URI。
 save_application=保存
 oauth2_client_id=客户端ID
@@ -915,6 +939,7 @@ oauth2_client_secret_hint=您离开或刷新此页面后将不会再显示此密
 oauth2_application_edit=编辑
 oauth2_application_create_description=OAuth2 应用允许您的第三方应用程序访问此实例的用户帐户。
 oauth2_application_remove_description=移除一个OAuth2应用将会阻止它访问此实例上的已授权用户账户。是否继续?
+oauth2_application_locked=如果配置启用,Gitea预注册一些OAuth2应用程序。 为了防止意外的行为, 这些应用既不能编辑也不能删除。请参阅OAuth2文档以获取更多信息。
 
 authorized_oauth2_applications=已授权的 OAuth2 应用
 authorized_oauth2_applications_description=您已授予这些第三方应用程序访问您的个人 Gitea 账户的权限。请撤销那些您不再需要的应用程序的访问权限。
@@ -923,20 +948,26 @@ revoke_oauth2_grant=撤回权限
 revoke_oauth2_grant_description=确定撤销此三方应用程序的授权,并阻止此应用程序访问您的数据?
 revoke_oauth2_grant_success=成功撤销了访问权限。
 
+twofa_desc=为保护你的账号密码安全,你可以使用智能手机或其它设备来接收时间强相关的一次性密码(TOTP)。
 twofa_recovery_tip=如果您丢失了您的设备,您将能够使用一次性恢复密钥来重新获得对您账户的访问。
 twofa_is_enrolled=你的账号已启用了两步验证。
 twofa_not_enrolled=你的账号未开启两步验证。
 twofa_disable=禁用两步认证
+twofa_scratch_token_regenerate=重新生成初始令牌
+twofa_scratch_token_regenerated=您的初始令牌现在是 %s。将其存放在安全的地方,它将不会再次显示。
 twofa_enroll=启用两步验证
 twofa_disable_note=如果需要, 可以禁用双因素身份验证。
 twofa_disable_desc=关掉两步验证会使得您的账号不安全,继续执行?
+regenerate_scratch_token_desc=如果您丢失了您的恢复密钥或已经使用它登录, 您可以在这里重置它。
 twofa_disabled=两步验证已被禁用。
 scan_this_image=使用您的授权应用扫描这张图片:
 or_enter_secret=或者输入密钥:%s
 then_enter_passcode=并输入应用程序中显示的密码:
 passcode_invalid=密码不正确。再试一次。
+twofa_enrolled=你的账号已经启用了两步验证。请保存初始令牌(%s)到一个安全的地方,此令牌仅显示一次。
 twofa_failed_get_secret=获取 secret 失败。
 
+webauthn_desc=安全密钥是包含加密密钥的硬件设备。它们可以用于双因素身份验证。安全密钥必须支持 WebAuthn 身份验证器 标准。
 webauthn_register_key=添加安全密钥
 webauthn_nickname=昵称
 webauthn_delete_key=移除安全密钥
@@ -1002,6 +1033,8 @@ fork_to_different_account=派生到其他账号
 fork_visibility_helper=无法更改派生仓库的可见性。
 fork_branch=要克隆到 Fork 的分支
 all_branches=所有分支
+view_all_branches=查看所有分支
+view_all_tags=查看所有标签
 fork_no_valid_owners=这个代码仓库无法被派生,因为没有有效的所有者。
 fork.blocked_user=无法克隆仓库,因为您被仓库所有者屏蔽。
 use_template=使用此模板
@@ -1013,6 +1046,8 @@ generate_repo=生成仓库
 generate_from=生成自
 repo_desc=仓库描述
 repo_desc_helper=输入简要描述 (可选)
+repo_no_desc=无详细信息
+repo_lang=编程语言
 repo_gitignore_helper=选择 .gitignore 模板。
 repo_gitignore_helper_desc=从常见语言的模板列表中选择忽略跟踪的文件。默认情况下,由开发或构建工具生成的特殊文件都包含在 .gitignore 中。
 issue_labels=工单标签
@@ -1074,6 +1109,7 @@ delete_preexisting_success=删除 %s 中未收录的文件
 blame_prior=查看此更改前的 blame
 blame.ignore_revs=忽略 .git-blame-ignore-revs 的修订。点击 绕过 并查看正常的 Blame 视图。
 blame.ignore_revs.failed=忽略 .git-blame-ignore-revs 版本失败。
+user_search_tooltip=最多显示30名用户
 
 tree_path_not_found_commit=路径%[1]s 在提交 %[2]s 中不存在
 tree_path_not_found_branch=路径 %[1]s 不存在于分支 %[2]s 中。
@@ -1222,6 +1258,7 @@ releases=版本发布
 tag=Git标签
 released_this=发布
 tagged_this=已标记
+file.title=%s 位于 %s
 file_raw=原始文件
 file_history=文件历史
 file_view_source=源码模式
@@ -1238,6 +1275,7 @@ ambiguous_runes_header=`此文件含有模棱两可的 Unicode 字符`
 ambiguous_runes_description=`此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。`
 invisible_runes_line=`此行含有不可见的 unicode 字符`
 ambiguous_runes_line=`此行有模棱两可的 unicode 字符`
+ambiguous_character=`%[1]c [U+%04[1]X] 容易和 %[2]c [U+%04[2]X] 混淆`
 
 escape_control_characters=Escape
 unescape_control_characters=Unescape
@@ -1429,6 +1467,7 @@ issues.new.clear_milestone=取消选中里程碑
 issues.new.assignees=指派成员
 issues.new.clear_assignees=取消指派成员
 issues.new.no_assignees=未指派成员
+issues.new.no_reviewers=无审核者
 issues.new.blocked_user=无法创建工单,因为您已被仓库所有者屏蔽。
 issues.edit.already_changed=无法保存对工单的更改。其内容似乎已被其他用户更改。 请刷新页面并重新编辑以避免覆盖他们的更改
 issues.edit.blocked_user=无法编辑内容,因为您已被仓库所有者或工单创建者屏蔽。
@@ -1457,6 +1496,7 @@ issues.remove_labels=于 %[2]s 删除了标签 %[1]s
 issues.add_remove_labels=于 %[3]s 添加了标签 %[1]s ,删除了标签 %[2]s
 issues.add_milestone_at=`于 %[2]s 添加了里程碑 %[1]s`
 issues.add_project_at=`于 %[2]s 将此添加到 %[1]s 项目`
+issues.move_to_column_of_project=`将此对象移至 %s 的 %s 中在 %s 上`
 issues.change_milestone_at=`%[3]s 修改了里程碑从 %[1]s%[2]s`
 issues.change_project_at=于 %[3]s 将此从项目 %[1]s 移到 %[2]s
 issues.remove_milestone_at=`%[2]s 删除了里程碑 %[1]s`
@@ -1488,6 +1528,8 @@ issues.filter_assignee=指派人筛选
 issues.filter_assginee_no_select=所有指派成员
 issues.filter_assginee_no_assignee=未指派
 issues.filter_poster=作者
+issues.filter_user_placeholder=搜索用户
+issues.filter_user_no_select=所有用户
 issues.filter_type=类型筛选
 issues.filter_type.all_issues=所有工单
 issues.filter_type.assigned_to_you=指派给您的
@@ -1541,7 +1583,9 @@ issues.no_content=没有提供说明。
 issues.close=关闭工单
 issues.comment_pull_merged_at=已合并提交 %[1]s 到 %[2]s %[3]s
 issues.comment_manually_pull_merged_at=手动合并提交 %[1]s 到 %[2]s %[3]s
+issues.close_comment_issue=评论并关闭
 issues.reopen_issue=重新开启
+issues.reopen_comment_issue=评论并重新开启
 issues.create_comment=评论
 issues.comment.blocked_user=无法创建或编辑评论,因为您已被仓库所有者或工单创建者屏蔽。
 issues.closed_at=`于 %[2]s 关闭此工单`
@@ -1630,12 +1674,25 @@ issues.delete.title=是否删除工单?
 issues.delete.text=您真的要删除这个工单吗?(该操作将会永久删除所有内容。如果您需要保留,请关闭它)
 
 issues.tracker=时间跟踪
+issues.timetracker_timer_start=启动计时器
+issues.timetracker_timer_stop=停止计时器
+issues.timetracker_timer_discard=删除计时器
+issues.timetracker_timer_manually_add=添加时间
 
+issues.time_estimate_set=设置预计时间
+issues.time_estimate_display=预计: %s
+issues.change_time_estimate_at=将预计时间修改为 %s %s
+issues.remove_time_estimate_at=删除预计时间 %s
+issues.time_estimate_invalid=预计时间格式无效
+issues.start_tracking_history=`开始工作 %s`
 issues.tracker_auto_close=当此工单关闭时,自动停止计时器
 issues.tracking_already_started=`你已经开始对 另一个工单 进行时间跟踪!`
+issues.stop_tracking_history=`停止工作 %s`
 issues.cancel_tracking_history=`取消时间跟踪 %s`
 issues.del_time=删除此时间跟踪日志
+issues.add_time_history=`添加计时 %s`
 issues.del_time_history=`已删除时间 %s`
+issues.add_time_manually=手动添加时间
 issues.add_time_hours=小时
 issues.add_time_minutes=分钟
 issues.add_time_sum_to_small=没有输入时间。
@@ -1695,6 +1752,7 @@ issues.dependency.add_error_dep_not_same_repo=这两个工单必须在同一仓
 issues.review.self.approval=您不能批准您自己的合并请求。
 issues.review.self.rejection=您不能请求对您自己的合并请求进行更改。
 issues.review.approve=于 %s 批准此合并请求
+issues.review.comment=评审于 %s
 issues.review.dismissed=于 %[2]s 取消了 %[1]s 的评审
 issues.review.dismissed_label=已取消
 issues.review.left_comment=留下了一条评论
@@ -1720,6 +1778,10 @@ issues.review.resolve_conversation=已解决问题
 issues.review.un_resolve_conversation=未解决问题
 issues.review.resolved_by=标记问题为已解决
 issues.review.commented=评论
+issues.review.official=已批准
+issues.review.requested=等待审核
+issues.review.rejected=请求变更
+issues.review.stale=批准后已更新
 issues.review.unofficial=非官方审批数
 issues.assignee.error=因为未知原因,并非所有的指派都成功。
 issues.reference_issue.body=内容
@@ -1837,7 +1899,9 @@ pulls.unrelated_histories=合并失败:两个分支没有共同历史。提示
 pulls.merge_out_of_date=合并失败:在生成合并时,主分支已更新。提示:再试一次。
 pulls.head_out_of_date=合并失败:在生成合并时,head 已更新。提示:再试一次。
 pulls.has_merged=失败:合并请求已经被合并,您不能再次合并或更改目标分支。
+pulls.push_rejected=推送失败:推送被拒绝。审查此仓库的 Git 钩子。
 pulls.push_rejected_summary=详细拒绝信息
+pulls.push_rejected_no_message=推送失败:此推送被拒绝但未提供其他信息。请检查此仓库的 Git 钩子。
 pulls.open_unmerged_pull_exists=`您不能执行重新打开操作, 因为已经存在相同的合并请求 (#%d)。`
 pulls.status_checking=一些检测仍在等待运行
 pulls.status_checks_success=所有检测均成功
@@ -1881,6 +1945,10 @@ pulls.delete.title=删除此合并请求?
 pulls.delete.text=你真的要删除这个合并请求吗? (这将永久删除所有内容。如果你打算将内容存档,请考虑关闭它)
 
 pulls.recently_pushed_new_branches=您已经于%[2]s推送了分支 %[1]s
+pulls.upstream_diverging_prompt_behind_1=该分支落后于 %s %d 个提交
+pulls.upstream_diverging_prompt_behind_n=该分支落后于 %s %d 个提交
+pulls.upstream_diverging_prompt_base_newer=基础分支 %s 有新的更改
+pulls.upstream_diverging_merge=同步派生
 
 pull.deleted_branch=(已删除): %s
 pull.agit_documentation=查看有关 AGit 的文档
@@ -1894,6 +1962,7 @@ milestones.no_due_date=暂无截止日期
 milestones.open=开启
 milestones.close=关闭
 milestones.new_subheader=里程碑可以帮助您组织工单并跟踪其进度。
+milestones.completeness=%d%% 已完成
 milestones.create=创建里程碑
 milestones.title=标题
 milestones.desc=描述
@@ -2116,6 +2185,7 @@ settings.pulls.default_delete_branch_after_merge=默认合并后删除合并请
 settings.pulls.default_allow_edits_from_maintainers=默认开启允许维护者编辑
 settings.releases_desc=启用发布
 settings.packages_desc=启用仓库软件包注册中心
+settings.projects_desc=启用项目
 settings.projects_mode_desc=项目模式 (要显示的项目类型)
 settings.projects_mode_repo=仅仓库项目
 settings.projects_mode_owner=仅限用户或组织项目
@@ -2155,6 +2225,7 @@ settings.transfer_in_progress=当前正在进行转让。 如果你想将此代
 settings.transfer_notices_1=- 如果将此仓库转移给其他用户, 您将失去对此仓库的访问权限。
 settings.transfer_notices_2=-如果将其转移到您 (共同) 拥有的组织,您可以继续访问该仓库。
 settings.transfer_notices_3=- 如果仓库是私有的并且被转移给某个用户,那么此操作可以确保该用户至少具有读权限(以及必要时的更改权限)。
+settings.transfer_notices_4=- 如果存储库属于某个组织,而您将其转移给另一个组织或个人,那么您将失去存储库工单与其组织项目系统之间的链接。
 settings.transfer_owner=新拥有者
 settings.transfer_perform=执行转让
 settings.transfer_started=该代码库已被标记为转让并等待来自 %s 的确认
@@ -2291,6 +2362,7 @@ settings.event_pull_request_merge=合并请求合并
 settings.event_package=软件包
 settings.event_package_desc=软件包已在仓库中被创建或删除。
 settings.branch_filter=分支过滤
+settings.branch_filter_desc=推送、创建,删除分支事件的分支白名单,使用 glob 模式匹配指定。若为空或 *,则将报告所有分支的事件。语法文档见 %[2]s。示例:master,{master,release*}。
 settings.authorization_header=授权标头
 settings.authorization_header_desc=当存在时将被作为授权标头包含在内。例如: %s。
 settings.active=激活
@@ -2336,25 +2408,53 @@ settings.deploy_key_deletion=删除部署密钥
 settings.deploy_key_deletion_desc=删除部署密钥将取消此密钥对此仓库的访问权限。继续?
 settings.deploy_key_deletion_success=部署密钥已删除。
 settings.branches=分支
+settings.protected_branch=分支保护
 settings.protected_branch.save_rule=保存规则
 settings.protected_branch.delete_rule=删除规则
+settings.protected_branch_can_push=是否允许推送?
 settings.protected_branch_can_push_yes=你可以推
+settings.protected_branch_can_push_no=你不能推送
+settings.branch_protection=分支 '%s' 的保护规则
 settings.protect_this_branch=启用分支保护
 settings.protect_this_branch_desc=阻止删除并限制Git推送和合并到分支。
 settings.protect_disable_push=禁用推送
 settings.protect_disable_push_desc=此分支不允许推送。
+settings.protect_disable_force_push=禁用强制推送
+settings.protect_disable_force_push_desc=此分支禁止强制推送。
 settings.protect_enable_push=启用推送
 settings.protect_enable_push_desc=任何拥有写访问权限的人将被允许推送到此分支(但不能强行推送)。
+settings.protect_enable_force_push_all=启用强制推送
+settings.protect_enable_force_push_all_desc=任何拥有推送权限的人都可以对这个分支进行强制推送。
+settings.protect_enable_force_push_allowlist=强制推送白名单
+settings.protect_enable_force_push_allowlist_desc=只有白名单中的用户或团队才能强制推送到这个分支。
 settings.protect_enable_merge=启用合并
 settings.protect_enable_merge_desc=任何具有写入权限的人都可以将合并请求合并到此分支中。
+settings.protect_whitelist_committers=受白名单限制的推送
+settings.protect_whitelist_committers_desc=只有列入白名单的用户或团队才能被允许推送到此分支(但不能强行推送)。
+settings.protect_whitelist_deploy_keys=具有推送权限的部署密钥白名单。
+settings.protect_whitelist_users=推送白名单用户:
+settings.protect_whitelist_teams=推送白名单团队:
+settings.protect_force_push_allowlist_users=允许强制推送的白名单用户:
+settings.protect_force_push_allowlist_teams=允许强制推送的白名单团队:
+settings.protect_force_push_allowlist_deploy_keys=允许白名单中的部署密钥进行强制推送。
+settings.protect_merge_whitelist_committers=启用合并白名单
+settings.protect_merge_whitelist_committers_desc=仅允许白名单用户或团队合并合并请求到此分支。
+settings.protect_merge_whitelist_users=合并白名单用户:
+settings.protect_merge_whitelist_teams=合并白名单团队:
 settings.protect_check_status_contexts=启用状态检查
 settings.protect_status_check_patterns=状态检查模式:
 settings.protect_status_check_patterns_desc=输入模式,指定哪些状态检查必须通过,才能将分支合并到符合此规则的分支中去。每一行指定一个模式,模式不能为空。
+settings.protect_check_status_contexts_desc=要求状态检查通过才能合并。如果启用,提交必须先推送到另一个分支,然后再合并或推送到匹配这些保护规则的分支。如果没有选择具体的状态检查上下文,则所有的状态检查都通过才能合并。
 settings.protect_check_status_contexts_list=此仓库上周进行过的状态检查
 settings.protect_status_check_matched=匹配
 settings.protect_invalid_status_check_pattern=无效的状态检查规则:“%s”。
 settings.protect_no_valid_status_check_patterns=没有有效的状态检查规则。
 settings.protect_required_approvals=所需的批准:
+settings.protect_required_approvals_desc=只允许合并有足够审核的合并请求。要求的审核必须来自白名单或者有权限的用户或团队。
+settings.protect_approvals_whitelist_enabled=仅列入白名单的用户或团队才可批准
+settings.protect_approvals_whitelist_enabled_desc=只有白名单用户或团队的审核才能计数。 如果规则没有批准白名单,来自任何有写访问权限的人的审核都将计数。
+settings.protect_approvals_whitelist_users=审查者白名单:
+settings.protect_approvals_whitelist_teams=审查团队白名单:
 settings.dismiss_stale_approvals=取消过时的批准
 settings.dismiss_stale_approvals_desc=当新的提交更改合并请求内容被推送到分支时,旧的批准将被撤销。
 settings.ignore_stale_approvals=忽略过期批准
@@ -2362,12 +2462,18 @@ settings.ignore_stale_approvals_desc=对旧提交(过期审核)的批准将
 settings.require_signed_commits=需要签名提交
 settings.require_signed_commits_desc=拒绝推送未签名或无法验证的提交到分支
 settings.protect_branch_name_pattern=受保护的分支名称模式
+settings.protect_branch_name_pattern_desc=分支保护的名称匹配规则。语法请参阅 文档 。如:main, release/**
 settings.protect_patterns=规则
 settings.protect_protected_file_patterns=受保护的文件模式(使用分号 ';' 分隔):
+settings.protect_protected_file_patterns_desc=即使用户有权添加、编辑或删除此分支中的文件,也不允许直接更改受保护的文件。 可以使用分号 (';') 分隔多个模式。 见%[2]s文档了解模式语法。例如: .drone.yml, /docs/**/*.txt
 settings.protect_unprotected_file_patterns=不受保护的文件模式(使用分号 ';' 分隔):
+settings.protect_unprotected_file_patterns_desc=如果用户有写权限,则允许直接更改的不受保护的文件,以绕过推送限制。可以使用分号分隔多个模式 (';')。 见 %[2]s 文档了解模式语法。例如: .drone.yml, /docs/**/*.txt
+settings.add_protected_branch=启用保护
+settings.delete_protected_branch=禁用保护
 settings.update_protect_branch_success=分支保护规则 %s 更新成功。
 settings.remove_protected_branch_success=移除分支保护规则"%s"成功。
 settings.remove_protected_branch_failed=移除分支保护规则"%s"失败。
+settings.protected_branch_deletion=删除分支保护
 settings.protected_branch_deletion_desc=禁用分支保护允许具有写入权限的用户推送提交到此分支。继续?
 settings.block_rejected_reviews=拒绝审核阻止了此合并
 settings.block_rejected_reviews_desc=如果官方审查人员要求作出改动,即使有足够的批准,合并也不允许。
@@ -2375,8 +2481,11 @@ settings.block_on_official_review_requests=有官方审核阻止了代码合并
 settings.block_on_official_review_requests_desc=处于评审状态时,即使有足够的批准,也不能合并。
 settings.block_outdated_branch=如果合并请求已经过时,阻止合并
 settings.block_outdated_branch_desc=当头部分支落后基础分支时,不能合并。
+settings.block_admin_merge_override=管理员须遵守分支保护规则
+settings.block_admin_merge_override_desc=管理员须遵守分支保护规则,不能规避该规则。
 settings.default_branch_desc=请选择一个默认的分支用于合并请求和提交:
 settings.merge_style_desc=合并方式
+settings.default_merge_style_desc=默认合并风格
 settings.choose_branch=选择一个分支...
 settings.no_protected_branch=没有受保护的分支
 settings.edit_protected_branch=编辑
@@ -2392,12 +2501,25 @@ settings.tags.protection.allowed.teams=允许的团队
 settings.tags.protection.allowed.noone=无
 settings.tags.protection.create=保护Git标签
 settings.tags.protection.none=没有受保护的Git标签
+settings.tags.protection.pattern.description=你可以使用单个名称或 glob 模式匹配或正则表达式来匹配多个标签。了解详情请访问 保护Git标签指南。
 settings.bot_token=Bot 令牌
 settings.chat_id=聊天 ID
 settings.thread_id=线程 ID
 settings.matrix.homeserver_url=主服务器网址
 settings.matrix.room_id=房间ID
 settings.matrix.message_type=消息类型
+settings.visibility.private.button=设为私有
+settings.visibility.private.text=将可见性更改为私有不仅会使仓库仅对允许的成员可见,而且可能会消除它与派生仓库、关注者和点赞之间的关系。
+settings.visibility.private.bullet_title=将可见性改为私有将会:
+settings.visibility.private.bullet_one=使仓库只对允许的成员可见。
+settings.visibility.private.bullet_two=可能会删除它与 派生仓库关注者点赞 之间的关系。
+settings.visibility.public.button=设为公开
+settings.visibility.public.text=将可见性更改为公开会使任何人都可见。
+settings.visibility.public.bullet_title=将可见性改为公开将会:
+settings.visibility.public.bullet_one=使仓库让任何人都可见。
+settings.visibility.success=仓库可见性已更改。
+settings.visibility.error=试图更改仓库可见性时出错。
+settings.visibility.fork_error=无法更改派生仓库的可见性。
 settings.archive.button=归档仓库
 settings.archive.header=归档此仓库
 settings.archive.text=归档仓库将使其完全只读。它将在首页隐藏。没有人(甚至你!)能够进行新的提交,或打开工单及合并请求。
@@ -2509,6 +2631,7 @@ release.new_release=发布新版
 release.draft=草稿
 release.prerelease=预发行
 release.stable=稳定
+release.latest=最新版本
 release.compare=比较
 release.edit=编辑
 release.ahead.commits=%d 次提交
@@ -2593,6 +2716,7 @@ tag.create_success=标签"%s"已存在
 
 topic.manage_topics=管理主题
 topic.done=保存
+topic.count_prompt=您最多选择25个主题
 topic.format_prompt=主题必须以字母或数字开头,可以包含连字符 ('-') 和句点 ('.'),长度不得超过35个字符。字符必须为小写。
 
 find_file.go_to_file=转到文件
@@ -2665,6 +2789,7 @@ settings.delete_prompt=删除操作会永久清除该组织的信息,并且 所有仓库。
 
 settings.labels_desc=添加能够被该组织下的 所有仓库 的工单使用的标签。
 
@@ -2689,6 +2814,7 @@ teams.leave.detail=离开 %s?
 teams.can_create_org_repo=创建仓库
 teams.can_create_org_repo_helper=成员可以在组织中创建仓库。创建者将自动获得创建的仓库的管理员权限。
 teams.none_access=无访问权限
+teams.none_access_helper=成员无法查看此单元或对其执行任何其他操作。对公开仓库无此影响。
 teams.general_access=常规访问
 teams.general_access_helper=成员权限将由以下权限表决定。
 teams.read_access=可读
@@ -2757,6 +2883,7 @@ last_page=末页
 total=总计:%d
 settings=管理设置
 
+dashboard.new_version_hint=Gitea %s 现已可用,您正在运行 %s。查看 博客 了解详情。
 dashboard.statistic=摘要
 dashboard.maintenance_operations=运维
 dashboard.system_status=系统状态
@@ -2799,6 +2926,7 @@ dashboard.reinit_missing_repos=重新初始化所有丢失的 Git 仓库存在
 dashboard.sync_external_users=同步外部用户数据
 dashboard.cleanup_hook_task_table=清理 hook_task 表
 dashboard.cleanup_packages=清理过期的软件包
+dashboard.cleanup_actions=清理过期的 Actions 资源
 dashboard.server_uptime=服务运行时间
 dashboard.current_goroutine=当前 Goroutines 数量
 dashboard.current_memory_usage=当前内存使用量
@@ -2828,12 +2956,19 @@ dashboard.total_gc_time=GC 暂停时间总量
 dashboard.total_gc_pause=GC 暂停时间总量
 dashboard.last_gc_pause=上次 GC 暂停时间
 dashboard.gc_times=GC 执行次数
+dashboard.delete_old_actions=从数据库中删除所有旧操作记录
+dashboard.delete_old_actions.started=已开始从数据库中删除所有旧操作记录。
 dashboard.update_checker=更新检查器
 dashboard.delete_old_system_notices=从数据库中删除所有旧系统通知
 dashboard.gc_lfs=垃圾回收 LFS 元数据
+dashboard.stop_zombie_tasks=停止僵尸任务
+dashboard.stop_endless_tasks=停止无法停止的任务
+dashboard.cancel_abandoned_jobs=取消丢弃的任务
+dashboard.start_schedule_tasks=开始Actions调度任务
 dashboard.sync_branch.started=分支同步已开始
 dashboard.sync_tag.started=标签同步已开始
 dashboard.rebuild_issue_indexer=重建工单索引
+dashboard.sync_repo_licenses=重新仓库许可证探测
 
 users.user_manage_panel=用户帐户管理
 users.new_account=创建新帐户
@@ -2905,6 +3040,10 @@ emails.not_updated=无法更新请求的电子邮件地址: %v
 emails.duplicate_active=此电子邮件地址已被另一个用户激活使用。
 emails.change_email_header=更新电子邮件属性
 emails.change_email_text=您确定要更新该电子邮件地址吗?
+emails.delete=删除电子邮件
+emails.delete_desc=您确定要删除该电子邮件地址?
+emails.deletion_success=电子邮件地址已被删除。
+emails.delete_primary_email_error=您不能删除主电子邮件。
 
 orgs.org_manage_panel=组织管理
 orgs.name=名称
@@ -2937,10 +3076,12 @@ packages.size=大小
 packages.published=已发布
 
 defaulthooks=默认Web钩子
+defaulthooks.desc=当某些 Gitea 事件触发时,Web 钩子自动向服务器发出 HTTP POST 请求。这里定义的 Web 钩子是默认配置,将被复制到所有新的仓库中。详情请访问 Web 钩子指南。
 defaulthooks.add_webhook=添加默认Web 钩子
 defaulthooks.update_webhook=更新默认 Web 钩子
 
 systemhooks=系统 Web 钩子
+systemhooks.desc=当某些 Gitea 事件触发时,Web 钩子自动向服务器发出HTTP POST请求。这里定义的 Web 钩子将作用于系统上的所有仓库,所以请考虑这可能带来的任何性能影响。了解详情请访问 Web 钩子指南。
 systemhooks.add_webhook=添加系统 Web 钩子
 systemhooks.update_webhook=更新系统 Web 钩子
 
@@ -3035,8 +3176,18 @@ auths.tips=帮助提示
 auths.tips.oauth2.general=OAuth2 认证
 auths.tips.oauth2.general.tip=当注册新的 OAuth2 身份验证时,回调/重定向 URL 应该是:
 auths.tip.oauth2_provider=OAuth2 提供程序
+auths.tip.bitbucket=在 %s 注册新的 OAuth 使用者同时添加权限“账号”-“读取”
 auths.tip.nextcloud=使用下面的菜单“设置(Settings) -> 安全(Security) -> OAuth 2.0 client”在您的实例上注册一个新的 OAuth 客户端。
+auths.tip.dropbox=在 %s 上创建一个新的应用程序
+auths.tip.facebook=`在 %s 注册一个新的应用,并添加产品"Facebook 登录"`
+auths.tip.github=在 %s 注册一个 OAuth 应用程序
+auths.tip.gitlab_new=在 %s 注册一个新的应用
+auths.tip.google_plus=从谷歌 API 控制台 %s 获得 OAuth2 客户端凭据
 auths.tip.openid_connect=使用 OpenID 连接发现 URL ({server}/.well-known/openid-configuration) 来指定终点
+auths.tip.twitter=访问 %s,创建应用并确保启用了"允许此应用程序用于登录 Twitter"的选项。
+auths.tip.discord=在 %s 上注册新应用程序
+auths.tip.gitea=注册一个新的 OAuth2 应用程序。可以访问 %s 查看帮助
+auths.tip.yandex=在 %s 上创建一个新的应用程序。在“ Yandex.Passport API”这部分中选择以下权限:“访问电子邮件地址(Access to email address)”,“访问用户头像(Access to user avatar)”和“访问用户名,名字和姓氏,性别(Access to username, first name and surname, genderAccess to username, first name and surname, gender)”
 auths.tip.mastodon=输入您想要认证的 mastodon 实例的自定义 URL (或使用默认值)
 auths.edit=修改认证源
 auths.activated=该认证源已经启用
@@ -3153,6 +3304,9 @@ config.cache_interval=Cache 周期
 config.cache_conn=Cache 连接字符串
 config.cache_item_ttl=缓存项目 TTL
 config.cache_test=测试缓存
+config.cache_test_failed=缓存测试失败: %v。
+config.cache_test_slow=缓存测试成功,但响应缓慢: %s。
+config.cache_test_succeeded=缓存测试成功,在 %s 时间内得到响应。
 
 config.session_config=Session 配置
 config.session_provider=Session 提供者
@@ -3199,6 +3353,7 @@ monitor.next=下次执行时间
 monitor.previous=上次执行时间
 monitor.execute_times=执行次数
 monitor.process=运行中进程
+monitor.stacktrace=调用栈踪迹
 monitor.processes_count=%d 个进程
 monitor.download_diagnosis_report=下载诊断报告
 monitor.desc=进程描述
@@ -3206,6 +3361,8 @@ monitor.start=开始时间
 monitor.execute_time=执行时长
 monitor.last_execution_result=结果
 monitor.process.cancel=中止进程
+monitor.process.cancel_desc=中止一个进程可能导致数据丢失
+monitor.process.cancel_notices=中止:%s ?
 monitor.process.children=子进程
 
 monitor.queues=队列
@@ -3307,6 +3464,8 @@ raw_minutes=分钟
 
 [dropzone]
 default_message=拖动文件或者点击此处上传。
+invalid_input_type=您不能上传该类型的文件
+file_too_big=文件体积({{filesize}} MB)超过了最大允许体积({{maxFilesize}} MB)
 remove_file=移除文件
 
 [notification]
@@ -3378,6 +3537,8 @@ alpine.repository=仓库信息
 alpine.repository.branches=分支
 alpine.repository.repositories=仓库管理
 alpine.repository.architectures=架构
+arch.registry=添加具有相关仓库和架构的服务器到 /etc/pacman.conf 中:
+arch.install=使用 pacman 同步软件包:
 arch.repository=仓库信息
 arch.repository.repositories=仓库管理
 arch.repository.architectures=架构
@@ -3456,6 +3617,7 @@ settings.link=将此软件包链接到仓库
 settings.link.description=如果您将一个软件包与一个代码库链接起来,软件包将显示在代码库的软件包列表中。
 settings.link.select=选择仓库
 settings.link.button=更新仓库链接
+settings.link.success=仓库链接已成功更新。
 settings.link.error=更新仓库链接失败。
 settings.delete=删除软件包
 settings.delete.description=删除软件包是永久性的,无法撤消。
@@ -3579,12 +3741,18 @@ runs.no_workflows.quick_start=不知道如何使用 Gitea Actions吗?请查看
 runs.no_workflows.documentation=关于Gitea Actions的更多信息,请参阅 文档。
 runs.no_runs=工作流尚未运行过。
 runs.empty_commit_message=(空白的提交消息)
+runs.expire_log_message=旧的日志已被清除
 
 workflow.disable=禁用工作流
 workflow.disable_success=工作流 '%s' 已成功禁用。
 workflow.enable=启用工作流
 workflow.enable_success=工作流 '%s' 已成功启用。
 workflow.disabled=工作流已禁用。
+workflow.run=运行工作流
+workflow.not_found=工作流 %s 未找到。
+workflow.run_success=工作流 %s 已成功运行。
+workflow.from_ref=使用工作流从
+workflow.has_workflow_dispatch=此 Workflow 有一个 Workflow_dispatch 事件触发器。
 
 need_approval_desc=该工作流由派生仓库的合并请求所触发,需要批准方可运行。
 
@@ -3605,6 +3773,7 @@ variables.update.failed=编辑变量失败。
 variables.update.success=该变量已被编辑。
 
 [projects]
+deleted.display_name=已删除项目
 type-1.display_name=个人项目
 type-2.display_name=仓库项目
 type-3.display_name=组织项目
diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini
index 3b1d37a322a..fb93a2ff076 100644
--- a/options/locale/locale_zh-TW.ini
+++ b/options/locale/locale_zh-TW.ini
@@ -4,6 +4,7 @@ explore=探索
 help=說明
 logo=標誌
 sign_in=登入
+sign_in_with_provider=使用 %s 帳戶登入
 sign_in_or=或
 sign_out=登出
 sign_up=註冊
@@ -16,6 +17,7 @@ template=模板
 language=語言
 notifications=通知
 active_stopwatch=進行中的時間追蹤
+tracked_time_summary=目前的 issue 累計時長
 create_new=建立...
 user_profile_and_more=個人資料和設定...
 signed_in_as=已登入
@@ -23,6 +25,7 @@ enable_javascript=本網站需要 JavaScript。
 toc=目錄
 licenses=授權條款
 return_to_gitea=返回 Gitea
+more_items=更多項目
 
 username=帳號
 email=電子信箱
@@ -80,6 +83,8 @@ milestones=里程碑
 ok=確認
 cancel=取消
 retry=重試
+rerun=重新執行
+rerun_all=重新執行所有工作
 save=儲存
 add=增加
 add_all=全部增加
@@ -87,14 +92,19 @@ remove=移除
 remove_all=全部移除
 remove_label_str=移除項目「%s」
 edit=編輯
+view=檢視
+test=測試
 
 enabled=已啟用
 disabled=已停用
+locked=已上鎖
 
 copy=複製
 copy_url=複製 URL
+copy_hash=複製哈希值
 copy_content=複製內容
 copy_branch=複製分支名稱
+copy_path=複製路徑
 copy_success=複製成功!
 copy_error=複製失敗
 copy_type_unsupported=無法複製此類型的檔案
@@ -105,6 +115,8 @@ loading=載入中…
 
 error=錯誤
 error404=您正嘗試訪問的頁面 不存在您尚未被授權 查看該頁面。
+go_back=返回
+invalid_data=無效的資料: %v
 
 never=從來沒有
 unknown=未知
@@ -114,25 +126,67 @@ rss_feed=RSS 摘要
 pin=固定
 unpin=取消固定
 
+artifacts=檔案或工件
+confirm_delete_artifact=你確定要刪除這個檔案 '%s' 嗎?
 
 archived=已封存
 
+concept_system_global=全域
+concept_user_individual=個人
 concept_code_repository=儲存庫
 concept_user_organization=組織
 
+show_timestamps=顯示時間戳記
+show_log_seconds=顯示秒數
+show_full_screen=全螢幕顯示
+download_logs=下載記錄
 
+confirm_delete_selected=確定要刪除所有已選取的項目嗎?
 
 name=名稱
 value=值
 
 filter=篩選
+filter.clear=清除篩選器
 filter.is_archived=已封存
+filter.not_archived=未歸檔
+filter.is_fork=已分支
+filter.not_fork=未分支
+filter.is_mirror=已鏡像
+filter.not_mirror=不是鏡像
 filter.is_template=模板
+filter.not_template=不是範本
 filter.public=公開
 filter.private=私有
 
+no_results_found=找不到結果。
+internal_error_skipped=已略過內部錯誤:%s
 
 [search]
+search=搜尋…
+type_tooltip=搜尋類型
+fuzzy=模糊
+fuzzy_tooltip=包含與搜尋詞接近的結果
+exact=精確
+exact_tooltip=只包含完全符合關鍵字的結果
+repo_kind=搜尋儲存庫…
+user_kind=搜尋使用者…
+org_kind=搜尋組織…
+team_kind=搜尋團隊…
+code_kind=搜尋代碼…
+code_search_unavailable=現在無法使用原始碼搜尋。請與網站管理員聯絡。
+code_search_by_git_grep=目前的原始碼搜尋結果是由「git grep」提供。如果網站管理者啟用 Repository Indexer 可能可以提供更好的結果。
+package_kind=搜尋軟體包...
+project_kind=搜尋專案…
+branch_kind=搜尋分支…
+tag_kind=搜尋標籤…
+tag_tooltip=搜尋符合的標籤。使用「%」以比對任意長度的數字。
+commit_kind=搜尋提交歷史…
+runner_kind=搜尋 Runner...
+no_results=找不到符合的結果。
+issue_kind=搜尋議題…
+pull_kind=搜尋合併請求...
+keyword_search_unavailable=現在無法使用關鍵字搜尋。請與網站管理員聯絡。
 
 [aria]
 navbar=導航列
@@ -156,9 +210,13 @@ buttons.link.tooltip=新增連結
 buttons.list.unordered.tooltip=新增項目符號清單
 buttons.list.ordered.tooltip=新增編號清單
 buttons.list.task.tooltip=新增工作項目清單
+buttons.table.add.tooltip=新增表格
 buttons.table.add.insert=增加
+buttons.table.rows=行
+buttons.table.cols=列
 buttons.mention.tooltip=提及使用者或團隊
 buttons.ref.tooltip=參考問題或合併請求
+buttons.switch_to_legacy.tooltip=使用舊版編輯器代替
 buttons.enable_monospace_font=啟用等寬字型
 buttons.disable_monospace_font=停用等寬字型
 
@@ -168,16 +226,20 @@ string.desc=Z - A
 
 [error]
 occurred=發生錯誤
+report_message=如果你確定這是一個 Gitea 的 bug,請去  GitHub 搜尋相關的問題,如果有需要你也可以開一個新的問題
 not_found=找不到目標。
 network_error=網路錯誤
 
 [startpage]
 app_desc=一套極易架設的 Git 服務
 install=安裝容易
+install_desc=直接用 執行檔安裝,還可以透過 Docker部屬,或是取得 套件。
 platform=跨平台
+platform_desc=Gitea 可以在所有能編譯 Go 語言的平台上執行: Windows, macOS, Linux, ARM 等等。挑一個您喜歡的吧!
 lightweight=輕量級
 lightweight_desc=一片便宜的 Raspberry Pi 就可以滿足 Gitea 的最低需求。節省您的機器資源!
 license=開放原始碼
+license_desc=取得 code.gitea.io/gitea !成為一名貢獻者和我們一起讓 Gitea 更好,快點加入我們吧!
 
 [install]
 install=安裝頁面
@@ -216,6 +278,7 @@ repo_path_helper=所有遠端 Git 儲存庫會儲存到此目錄。
 lfs_path=Git LFS 根目錄
 lfs_path_helper=以 Git LFS 儲存檔案時會被儲存在此目錄中。請留空以停用 LFS 功能。
 run_user=以使用者名稱執行
+run_user_helper=輸入 Gitea 執行的作業系統使用者名稱。請注意,此使用者必須擁有存儲庫根目錄的存取權限。
 domain=伺服器域名
 domain_helper=伺服器的域名或主機位置。
 ssh_port=SSH 伺服器埠
@@ -232,6 +295,7 @@ email_title=電子郵件設定
 smtp_addr=SMTP 主機
 smtp_port=SMTP 連接埠
 smtp_from=電子郵件寄件者
+smtp_from_invalid=「以此電子信箱寄送」的地址無效
 smtp_from_helper=Gitea 將會使用的電子信箱,直接輸入電子信箱或使用「"名稱" 」的格式。
 mailer_user=SMTP 帳號
 mailer_password=SMTP 密碼
@@ -287,8 +351,12 @@ invalid_password_algorithm=無效的密碼雜湊演算法
 password_algorithm_helper=設定密碼雜湊演算法。演算法有不同的需求與強度。argon2 演算法雖然較安全但會使用大量記憶體,可能不適用於小型系統。
 enable_update_checker=啟用更新檢查器
 enable_update_checker_helper=定期連線到 gitea.io 檢查更新。
+env_config_keys=環境組態設定
+env_config_keys_prompt=下列環境變數也會套用到您的組態檔:
+config_write_file_prompt=這些配置選項將被寫入到: %s
 
 [home]
+nav_menu=導覽選單
 uname_holder=帳號或電子信箱
 password_holder=密碼
 switch_dashboard_context=切換資訊主頁帳戶
@@ -318,6 +386,7 @@ issues.in_your_repos=在您的儲存庫中
 repos=儲存庫
 users=使用者
 organizations=組織
+go_to=前往
 code=程式碼
 code_last_indexed_at=最後索引 %s
 relevant_repositories_tooltip=已隱藏缺少主題、圖示、說明、Fork 的儲存庫。
@@ -325,27 +394,38 @@ relevant_repositories=只顯示相關的儲存庫,顯示未篩選
 
 [auth]
 create_new_account=註冊帳戶
+already_have_account=已經有帳號嗎?
+sign_in_now=立即登入!
 disable_register_prompt=註冊功能已停用。 請聯繫您的網站管理員。
 disable_register_mail=已停用註冊確認電子郵件。
 manual_activation_only=請聯絡您的網站管理員以完成啟用程序。
 remember_me=記得這個裝置
+remember_me.compromised=這個登入 token 已經失效,可能代表著帳號已被入侵。請確認您的帳號是否有不尋常的活動。
 forgot_password_title=忘記密碼
 forgot_password=忘記密碼?
+need_account=需要一個帳號?
+sign_up_now=還沒有帳戶?馬上註冊。
+sign_up_successful=帳戶已成功建立。歡迎您!
+confirmation_mail_sent_prompt_ex=新的確認信已寄到%s。請在接下來的 %s 確認您的收件夾來完成註冊手續。如果您的註冊地址有錯誤,您可以再次登入並修改它。
 must_change_password=更新您的密碼
 allow_password_change=要求使用者更改密碼 (推薦)
 reset_password_mail_sent_prompt=確認信已發送至 %s。請在 %s內檢查您的收件匣並完成帳戶救援作業。
 active_your_account=啟用您的帳戶
 account_activated=帳戶已啟用
 prohibit_login=禁止登入
+prohibit_login_desc=您的帳戶被禁止登入,請聯絡網站管理員
 resent_limit_prompt=抱歉,您請求發送驗證電子郵件太過頻繁,請等待 3 分鐘後再試一次。
 has_unconfirmed_mail=%s 您好,您有一封發送至( %s) 但未被確認的郵件。如果您未收到啟用郵件,或需要重新發送,請單擊下方的按鈕。
+change_unconfirmed_mail_address=如果您註冊的電子郵件地址有錯誤,您可以在這邊更正,並重新寄送確認郵件。
 resend_mail=單擊此處重新發送確認郵件
 email_not_associate=此電子信箱未與任何帳戶連結
 send_reset_mail=發送帳戶救援信
 reset_password=帳戶救援
 invalid_code=您的確認代碼無效或已過期。
+invalid_code_forgot_password=您的確認代碼無效或已過期。開啟 這個連結 開始新的 session。
 invalid_password=您的密碼和用來建立帳戶的不符。
 reset_password_helper=帳戶救援
+reset_password_wrong_user=您已經使用 %s 的帳戶登入,但帳戶救援連結是給 %s 的
 password_too_short=密碼長度不能少於 %d 個字!
 non_local_account=非本地帳戶無法透過 Gitea 的網頁介面更改密碼。
 verify=驗證
@@ -365,11 +445,13 @@ oauth_signin_submit=連結帳戶
 oauth.signin.error=處理授權請求時發生錯誤。如果這個問題持續發生,請聯絡網站管理員。
 oauth.signin.error.access_denied=授權請求被拒絕。
 oauth.signin.error.temporarily_unavailable=授權失敗,因為認證伺服器暫時無法使用。請稍後再試。
+oauth_callback_unable_auto_reg=自助註冊已啟用,但是 OAuth2 提供者 %[1]s 回傳的結果缺少欄位:%[2]s,導致無法自動建立帳號。請建立新帳號或是連結至既存的帳號,或是聯絡網站管理者。
 openid_connect_submit=連接
 openid_connect_title=連接到現有帳戶
 openid_connect_desc=所選的 OpenID URI 未知。在這裡連結一個新帳戶。
 openid_register_title=建立新帳戶
 openid_register_desc=所選的 OpenID URI 未知。在這裡連結一個新帳戶。
+openid_signin_desc=輸入您的 OpenID 位址。例如:alice.openid.example.org 或是 https://openid.example.org/alice。
 disable_forgot_password_mail=由於未設定電子郵件功能,帳戶救援功能已被停用。請與網站管理員聯絡。
 disable_forgot_password_mail_admin=帳戶救援功能需要設定電子郵件功能才能使用。請設定電子郵件功能以啟用帳戶救援功能。
 email_domain_blacklisted=您無法使用您的電子信箱註冊帳號。
@@ -379,8 +461,13 @@ authorize_application_created_by=此應用程式是由 %s 建立的。
 authorize_application_description=如果您允許,它將能夠讀取和修改您的所有帳戶資訊,包括私有儲存庫和組織。
 authorize_title=授權「%s」存取您的帳戶?
 authorization_failed=授權失效
+authorization_failed_desc=授權失敗,因為我們偵測到無效的請求。請聯絡您欲授權之應用程式的維護人員。
 sspi_auth_failed=SSPI 認證失敗
+password_pwned=您選擇的密碼已被列於被盜密碼清單中,該清單因公共資料外洩而暴露。請試試其他密碼。
 password_pwned_err=無法完成對 HaveIBeenPwned 的請求。
+last_admin=您無法移除最後一個管理員。至少需要保留一個管理員帳戶。
+signin_passkey=使用 Passkey 登入
+back_to_sign_in=返回至登入
 
 [mail]
 view_it_on=在 %s 上查看
@@ -394,8 +481,10 @@ activate_account.text_1=%[1]s 您好,感謝您註冊 %[2]s!
 activate_account.text_2=請在 %s內點擊下列連結以啟用您的帳戶:
 
 activate_email=請驗證您的電子信箱
+activate_email.title=%s,請驗證您的電子信箱
 activate_email.text=請在 %s內點擊下列連結以驗證您的電子信箱:
 
+register_notify=歡迎來到 Gitea
 register_notify.title=%[1]s,歡迎來到 %[2]s
 register_notify.text_1=這是您在 %s 的註冊確認信!
 register_notify.text_2=您現在可以用帳號 %s 登入。
@@ -497,6 +586,9 @@ lang_select_error=從清單中選擇一個語言。
 
 username_been_taken=帳號已被使用
 username_change_not_local_user=非本地使用者不允許更改他們的帳號。詳細資訊請聯絡您的系統管理員。
+change_username_disabled=更改使用者名稱功能已被停用。
+change_full_name_disabled=更改完整名稱功能已被停用。
+username_has_not_been_changed=使用者名稱並未變更
 repo_name_been_taken=儲存庫名稱已被使用。
 repository_force_private=已啟用「強制私有」:私有儲存庫不能被公開。
 repository_files_already_exist=此儲存庫的檔案已存在,請聯絡系統管理有。
@@ -510,6 +602,7 @@ team_name_been_taken=團隊名稱已被使用。
 team_no_units_error=請至少選擇一個儲存庫區域。
 email_been_used=此電子信箱已被使用
 email_invalid=此電子信箱無效。
+email_domain_is_not_allowed=使用者的電子郵件地址 %s 與電子郵件域名允許清單或是電子郵件域名禁止清單有衝突。請確認您預期執行這個動作。
 openid_been_used=OpenID 位址「%s」已被使用。
 username_password_incorrect=帳號或密碼不正確
 password_complexity=密碼複雜度沒有通過以下的要求:
@@ -521,6 +614,8 @@ enterred_invalid_repo_name=您輸入的儲存庫名稱不正確。
 enterred_invalid_org_name=您輸入的組織名稱不正確。
 enterred_invalid_owner_name=新的擁有者名稱無效。
 enterred_invalid_password=您輸入的密碼不正確。
+unset_password=登入的使用者並未設定密碼。
+unsupported_login_type=這個登入方式並不支援刪除帳號。
 user_not_exist=該用戶名不存在
 team_not_exist=團隊不存在
 last_org_owner=你不能從「Owners」團隊中刪除最後一個使用者。每個組織中至少要有一個擁有者。
@@ -542,10 +637,13 @@ org_still_own_repo=此組織仍然擁有一個以上的儲存庫,請先刪除
 org_still_own_packages=此組織仍然擁有一個以上的套件,請先刪除它們。
 
 target_branch_not_exist=目標分支不存在
+target_ref_not_exist=目標參考不存在 %s
 
+admin_cannot_delete_self=當您是管理者時,您無法移除自己。請先移除您的管理者權限。
 
 [user]
 change_avatar=更改大頭貼...
+joined_on=加入於 %s
 repositories=儲存庫
 activity=公開動態
 followers=追蹤者
@@ -561,11 +659,36 @@ user_bio=個人簡介
 disabled_public_activity=這個使用者已對外隱藏動態
 email_visibility.limited=所有已驗證的使用者都可以看到您的電子信箱地址
 email_visibility.private=只有您和系統管理員可以看到您的電子信箱地址
+show_on_map=在地圖上顯示此位置
+settings=使用者設定
 
 form.name_reserved=「%s」是保留的帳號。
 form.name_pattern_not_allowed=帳號不可包含字元「%s」。
 form.name_chars_not_allowed=帳號「%s」包含無效字元。
 
+block.block=封鎖
+block.block.user=封鎖使用者
+block.block.org=為組織阻擋使用者
+block.block.failure=無法封鎖使用者: %s
+block.unblock=解除封鎖
+block.unblock.failure=無法解除封鎖使用者: %s
+block.blocked=您已封鎖該使用者。
+block.title=封鎖使用者
+block.info=阻擋使用者可避免他們與儲存庫互動,如在建立合併請求或是問題以及於其上留言。了解更多關於阻擋一個使用者。
+block.info_1=阻擋一個使用者可避免其對您的帳號或是儲存庫進行以下動作:
+block.info_2=正在追蹤你的帳戶
+block.info_3=透過 @mentioning 標記您的使用者名稱來通知您
+block.info_4=邀請您成為他們的儲存庫的協作者
+block.info_5=對儲存庫加上星號、建立分之或是關注儲存庫
+block.info_6=建立問題或是合併請求,或是對其進行留言
+block.info_7=對您在問題或是合併請求中的留言送出反應
+block.user_to_block=欲阻擋的使用者
+block.note=備註
+block.note.title=選用附註:
+block.note.info=被阻擋的使用者不會看到這個附註。
+block.note.edit=編輯備註
+block.list=已封鎖的使用者
+block.list.none=您尚未封鎖任何使用者。
 
 [settings]
 profile=個人資料
@@ -580,10 +703,17 @@ applications=應用程式
 orgs=管理組織
 repos=儲存庫
 delete=刪除帳戶
+twofa=兩步驟驗證
 account_link=已連結帳號
 organization=組織
+webauthn=安全金鑰
 
 public_profile=公開的個人資料
+biography_placeholder=告訴我們一些關於您的事情吧! (您可以使用 Markdown)
+location_placeholder=與其他人分享您的大概位置
+profile_desc=控制您的個人檔案會如何呈現給其她使用者。您的主要電子郵件地址會被用於通知、密碼救援以及網頁上的 Git 操作。
+password_username_disabled=非本地使用者不允許更改他們的帳號。詳細資訊請聯絡您的系統管理員。
+password_full_name_disabled=您不被允許更改他們的全名。詳細資訊請聯絡您的系統管理員。
 full_name=全名
 website=個人網站
 location=所在地區
@@ -594,11 +724,16 @@ update_language_not_found=無法使用語言「%s」。
 update_language_success=已更新語言。
 update_profile_success=已更新您的個人資料。
 change_username=您的帳號已更改。
+change_username_prompt=註:更改您的使用名稱也會更改您的帳號網址。
+change_username_redirect_prompt=舊的帳號被領用前,會重新導向您的新帳號。
 continue=繼續
 cancel=取消
 language=語言
 ui=佈景主題
 hidden_comment_types=隱藏的留言類型
+hidden_comment_types_description=此處勾選的評論類型將不會顯示在問題頁面內。例如勾選「標籤」將移除所有的"{user} 新增/移除{label}" 評論。
+hidden_comment_types.ref_tooltip=當這個問題在其他的問題、提交…等地方被引用時的留言
+hidden_comment_types.issue_ref_tooltip=當使用者更改與這個問題相關聯的分支、標籤時的留言
 comment_type_group_reference=參考
 comment_type_group_label=標籤
 comment_type_group_milestone=里程碑
@@ -615,6 +750,7 @@ comment_type_group_project=專案
 comment_type_group_issue_ref=問題參考
 saved_successfully=您的設定已成功儲存。
 privacy=隱私
+keep_activity_private=隱藏個人檔案上的動態
 keep_activity_private_popup=讓動態只有你和管理員看得到
 
 lookup_avatar_by_mail=以電子信箱查詢大頭貼
@@ -624,8 +760,10 @@ choose_new_avatar=選擇新的大頭貼
 update_avatar=更新大頭貼
 delete_current_avatar=刪除目前的大頭貼
 uploaded_avatar_not_a_image=上傳的檔案不是圖片
+uploaded_avatar_is_too_big=上傳檔案大小 (%d KiB) 超過大小上限 (%d KiB)
 update_avatar_success=您的大頭貼已更新
 update_user_avatar_success=已更新使用者的大頭貼。
+cropper_prompt=您可以在儲存前編輯圖片。編輯後的圖片將以 PNG 格式儲存。
 
 change_password=更新密碼
 old_password=目前的密碼
@@ -639,13 +777,17 @@ emails=電子信箱
 manage_emails=管理電子信箱
 manage_themes=選擇預設佈景主題
 manage_openid=管理 OpenID 位址
+email_desc=您的主要電子信箱將用於通知、密碼恢復以及(如果未隱藏)基於網頁的 Git 操作。
 theme_desc=這將是您在整個網站上的預設佈景主題。
+theme_colorblindness_help=色盲主題支援
+theme_colorblindness_prompt=Gitea 剛取得了一些具有基本色盲支援的主題,其中僅定義了幾種顏色。這項工作仍在進行中。透過在主題 CSS 檔案中定義更多顏色可以完成更多改進。
 primary=主要
 activated=已啟用
 requires_activation=需要啟動
 primary_email=設為主要
 activate_email=寄出啟用信
 activations_pending=等待啟用中
+can_not_add_email_activations_pending=有一個待處理的啟用,若要新增電子信箱,請幾分鐘後再試。
 delete_email=移除
 email_deletion=移除電子信箱
 email_deletion_desc=電子信箱和相關資訊將從您的帳戶中刪除,由此電子信箱所提交的 Git 將保持不變,是否繼續?
@@ -659,11 +801,13 @@ add_new_email=新增電子信箱
 add_new_openid=新增 OpenID URI
 add_email=新增電子信箱
 add_openid=新增 OpenID URI
+add_email_confirmation_sent=確認信已發送至「%s」,請在 %s內檢查您的收件匣並確認您的電子信箱。
 add_email_success=已加入新的電子信箱。
 email_preference_set_success=已套用郵件偏好設定
 add_openid_success=已加入新的 OpenID 地址。
 keep_email_private=隱藏電子信箱
-openid_desc=OpenID 讓你可以授權認證給外部服務。
+keep_email_private_popup=這將隱藏您的電子郵件地址,無論是在您的個人資料中,還是在您使用網頁介面進行 pull request 或編輯文件時。推送的提交將不會被修改。使用 %s 在提交中將其與您的帳戶關聯。
+openid_desc=OpenID 讓您可以授權認證給外部服務。
 
 manage_ssh_keys=管理 SSH 金鑰
 manage_ssh_principals=管理 SSH 認證主體
@@ -724,6 +868,8 @@ ssh_principal_deletion_desc=移除 SSH 認證主體將撤銷其對您帳戶的
 ssh_key_deletion_success=SSH 金鑰已被移除。
 gpg_key_deletion_success=GPG 金鑰已被移除。
 ssh_principal_deletion_success=已移除主體。
+added_on=新增於 %s
+valid_until_date=有效直到 %s
 valid_forever=永遠有效
 last_used=上次使用在
 no_activity=沒有近期動態
@@ -735,9 +881,12 @@ principal_state_desc=此主體在過去 7 天內曾被使用
 show_openid=在個人資料顯示
 hide_openid=從個人資料隱藏
 ssh_disabled=已停用 SSH
+ssh_signonly=目前已停用 SSH,因此這些金鑰僅用於 commit 簽章驗證。
 ssh_externally_managed=此 SSH 金鑰由此使用者的外部服務所管理
 manage_social=管理關聯的社群帳戶
+social_desc=這些帳號可以用已登入你的帳號,請確認你知道它們。
 unbind=解除連結
+unbind_success=社群帳號刪除成功
 
 manage_access_token=管理 Access Token
 generate_new_token=產生新的 Token
@@ -752,8 +901,17 @@ access_token_deletion_cancel_action=取消
 access_token_deletion_confirm_action=刪除
 access_token_deletion_desc=刪除 Token 後,使用此 Token 的應用程式將無法再存取您的帳戶,此動作不可還原。是否繼續?
 delete_token_success=已刪除 Token。使用此 Token 的應用程式無法再存取您的帳戶。
+repo_and_org_access=儲存庫和組織存取
+permissions_public_only=僅公開
+permissions_access_all=全部 (公開、私有與受限)
+select_permissions=選擇權限
+permission_not_set=尚未設定
 permission_no_access=沒有權限
 permission_read=讀取
+permission_write=讀取和寫入
+access_token_desc=選擇的 token 權限僅限於對應的 API 路徑授權。閱讀 文件 以了解更多資訊。
+at_least_one_permission=您必須選擇至少一個權限來建立 token
+permissions_list=權限:
 
 manage_oauth2_applications=管理 OAuth2 應用程式
 edit_oauth2_application=編輯 OAuth2 應用程式
@@ -763,38 +921,56 @@ remove_oauth2_application_desc=刪除 OAuth2 應用程式將會撤銷所有已
 remove_oauth2_application_success=已刪除應用程式。
 create_oauth2_application=新增 OAuth2 應用程式
 create_oauth2_application_button=建立應用程式
+create_oauth2_application_success=您已成功建立新的 OAuth2 應用程式。
+update_oauth2_application_success=您已成功更新 OAuth2 應用程式。
 oauth2_application_name=應用程式名稱
 oauth2_confidential_client=機密客戶端 (Confidential Client)。請為能保持機密性的程式勾選,例如網頁應用程式。使用原生程式時不要勾選,包含桌面、行動應用程式。
+oauth2_skip_secondary_authorization=授權一次後,跳過公用客戶端的二次授權。可能會帶來安全風險。
+oauth2_redirect_uris=重新導向 URI,每行一個 URI。
 save_application=儲存
 oauth2_client_id=客戶端 ID
 oauth2_client_secret=客戶端密鑰
 oauth2_regenerate_secret=重新產生密鑰
 oauth2_regenerate_secret_hint=遺失您的密鑰?
+oauth2_client_secret_hint=離開或重新整理此頁面後將不會再顯示密鑰。請確保您已保存它。
 oauth2_application_edit=編輯
 oauth2_application_create_description=OAuth2 應用程式讓您的第三方應用程式可以存取此 Gitea 上的帳戶。
+oauth2_application_remove_description=移除 OAuth2 應用程式將阻止其存取此實例上的授權使用者帳戶。是否繼續?
+oauth2_application_locked=Gitea 在啟動時會預先註冊一些 OAuth2 應用程式(如果在配置中啟用)。為防止意外行為,這些應用程式無法編輯或刪除。請參閱 OAuth2 文件以獲取更多資訊。
 
 authorized_oauth2_applications=已授權的 OAuth2 應用程式
+authorized_oauth2_applications_description=您已授權這些第三方應用程式存取您的 Gitea 帳戶。請撤銷不再需要的應用程式的存取權。
 revoke_key=撤銷
 revoke_oauth2_grant=撤銷存取權
-revoke_oauth2_grant_description=撤銷此第三方應用程式的存取權,此應用程式就無法再存取您的資料?您確定嗎?
+revoke_oauth2_grant_description=撤銷此第三方應用程式的存取權,此應用程式就無法再存取您的資料。您確定嗎?
+revoke_oauth2_grant_success=成功撤銷存取權。
 
+twofa_desc=兩步驟驗證可以增強您的帳戶安全性。
+twofa_recovery_tip=如果您遺失設備,您可以使用一次性恢復密鑰重新獲取帳戶存取權。
 twofa_is_enrolled=您的帳戶已經啟用兩步驟驗證。
 twofa_not_enrolled=您的帳戶目前尚未啟用兩步驟驗證。
 twofa_disable=停用兩步驟驗證
+twofa_scratch_token_regenerate=重新產生備用驗證碼
+twofa_scratch_token_regenerated=您的單次使用恢復密鑰現在是 %s。請將其存放在安全的地方,因為它不會再次顯示。
 twofa_enroll=啟用兩步驟驗證
 twofa_disable_note=如有需要,您可以停用兩步驟驗證。
 twofa_disable_desc=關閉兩步驟驗證會使您的帳戶安全性降低,是否繼續?
+regenerate_scratch_token_desc=如果您遺失了備用驗證碼或已經使用它登入,您可以在此重新設定。
 twofa_disabled=兩步驟驗證已經被關閉。
 scan_this_image=使用您的授權應用程式來掃瞄圖片:
 or_enter_secret=或者輸入密碼: %s
 then_enter_passcode=然後輸入應用程式中顯示的驗證碼:
 passcode_invalid=無效的驗證碼,請重試。
+twofa_enrolled=您的帳戶已經啟用了兩步驟驗證。請將備用驗證碼 (%s) 保存到安全的地方,它只會顯示這麼一次!
 twofa_failed_get_secret=取得密鑰 (Secret) 失敗。
 
+webauthn_desc=安全金鑰是包含加密密鑰的硬體設備,它們可以用於兩步驟驗證。安全金鑰必須支援 WebAuthn Authenticator 標準。
 webauthn_register_key=新增安全金鑰
 webauthn_nickname=暱稱
 webauthn_delete_key=移除安全金鑰
 webauthn_delete_key_desc=如果您移除安全金鑰,將不能再使用它登入。是否繼續?
+webauthn_key_loss_warning=如果您遺失了安全金鑰,您將無法存取您的帳戶。
+webauthn_alternative_tip=您可能需要設定其他的驗證方法。
 
 manage_account_links=管理已連結的帳戶
 manage_account_links_desc=這些外部帳戶已連結到您的 Gitea 帳戶。
@@ -804,8 +980,10 @@ remove_account_link=刪除已連結的帳戶
 remove_account_link_desc=刪除連結帳戶將撤銷其對 Gitea 帳戶的存取權限。是否繼續?
 remove_account_link_success=已移除連結的帳戶。
 
+hooks.desc=新增 Webhook,當您擁有的所有儲存庫觸發事件時將會執行。
 
 orgs_none=您尚未成為任一組織的成員。
+repos_none=您尚未擁有任何儲存庫。
 
 delete_account=刪除您的帳戶
 delete_prompt=此動作將永久刪除您的使用者帳戶,而且無法復原。
@@ -824,9 +1002,12 @@ visibility=使用者瀏覽權限
 visibility.public=公開
 visibility.public_tooltip=所有人都可以看到
 visibility.limited=受限
+visibility.limited_tooltip=只有已授權的使用者可見
 visibility.private=私人
+visibility.private_tooltip=僅對您已加入的組織成員可見
 
 [repo]
+new_repo_helper=儲存庫包含所有專案檔案,包括修訂歷史。已經在其他地方託管了嗎?遷移儲存庫。
 owner=擁有者
 owner_helper=組織可能因為儲存庫數量上限而未列入此選單。
 repo_name=儲存庫名稱
@@ -838,6 +1019,7 @@ template_helper=將儲存庫設為範本
 template_description=儲存庫範本讓使用者可新增相同目錄結構、檔案以及設定的儲存庫。
 visibility=瀏覽權限
 visibility_description=只有組織擁有者或有權限的組織成員才能看到。
+visibility_helper=將儲存庫設為私有
 visibility_helper_forced=您的網站管理員強制新的存儲庫必需設定為私有。
 visibility_fork_helper=(修改本值將會影響所有 fork 儲存庫)
 clone_helper=需要有關 Clone 的協助嗎?查看幫助 。
@@ -846,7 +1028,14 @@ fork_from=Fork 自
 already_forked=您已經 fork 過 %s
 fork_to_different_account=Fork 到其他帳戶
 fork_visibility_helper=無法更改 fork 儲存庫的瀏覽權限。
+fork_branch=要克隆到 fork 的分支
+all_branches=所有分支
+view_all_branches=查看所有分支
+view_all_tags=查看所有標籤
+fork_no_valid_owners=此儲存庫無法 fork,因為沒有有效的擁有者。
+fork.blocked_user=無法 fork 儲存庫,因為您被儲存庫擁有者封鎖。
 use_template=使用此範本
+open_with_editor=以 %s 開啟
 download_zip=下載 ZIP
 download_tar=下載 TAR.GZ
 download_bundle=下載 BUNDLE
@@ -854,6 +1043,8 @@ generate_repo=產生儲存庫
 generate_from=產生自
 repo_desc=描述
 repo_desc_helper=輸入簡介 (選用)
+repo_no_desc=未提供描述
+repo_lang=儲存庫語言
 repo_gitignore_helper=選擇 .gitignore 範本
 repo_gitignore_helper_desc=從常見語言範本清單中挑選忽略追蹤的檔案。預設情況下各種語言建置工具產生的特殊檔案都包含在 .gitignore 中。
 issue_labels=問題標籤
@@ -861,6 +1052,9 @@ issue_labels_helper=選擇問題標籤集
 license=授權條款
 license_helper=請選擇授權條款檔案
 license_helper_desc=授權條款定義了他人使用您原始碼的允許和禁止事項。不確定哪個適用於您的專案?查看選擇授權條款。
+multiple_licenses=多重授權
+object_format=物件格式
+object_format_helper=儲存庫的物件格式。無法更改。SHA1 是最兼容的。
 readme=讀我檔案
 readme_helper=選擇讀我檔案範本。
 readme_helper_desc=這是您能為專案撰寫完整描述的地方。
@@ -872,15 +1066,19 @@ trust_model_helper_collaborator_committer=協作者 + 提交者: 信任協作者
 trust_model_helper_default=預設: 使用此 Gitea 的預設儲存庫信任模式
 create_repo=建立儲存庫
 default_branch=預設分支
+default_branch_label=預設
 default_branch_helper=預設分支是合併請求和提交程式碼的基礎分支。
 mirror_prune=裁減
 mirror_prune_desc=刪除過時的遠端追蹤參考
 mirror_interval=鏡像間隔 (有效時間單位為 'h'、'm'、's'),設為 0 以停用定期同步。(最小間隔: %s)
 mirror_interval_invalid=鏡像週期無效
+mirror_sync=已同步
 mirror_sync_on_commit=推送提交後進行同步
 mirror_address=從 URL Clone
 mirror_address_desc=在授權資訊中填入必要的資料。
-mirror_lfs=Large File Storage (LFS)
+mirror_address_url_invalid=提供的 URL 無效。您必須正確轉義 URL 的所有組件。
+mirror_address_protocol_invalid=提供的 URL 無效。僅可使用 http(s):// 或 git:// 位置進行鏡像。
+mirror_lfs=大型檔案存儲 (LFS)
 mirror_lfs_desc=啟動 LFS 檔案的鏡像功能。
 mirror_lfs_endpoint=LFS 端點
 mirror_lfs_endpoint_desc=同步將會嘗試使用 Clone URL 來確認 LFS 伺服器。如果存儲庫的 LFS 資料放在其他地方,您也可以指定自訂的端點。
@@ -890,7 +1088,9 @@ mirror_password_blank_placeholder=(未設定)
 mirror_password_help=修改帳號以清除已儲存的密碼。
 watchers=關注者
 stargazers=占星術師
+stars_remove_warning=這將移除此儲存庫的所有星標。
 forks=Fork
+stars=星
 reactions_more=再多添加 %d個
 unit_disabled=網站管理員已經停用這個儲存庫區域。
 language_other=其他
@@ -904,10 +1104,20 @@ delete_preexisting=刪除既有的檔案
 delete_preexisting_content=刪除 %s 中的檔案
 delete_preexisting_success=刪除 %s 中未接管的檔案
 blame_prior=檢視此變更前的 Blame
+blame.ignore_revs=忽略 .git-blame-ignore-revs 中的修訂。點擊 這裡 以繞過並查看正常的 Blame 視圖。
+blame.ignore_revs.failed=忽略 .git-blame-ignore-revs 中的修訂失敗。
+user_search_tooltip=顯示最多 30 個使用者
 
+tree_path_not_found_commit=路徑 %[1]s 在提交 %[2]s 中不存在
+tree_path_not_found_branch=路徑 %[1]s 在分支 %[2]s 中不存在
+tree_path_not_found_tag=路徑 %[1]s 在標籤 %[2]s 中不存在
 
 transfer.accept=同意轉移
+transfer.accept_desc=轉移到「%s」
 transfer.reject=拒絕轉移
+transfer.reject_desc=取消轉移到「%s」
+transfer.no_permission_to_accept=您沒有權限接受此轉移。
+transfer.no_permission_to_reject=您沒有權限拒絕此轉移。
 
 desc.private=私有
 desc.public=公開
@@ -926,12 +1136,15 @@ template.issue_labels=問題標籤
 template.one_item=至少須選擇一個範本項目
 template.invalid=必須選擇一個儲存庫範本
 
-archive.issue.nocomment=此存儲庫已封存,您不能在問題上留言。
-archive.pull.nocomment=此存儲庫已封存,您不能在合併請求上留言。
+archive.title=此儲存庫已封存。您可以查看檔案並進行 Clone,但無法推送或開啟問題或合併請求。
+archive.title_date=此儲存庫已於 %s 封存。您可以查看檔案並進行 Clone,但無法推送或開啟問題或合併請求。
+archive.issue.nocomment=此儲存庫已封存,您不能在問題上留言。
+archive.pull.nocomment=此儲存庫已封存,您不能在合併請求上留言。
 
 form.reach_limit_of_creation_1=您已經達到了您儲存庫的數量上限 (%d 個)。
 form.reach_limit_of_creation_n=您已經達到了您儲存庫的數量上限 (%d 個)。
 form.name_reserved=「%s」是保留的儲存庫名稱。
+form.name_pattern_not_allowed=儲存庫名稱不可包含字元「%s」。
 
 need_auth=授權
 migrate_options=遷移選項
@@ -941,6 +1154,7 @@ migrate_options_lfs=遷移 LFS 檔案
 migrate_options_lfs_endpoint.label=LFS 端點
 migrate_options_lfs_endpoint.description=遷移將會嘗試使用您的 Git Remote 來確認 LFS 伺服器。如果存儲庫的 LFS 資料放在其他地方,您也可以指定自訂的端點。
 migrate_options_lfs_endpoint.description.local=同時也支援本地伺服器路徑。
+migrate_options_lfs_endpoint.placeholder=如果留空,端點將從 Clone URL 中推導出來
 migrate_items=遷移項目
 migrate_items_wiki=Wiki
 migrate_items_milestones=里程碑
@@ -965,6 +1179,7 @@ migrated_from_fake=已從 %[1]s 遷移
 migrate.migrate=從 %s 遷移
 migrate.migrating=正在從 %s 遷移...
 migrate.migrating_failed=從 %s 遷移失敗
+migrate.migrating_failed.error=遷移失敗: %s
 migrate.migrating_failed_no_addr=遷移失敗。
 migrate.github.description=從 github.com 或其他 GitHub 執行個體遷移資料。
 migrate.git.description=從任何 Git 服務遷移儲存庫。
@@ -974,6 +1189,9 @@ migrate.gogs.description=從 notabug.org 或其他 Gogs 執行個體遷移資料
 migrate.onedev.description=從 code.onedev.io 或其他 OneDev 執行個體遷移資料。
 migrate.codebase.description=從 codebasehq.com 遷移資料。
 migrate.gitbucket.description=從 GitBucket 執行個體遷移資料。
+migrate.codecommit.description=從 AWS CodeCommit 遷移資料。
+migrate.codecommit.https_git_credentials_username=HTTPS Git 憑證使用者名稱
+migrate.codecommit.https_git_credentials_password=HTTPS Git 憑證密碼
 migrate.migrating_git=正在遷移 Git 資料
 migrate.migrating_topics=正在遷移主題
 migrate.migrating_milestones=正在遷移里程碑
@@ -981,6 +1199,8 @@ migrate.migrating_labels=正在遷移標籤
 migrate.migrating_releases=正在遷移版本發布
 migrate.migrating_issues=正在遷移問題
 migrate.migrating_pulls=正在遷移合併請求
+migrate.cancel_migrating_title=取消遷移
+migrate.cancel_migrating_confirm=您要取消遷移嗎?
 
 mirror_from=鏡像自
 forked_from=fork 自
@@ -994,6 +1214,7 @@ watch=關注
 unstar=移除星號
 star=加上星號
 fork=Fork
+action.blocked_user=無法執行操作,因為您被儲存庫擁有者封鎖。
 download_archive=下載此儲存庫
 more_operations=更多操作
 
@@ -1031,6 +1252,7 @@ releases=版本發布
 tag=標籤
 released_this=發布了此版本
 tagged_this=標記了此標籤
+file.title=%s 於 %s
 file_raw=原始文件
 file_history=歷史記錄
 file_view_source=檢視原始碼
@@ -1038,22 +1260,36 @@ file_view_rendered=檢視渲染圖
 file_view_raw=查看原始文件
 file_permalink=永久連結
 file_too_large=檔案太大,無法顯示。
-invisible_runes_line=`這一行有看不見的 Unicode 字元`
-ambiguous_runes_line=`這一行有易混淆的 Unicode 字元`
+file_is_empty=檔案是空的。
+code_preview_line_from_to=第 %[1]d 行到第 %[2]d 行在 %[3]s
+code_preview_line_in=第 %[1]d 行在 %[2]s
+invisible_runes_header=此檔案包含不可見的 Unicode 字元
+invisible_runes_description=此檔案包含不可見的 Unicode 字元,這些字元對人類來說是無法區分的,但電腦可能會以不同方式處理。如果您認為這是有意的,可以安全地忽略此警告。使用 Escape 鍵來顯示它們。
+ambiguous_runes_header=此檔案包含易混淆的 Unicode 字元
+ambiguous_runes_description=此檔案包含可能與其他字元混淆的 Unicode 字元。如果您認為這是有意的,可以安全地忽略此警告。使用 Escape 鍵來顯示它們。
+invisible_runes_line=這一行有看不見的 Unicode 字元
+ambiguous_runes_line=這一行有易混淆的 Unicode 字元
+ambiguous_character=%[1]c [U+%04[1]X] 容易與 %[2]c [U+%04[2]X] 混淆
 
-escape_control_characters=Escape
-unescape_control_characters=Unescape
+escape_control_characters=轉義控制字元
+unescape_control_characters=取消轉義控制字元
 file_copy_permalink=複製固定連結
 view_git_blame=檢視 Git Blame
 video_not_supported_in_browser=您的瀏覽器不支援使用 HTML5 播放影片。
 audio_not_supported_in_browser=您的瀏覽器不支援 HTML5 的「audio」標籤
 stored_lfs=已使用 Git LFS 儲存
 symbolic_link=符號連結
+executable_file=可執行檔
+vendored=已供應
+generated=已產生
 commit_graph=提交線圖
 commit_graph.select=選擇分支
 commit_graph.hide_pr_refs=隱藏合併請求
 commit_graph.monochrome=單色
 commit_graph.color=彩色
+commit.contained_in=此提交包含在:
+commit.contained_in_default_branch=此提交是預設分支的一部分
+commit.load_referencing_branches_and_tags=載入引用此提交的分支和標籤
 blame=Blame
 download_file=下載檔案
 normal_view=標準檢視
@@ -1081,6 +1317,7 @@ editor.or=或
 editor.cancel_lower=取消
 editor.commit_signed_changes=提交簽署過的變更
 editor.commit_changes=提交變更
+editor.add_tmpl=新增「」
 editor.add=新增 %s
 editor.update=更新 %s
 editor.delete=刪除 %s
@@ -1101,8 +1338,15 @@ editor.filename_cannot_be_empty=檔案名稱不能為空。
 editor.filename_is_invalid=檔名無效:「%s」。
 editor.branch_does_not_exist=此儲存庫沒有名為「%s」的分支。
 editor.branch_already_exists=此儲存庫已有名為「%s」的分支。
+editor.directory_is_a_file=目錄名稱「%s」已被此儲存庫的檔案使用。
+editor.file_is_a_symlink=`"%s" 是一個符號連結。符號連結無法在網頁編輯器中編輯`
+editor.filename_is_a_directory=檔名「%s」已被此儲存庫的目錄名稱使用。
+editor.file_editing_no_longer_exists=正要編輯的檔案「%s」已不存在此儲存庫中。
+editor.file_deleting_no_longer_exists=正要刪除的檔案「%s」已不存在此儲存庫中。
 editor.file_changed_while_editing=檔案內容在您編輯的途中已被變更。按一下此處查看更動的地方或再次提交以覆蓋這些變更。
 editor.file_already_exists=此儲存庫已有名為「%s」的檔案。
+editor.commit_id_not_matching=提交 ID 與您開始編輯時的 ID 不匹配。請提交到一個補丁分支然後合併。
+editor.push_out_of_date=推送似乎已過時。
 editor.commit_empty_file_header=提交空白檔案
 editor.commit_empty_file_text=你準備提交的檔案是空白的,是否繼續?
 editor.no_changes_to_show=沒有可以顯示的變更。
@@ -1127,6 +1371,7 @@ commits.commits=次程式碼提交
 commits.no_commits=沒有共同的提交。「%s」和「%s」的歷史完全不同。
 commits.nothing_to_compare=這些分支是相同的。
 commits.search.tooltip=你可以用「author:」、「committer:」、「after:」、「before:」等作為關鍵字的前綴,例如: 「revert author:Alice before:2019-01-13」。
+commits.search_branch=此分支
 commits.search_all=所有分支
 commits.author=作者
 commits.message=備註
@@ -1138,6 +1383,7 @@ commits.signed_by_untrusted_user=由不信任的使用者簽署
 commits.signed_by_untrusted_user_unmatched=由不受信任且與提交者不相符的使用者簽署
 commits.gpg_key_id=GPG 金鑰 ID
 commits.ssh_key_fingerprint=SSH 金鑰指紋
+commits.view_path=檢視此歷史時刻
 
 commit.operations=操作
 commit.revert=還原
@@ -1155,6 +1401,7 @@ commitstatus.success=成功
 ext_issues=存取外部問題
 ext_issues.desc=連結到外部問題追蹤器。
 
+projects.desc=在專案看板中管理問題與合併請求。
 projects.description=描述 (選用)
 projects.description_placeholder=描述
 projects.create=建立專案
@@ -1182,6 +1429,7 @@ projects.column.new=新增欄位
 projects.column.set_default=設為預設
 projects.column.set_default_desc=將此欄位設定為未分類問題及合併請求的預設預設值
 projects.column.delete=刪除欄位
+projects.column.deletion_desc=刪除專案欄位會將所有相關的問題移動到「未分類」,是否繼續?
 projects.column.color=顏色
 projects.open=開啟
 projects.close=關閉
@@ -1213,6 +1461,10 @@ issues.new.clear_milestone=清除已選取里程碑
 issues.new.assignees=負責人
 issues.new.clear_assignees=清除負責人
 issues.new.no_assignees=沒有負責人
+issues.new.no_reviewers=沒有審核者
+issues.new.blocked_user=無法建立問題,因為您被儲存庫擁有者封鎖。
+issues.edit.already_changed=無法儲存問題的變更。看起來內容已被其他使用者更改。請重新整理頁面並再次嘗試編輯以避免覆蓋他們的變更。
+issues.edit.blocked_user=無法編輯內容,因為您被發文者或儲存庫擁有者封鎖。
 issues.choose.get_started=開始
 issues.choose.open_external_link=開啟
 issues.choose.blank=預設
@@ -1238,6 +1490,7 @@ issues.remove_labels=移除了 %s 標籤 %s
 issues.add_remove_labels=加入了 %s 並移除了 %s 標籤 %s
 issues.add_milestone_at=`新增到 %s 里程碑 %s`
 issues.add_project_at=`將此加入到 %s 專案 %s`
+issues.move_to_column_of_project=`將此移動到 %s 的 %s 中 %s`
 issues.change_milestone_at=`%[3]s 修改了里程碑 %[1]s%[2]s`
 issues.change_project_at=`將專案從 %s 修改為 %s %s`
 issues.remove_milestone_at=`從 %s 里程碑移除 %s`
@@ -1258,6 +1511,10 @@ issues.filter_label_exclude=`使用 alt + click/enter
 issues.filter_label_no_select=所有標籤
 issues.filter_label_select_no_label=沒有標籤
 issues.filter_milestone=里程碑
+issues.filter_milestone_all=所有里程碑
+issues.filter_milestone_none=無里程碑
+issues.filter_milestone_open=開放中的里程碑
+issues.filter_milestone_closed=已關閉的里程碑
 issues.filter_project=專案
 issues.filter_project_all=所有專案
 issues.filter_project_none=未選擇專案
@@ -1265,6 +1522,8 @@ issues.filter_assignee=負責人
 issues.filter_assginee_no_select=所有負責人
 issues.filter_assginee_no_assignee=沒有負責人
 issues.filter_poster=作者
+issues.filter_user_placeholder=搜尋使用者
+issues.filter_user_no_select=所有使用者
 issues.filter_type=類型
 issues.filter_type.all_issues=所有問題
 issues.filter_type.assigned_to_you=指派給您的
@@ -1305,6 +1564,7 @@ issues.next=下一頁
 issues.open_title=開放中
 issues.closed_title=已關閉
 issues.draft_title=草稿
+issues.num_comments_1=%d 則評論
 issues.num_comments=%d 則留言
 issues.commented_at=`已留言  %s`
 issues.delete_comment_confirm=您確定要刪除這則留言嗎?
@@ -1313,9 +1573,15 @@ issues.context.quote_reply=引用回覆
 issues.context.reference_issue=新增問題並參考
 issues.context.edit=編輯
 issues.context.delete=刪除
+issues.no_content=沒有提供描述。
 issues.close=關閉問題
+issues.comment_pull_merged_at=合併提交 %[1]s 到 %[2]s %[3]s
+issues.comment_manually_pull_merged_at=手動合併提交 %[1]s 到 %[2]s %[3]s
+issues.close_comment_issue=留言並關閉
 issues.reopen_issue=重新開放
+issues.reopen_comment_issue=留言並重新開放
 issues.create_comment=留言
+issues.comment.blocked_user=無法建立或編輯留言,因為您被發文者或儲存庫擁有者封鎖。
 issues.closed_at=`關閉了這個問題 %[2]s`
 issues.reopened_at=`重新開放了這個問題 %[2]s`
 issues.commit_ref_at=`在提交中關聯了這個問題 %[2]s`
@@ -1327,8 +1593,17 @@ issues.ref_closed_from=`關閉了這個問題 %[4]s 重新開放了這個問題 %[4]s %[2]s`
 issues.ref_from=`自 %[1]s`
 issues.author=作者
+issues.author_helper=此使用者是作者。
 issues.role.owner=擁有者
+issues.role.owner_helper=此使用者是此儲存庫的擁有者。
 issues.role.member=普通成員
+issues.role.member_helper=此使用者是擁有此儲存庫的組織成員。
+issues.role.collaborator=協作者
+issues.role.collaborator_helper=此使用者已被邀請協作此儲存庫。
+issues.role.first_time_contributor=首次貢獻者
+issues.role.first_time_contributor_helper=此使用者是首次對此儲存庫進行貢獻。
+issues.role.contributor=貢獻者
+issues.role.contributor_helper=此使用者之前已提交過此儲存庫。
 issues.re_request_review=再次請求審核
 issues.is_stale=經過此審核以後,此合併請求有被修改
 issues.remove_request_review=移除審核請求
@@ -1343,6 +1618,9 @@ issues.label_title=名稱
 issues.label_description=描述
 issues.label_color=顏色
 issues.label_exclusive=互斥
+issues.label_archive=封存標籤
+issues.label_archived_filter=顯示封存標籤
+issues.label_archive_tooltip=封存標籤在搜尋標籤時預設會被排除在建議之外。
 issues.label_exclusive_desc=請以此格式命名標籤: scope/item,使它和其他 scope/ (相同範圍) 標籤互斥。
 issues.label_exclusive_warning=在編輯問題及合併請求的標籤時,將會刪除任何有相同範圍的標籤。
 issues.label_count=%d 個標籤
@@ -1390,11 +1668,25 @@ issues.delete.title=刪除此問題?
 issues.delete.text=您真的要刪除此問題嗎?(這將會永久移除所有內容。若您還想保留,請考慮改為關閉它。)
 
 issues.tracker=時間追蹤
+issues.timetracker_timer_start=開始計時
+issues.timetracker_timer_stop=停止計時
+issues.timetracker_timer_discard=捨棄計時
+issues.timetracker_timer_manually_add=手動新增時間
 
+issues.time_estimate_set=設定預估時間
+issues.time_estimate_display=預估時間:%s
+issues.change_time_estimate_at=將預估時間更改為 %s %s
+issues.remove_time_estimate_at=移除預估時間 %s
+issues.time_estimate_invalid=預估時間格式無效
+issues.start_tracking_history=`開始工作 %s`
 issues.tracker_auto_close=當這個問題被關閉時,自動停止計時器
 issues.tracking_already_started=`您已在另一個問題上開始時間追蹤!`
+issues.stop_tracking_history=`結束工作 %s`
+issues.cancel_tracking_history=`取消時間追蹤 %s`
 issues.del_time=刪除此時間記錄
+issues.add_time_history=`加入了花費時間 %s`
 issues.del_time_history=`刪除了花費時間 %s`
+issues.add_time_manually=手動新增時間
 issues.add_time_hours=小時
 issues.add_time_minutes=分鐘
 issues.add_time_sum_to_small=沒有輸入時間。
@@ -1413,6 +1705,7 @@ issues.due_date_form=yyyy年mm月dd日
 issues.due_date_form_add=新增截止日期
 issues.due_date_form_edit=編輯
 issues.due_date_form_remove=移除
+issues.due_date_not_writer=您需要對此儲存庫的寫入權限才能更新問題的截止日期。
 issues.due_date_not_set=未設定截止日期。
 issues.due_date_added=新增了截止日期 %s %s
 issues.due_date_modified=將截止日期從 %[2]s 修改為 %[1]s %[3]s
@@ -1436,6 +1729,7 @@ issues.dependency.issue_closing_blockedby=此問題被下列問題阻擋而無
 issues.dependency.issue_close_blocks=因為此問題的阻擋,下列問題無法被關閉
 issues.dependency.pr_close_blocks=因為此合併請求的阻擋,下列問題無法被關閉
 issues.dependency.issue_close_blocked=在您關閉此問題以前,您必須先關閉所有阻擋它的問題。
+issues.dependency.issue_batch_close_blocked=無法批次關閉您選擇的問題,因為問題 #%d 還有開放中的先決條件。
 issues.dependency.pr_close_blocked=在您合併以前,您必須先關閉所有阻擋它的問題。
 issues.dependency.blocks_short=阻擋
 issues.dependency.blocked_by_short=先決於
@@ -1452,6 +1746,7 @@ issues.dependency.add_error_dep_not_same_repo=這兩個問題必須在同一個
 issues.review.self.approval=您不能核可自己的合併請求。
 issues.review.self.rejection=您不能對自己的合併請求提出請求變更。
 issues.review.approve=核可了這些變更 %s
+issues.review.comment=已審核 %s
 issues.review.dismissed=取消 %s 的審核 %s
 issues.review.dismissed_label=已取消
 issues.review.left_comment=留下了回應
@@ -1462,9 +1757,13 @@ issues.review.add_review_request=請求了 %s 來審核 %s
 issues.review.remove_review_request=移除了對 %s 的審核請求 %s
 issues.review.remove_review_request_self=拒絕了審核 %s
 issues.review.pending=待處理
+issues.review.pending.tooltip=目前其他使用者還不能看見此留言。要送出您待定的留言請在頁面最上方選擇「%s」->「%s/%s/%s」。
 issues.review.review=審核
 issues.review.reviewers=審核者
 issues.review.outdated=過時的
+issues.review.outdated_description=此留言發表後內容已變更
+issues.review.option.show_outdated_comments=顯示過時的留言
+issues.review.option.hide_outdated_comments=隱藏過時的留言
 issues.review.show_outdated=顯示過時的
 issues.review.hide_outdated=隱藏過時的
 issues.review.show_resolved=顯示已解決
@@ -1473,6 +1772,11 @@ issues.review.resolve_conversation=解決對話
 issues.review.un_resolve_conversation=取消解決對話
 issues.review.resolved_by=標記了此對話為已解決
 issues.review.commented=留言
+issues.review.official=核准
+issues.review.requested=審核待處理
+issues.review.rejected=請求變更
+issues.review.stale=核准後已更新
+issues.review.unofficial=未計入的核准
 issues.assignee.error=因為未預期的錯誤,未能成功加入所有負責人。
 issues.reference_issue.body=內容
 issues.content_history.deleted=刪除
@@ -1488,6 +1792,9 @@ compare.compare_head=比較
 
 pulls.desc=啟用合併請求和程式碼審核。
 pulls.new=建立合併請求
+pulls.new.blocked_user=無法建立合併請求,因為您被儲存庫擁有者封鎖。
+pulls.new.must_collaborator=您必須是協作者才能建立合併請求。
+pulls.edit.already_changed=無法儲存合併請求的變更。看起來內容已被其他使用者更改。請重新整理頁面並再次嘗試編輯以避免覆蓋他們的變更。
 pulls.view=檢視合併請求
 pulls.compare_changes=建立合併請求
 pulls.allow_edits_from_maintainers=允許維護者編輯
@@ -1504,20 +1811,31 @@ pulls.compare_compare=拉取自
 pulls.switch_comparison_type=切換比較類型
 pulls.switch_head_and_base=切換 head 和 base
 pulls.filter_branch=過濾分支
+pulls.show_all_commits=顯示所有提交
+pulls.show_changes_since_your_last_review=顯示自上次審核以來的變更
+pulls.showing_only_single_commit=僅顯示提交 %[1]s 的變更
+pulls.showing_specified_commit_range=僅顯示介於 %[1]s 和 %[2]s 之間的變更
+pulls.select_commit_hold_shift_for_range=選擇提交。按住 Shift 並點擊以選擇範圍
+pulls.review_only_possible_for_full_diff=僅在查看完整差異時才能進行審核
+pulls.filter_changes_by_commit=按提交篩選變更
 pulls.nothing_to_compare=這些分支的內容相同,無需建立合併請求。
+pulls.nothing_to_compare_have_tag=所選的分支/標籤相同。
 pulls.nothing_to_compare_and_allow_empty_pr=這些分支的內容相同,此合併請求將會是空白的。
 pulls.has_pull_request=`已有介於這些分支間的合併請求:%[2]s#%[3]d`
 pulls.create=建立合併請求
-pulls.title_desc=請求將 %[1]d 次程式碼提交從 %[2]s 合併至 %[3]s
+pulls.title_desc=請求將 %[1]d 次提交從 %[2]s 合併至 %[3]s
 pulls.merged_title_desc=將 %[1]d 次提交從 %[2]s 合併至 %[3]s %[4]s
 pulls.change_target_branch_at=`將目標分支從 %s 更改為 %s %s`
 pulls.tab_conversation=對話內容
-pulls.tab_commits=程式碼提交
+pulls.tab_commits=提交
 pulls.tab_files=檔案變動
 pulls.reopen_to_merge=請重新開放此合併請求以進行合併作業。
 pulls.cant_reopen_deleted_branch=無法重新開放此合併請求,因為該分支已刪除。
 pulls.merged=已合併
+pulls.merged_success=合併請求已成功合併並關閉
+pulls.closed=關閉合併請求
 pulls.manually_merged=手動合併
+pulls.merged_info_text=現在可以刪除分支 %s。
 pulls.is_closed=合併請求已被關閉。
 pulls.title_wip_desc=`標題用 %s 開頭以避免意外地合併此合併請求。`
 pulls.cannot_merge_work_in_progress=此合併請求被標記為還在進行中 (WIP)。
@@ -1532,6 +1850,13 @@ pulls.is_empty=在這個分支上的更動都已經套用在目標分支上。
 pulls.required_status_check_failed=未通過某些必要的檢查。
 pulls.required_status_check_missing=遺失某些必要的檢查。
 pulls.required_status_check_administrator=身為系統管理員,您依然可以進行合併。
+pulls.blocked_by_approvals=此合併請求尚未獲得足夠的核可。已獲得 %d 個核可中的 %d 個。
+pulls.blocked_by_approvals_whitelisted=此合併請求尚未獲得足夠的核可。已獲得允許名單中的 %d 個核可中的 %d 個。
+pulls.blocked_by_rejection=此合併請求有官方審核者請求變更。
+pulls.blocked_by_official_review_requests=此合併請求有官方審核請求。
+pulls.blocked_by_outdated_branch=此合併請求被阻擋,因為它已過時。
+pulls.blocked_by_changed_protected_files_1=此合併請求被阻擋,因為它更改了受保護的檔案:
+pulls.blocked_by_changed_protected_files_n=此合併請求被阻擋,因為它更改了受保護的檔案:
 pulls.can_auto_merge_desc=這個合併請求可以自動合併。
 pulls.cannot_auto_merge_desc=此合併請求無法自動合併,因為有衝突。
 pulls.cannot_auto_merge_helper=手動合併以解決此衝突。
@@ -1566,7 +1891,10 @@ pulls.rebase_conflict_summary=錯誤訊息
 pulls.unrelated_histories=合併失敗:要合併的 HEAD 和基底分支沒有共同的歷史。 提示:請嘗試不同的策略
 pulls.merge_out_of_date=合併失敗:產生合併時,基底已被更新。提示:再試一次。
 pulls.head_out_of_date=合併失敗:產生合併時,head 已被更新。提示:再試一次。
+pulls.has_merged=失敗:此合併請求已被合併,您不能再次合併或更改目標分支。
+pulls.push_rejected=合併失敗:此推送被拒絕。請檢查此儲存庫的 Git Hook。
 pulls.push_rejected_summary=完整的拒絕訊息
+pulls.push_rejected_no_message=合併失敗:此推送被拒絕但未提供其他資訊。
請檢查此儲存庫的 Git Hook。 pulls.open_unmerged_pull_exists=`您不能重新開放,因為目前有相同的合併請求 (#%d) 正在進行中。` pulls.status_checking=還在進行一些檢查 pulls.status_checks_success=已通過所有檢查 @@ -1575,6 +1903,8 @@ pulls.status_checks_failure=一些檢查失敗了 pulls.status_checks_error=一些檢查回報了錯誤 pulls.status_checks_requested=必要 pulls.status_checks_details=詳情 +pulls.status_checks_hide_all=隱藏所有檢查 +pulls.status_checks_show_all=顯示所有檢查 pulls.update_branch=以合併更新分支 pulls.update_branch_rebase=以 Rebase 更新分支 pulls.update_branch_success=分支更新成功 @@ -1583,6 +1913,12 @@ pulls.outdated_with_base_branch=相對於基底分支,此分支已過時 pulls.close=關閉合併請求 pulls.closed_at=`關閉了這個合併請求 %[2]s` pulls.reopened_at=`重新開放了這個合併請求 %[2]s` +pulls.cmd_instruction_hint=`檢視 命令列指示。` +pulls.cmd_instruction_checkout_title=檢出 +pulls.cmd_instruction_checkout_desc=從您的專案儲存庫中,檢出一個新分支並測試變更。 +pulls.cmd_instruction_merge_title=合併 +pulls.cmd_instruction_merge_desc=合併變更並在 Gitea 上更新。 +pulls.cmd_instruction_merge_warning=警告:此操作無法合併合併請求,因為未啟用「自動檢測手動合併」 pulls.clear_merge_message=清除合併訊息 pulls.clear_merge_message_hint=清除合併訊息將僅移除提交訊息內容,留下產生的 git 結尾,如「Co-Authored-By …」。 @@ -1601,8 +1937,16 @@ pulls.auto_merge_canceled_schedule_comment=`取消了在通過所有檢查後自 pulls.delete.title=刪除此合併請求? pulls.delete.text=您真的要刪除此合併請求嗎?(這將會永久移除所有內容。若您還想保留,請考慮改為關閉它。) +pulls.recently_pushed_new_branches=您在分支 %[1]s 上推送了 %[2]s +pulls.upstream_diverging_prompt_behind_1=此分支落後 %s %d 次提交 +pulls.upstream_diverging_prompt_behind_n=此分支落後 %s %d 次提交 +pulls.upstream_diverging_prompt_base_newer=基底分支 %s 有新變更 +pulls.upstream_diverging_merge=同步 fork +pull.deleted_branch=(已刪除): %s +pull.agit_documentation=查看 AGit 的文件 +comments.edit.already_changed=無法儲存留言的變更。看起來內容已被其他使用者更改。請重新整理頁面並再次嘗試編輯以避免覆蓋他們的變更 milestones.new=新增里程碑 milestones.closed=於 %s關閉 @@ -1610,6 +1954,8 @@ milestones.update_ago=已更新 %s milestones.no_due_date=暫無截止日期 milestones.open=開啟 milestones.close=關閉 +milestones.new_subheader=里程碑可用來組織問題和追蹤進度。 +milestones.completeness=%d%% 完成 milestones.create=建立里程碑 milestones.title=標題 milestones.desc=描述 @@ -1626,11 +1972,26 @@ milestones.deletion=刪除里程碑 milestones.deletion_desc=刪除里程碑會從所有相關的問題移除它。是否繼續? milestones.deletion_success=里程碑已刪除 milestones.filter_sort.name=名稱 +milestones.filter_sort.earliest_due_data=截止日期由遠到近 +milestones.filter_sort.latest_due_date=截止日期由近到遠 milestones.filter_sort.least_complete=完成度由低到高 milestones.filter_sort.most_complete=完成度由高到低 milestones.filter_sort.most_issues=問題由多到少 milestones.filter_sort.least_issues=問題由少到多 +signing.will_sign=此提交將使用金鑰「%s」簽署。 +signing.wont_sign.error=檢查提交是否可以簽署時發生錯誤。 +signing.wont_sign.nokey=沒有可用的金鑰來簽署此提交。 +signing.wont_sign.never=提交從不簽署。 +signing.wont_sign.always=提交總是簽署。 +signing.wont_sign.pubkey=提交不會被簽署,因為您的帳戶沒有關聯的公鑰。 +signing.wont_sign.twofa=您必須啟用雙因素驗證才能簽署提交。 +signing.wont_sign.parentsigned=提交不會被簽署,因為父提交未簽署。 +signing.wont_sign.basesigned=合併不會被簽署,因為基底提交未簽署。 +signing.wont_sign.headsigned=合併不會被簽署,因為 head 提交未簽署。 +signing.wont_sign.commitssigned=合併不會被簽署,因為所有相關的提交都未簽署。 +signing.wont_sign.approved=合併不會被簽署,因為 PR 未被核准。 +signing.wont_sign.not_signed_in=你還沒有登入。 ext_wiki=存取外部 Wiki ext_wiki.desc=連結外部 Wiki。 @@ -1660,8 +2021,13 @@ wiki.reserved_page=「%s」是保留的 Wiki 頁面名稱。 wiki.pages=所有頁面 wiki.last_updated=最後更新於 %s wiki.page_name_desc=輸入此 Wiki 頁面的名稱。一些特殊名稱有:「Home」、「_Sidebar」、「_Footer」等。 +wiki.original_git_entry_tooltip=檢視原始 Git 檔案而不是使用友善連結。 activity=動態 +activity.navbar.pulse=脈搏 +activity.navbar.code_frequency=程式碼頻率 +activity.navbar.contributors=貢獻者 +activity.navbar.recent_commits=最近提交 activity.period.filter_label=期間: activity.period.daily=1 天 activity.period.halfweekly=3 天 @@ -1727,7 +2093,10 @@ activity.git_stats_and_deletions=和 activity.git_stats_deletion_1=刪除 %d 行 activity.git_stats_deletion_n=刪除 %d 行 +contributors.contribution_type.filter_label=貢獻類型: contributors.contribution_type.commits=提交歷史 +contributors.contribution_type.additions=新增 +contributors.contribution_type.deletions=刪除 settings=設定 settings.desc=設定是您可以管理儲存庫設定的地方 @@ -1742,7 +2111,20 @@ settings.hooks=Webhook settings.githooks=Git Hook settings.basic_settings=基本設定 settings.mirror_settings=鏡像設定 +settings.mirror_settings.docs=設定您的儲存庫自動同步其他儲存庫的提交、標籤、分支。 +settings.mirror_settings.docs.disabled_pull_mirror.instructions=設定您的專案自動將提交、標籤、分支推送到其他儲存庫。您的網站管理員已停用了拉取鏡像。 +settings.mirror_settings.docs.disabled_push_mirror.instructions=設定您的專案自動從其他儲存庫拉取提交、標籤、分支。 +settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=現在這個功能只能從「遷移外部儲存庫」進行設定,詳情請參考: +settings.mirror_settings.docs.disabled_push_mirror.info=您的網站管理員已停用了推送鏡像。 +settings.mirror_settings.docs.no_new_mirrors=您的儲存庫正在鏡像變更到或從另一個儲存庫。請注意,您目前無法建立任何新的鏡像。 +settings.mirror_settings.docs.can_still_use=雖然您無法修改現有的鏡像或建立新的鏡像,但您仍然可以使用現有的鏡像。 +settings.mirror_settings.docs.pull_mirror_instructions=設定拉取鏡像請參考: +settings.mirror_settings.docs.more_information_if_disabled=您可以在這裡找到有關推送和拉取鏡像的更多資訊: +settings.mirror_settings.docs.doc_link_title=如何鏡像儲存庫? +settings.mirror_settings.docs.doc_link_pull_section=文件中的「從遠端儲存庫拉取」部分。 +settings.mirror_settings.docs.pulling_remote_title=正在從遠端儲存庫拉取 settings.mirror_settings.mirrored_repository=已鏡像的儲存庫 +settings.mirror_settings.pushed_repository=推送的儲存庫 settings.mirror_settings.direction=方向 settings.mirror_settings.direction.pull=拉取 settings.mirror_settings.direction.push=推送 @@ -1750,15 +2132,23 @@ settings.mirror_settings.last_update=最近更新時間 settings.mirror_settings.push_mirror.none=未設定推送鏡像 settings.mirror_settings.push_mirror.remote_url=Git 遠端儲存庫 URL settings.mirror_settings.push_mirror.add=新增推送鏡像 +settings.mirror_settings.push_mirror.edit_sync_time=編輯鏡像同步間隔 settings.sync_mirror=立即同步 +settings.pull_mirror_sync_in_progress=目前正在從遠端 %s 拉取變更。 +settings.push_mirror_sync_in_progress=目前正在推送變更到遠端 %s。 settings.site=網站 settings.update_settings=更新設定 +settings.update_mirror_settings=更新鏡像設定 +settings.branches.switch_default_branch=切換預設分支 settings.branches.update_default_branch=更新預設分支 settings.branches.add_new_rule=加入新規則 settings.advanced_settings=進階設定 settings.wiki_desc=啟用儲存庫 Wiki settings.use_internal_wiki=使用內建 Wiki +settings.default_wiki_branch_name=預設 Wiki 分支名稱 +settings.default_wiki_everyone_access=登入使用者的預設存取權限: +settings.failed_to_change_default_wiki_branch=更改預設 Wiki 分支失敗。 settings.use_external_wiki=使用外部 Wiki settings.external_wiki_url=外部 Wiki 連結 settings.external_wiki_url_error=外部 Wiki 網址不是有效的網址。 @@ -1788,6 +2178,10 @@ settings.pulls.default_delete_branch_after_merge=預設在合併後刪除合併 settings.pulls.default_allow_edits_from_maintainers=預設允許維護者進行編輯 settings.releases_desc=啟用儲存庫版本發佈 settings.packages_desc=啟用儲存庫套件註冊中心 +settings.projects_desc=啟用儲存庫專案 +settings.projects_mode_desc=專案模式 (顯示哪種類型的專案) +settings.projects_mode_repo=僅儲存庫專案 +settings.projects_mode_owner=僅使用者或組織專案 settings.projects_mode_all=所有專案 settings.actions_desc=啟用儲存庫 Actions settings.admin_settings=管理員設定 @@ -1814,14 +2208,17 @@ settings.convert_fork_succeed=此 fork 已轉換成普通儲存庫。 settings.transfer=轉移儲存庫所有權 settings.transfer.rejected=儲存庫轉移被拒絕。 settings.transfer.success=儲存庫已成功轉移。 +settings.transfer.blocked_user=無法轉移儲存庫,因為您被新擁有者封鎖。 settings.transfer_abort=取消轉移 settings.transfer_abort_invalid=您無法取消不存在的儲存庫轉移。 +settings.transfer_abort_success=成功取消轉移儲存庫至 %s。 settings.transfer_desc=將此儲存庫轉移給其他使用者或受您管理的組織。 settings.transfer_form_title=輸入儲存庫名稱以確認: settings.transfer_in_progress=目前正在進行轉移。如果您想要將此儲存庫轉移給其他使用者,請取消他。 settings.transfer_notices_1=- 如果將此儲存庫轉移給個別使用者,您將會失去此儲存庫的存取權。 settings.transfer_notices_2=- 如果將此儲存庫轉移到您(共同)擁有的組織,您將能繼續保有此儲存庫的存取權。 settings.transfer_notices_3=- 如果此儲存庫為私有儲存庫且將轉移給個別使用者,此動作確保該使用者至少擁有讀取權限 (必要時將會修改權限)。 +settings.transfer_notices_4=- 如果此儲存庫屬於組織,並且您將其轉移給另一個組織或個人,您將失去儲存庫問題與組織專案板之間的連結。 settings.transfer_owner=新擁有者 settings.transfer_perform=進行轉移 settings.transfer_started=此儲存庫已被標記為待轉移且正在等待「%s」的確認 @@ -1851,12 +2248,14 @@ settings.delete_notices_2=- 此操作將永久刪除 %s 儲存 settings.delete_notices_fork_1=- 在此儲存庫刪除後,它的 fork 將會變成獨立儲存庫。 settings.deletion_success=這個儲存庫已被刪除。 settings.update_settings_success=已更新儲存庫的設定。 +settings.update_settings_no_unit=儲存庫應允許至少某種形式的互動。 settings.confirm_delete=刪除儲存庫 settings.add_collaborator=增加協作者 settings.add_collaborator_success=成功增加協作者! settings.add_collaborator_inactive_user=無法將未啟用的使用者加入為協作者。 settings.add_collaborator_owner=無法將擁有者加入為協作者。 settings.add_collaborator_duplicate=此協作者早已被加入此儲存庫。 +settings.add_collaborator.blocked_user=協作者被儲存庫擁有者封鎖或反之亦然。 settings.delete_collaborator=移除 settings.collaborator_deletion=移除協作者 settings.collaborator_deletion_desc=移除協作者將拒絕他存取此儲存庫。是否繼續? @@ -1879,12 +2278,14 @@ settings.webhook_deletion_desc=移除 Webhook 將刪除它的設定及傳送記 settings.webhook_deletion_success=Webhook 已移除。 settings.webhook.test_delivery=傳送測試資料 settings.webhook.test_delivery_desc=使用假事件測試此 Webhook。 +settings.webhook.test_delivery_desc_disabled=要使用假事件測試此 Webhook,請啟用它。 settings.webhook.request=請求 settings.webhook.response=回應 settings.webhook.headers=標頭 settings.webhook.payload=內容 settings.webhook.body=本體 settings.webhook.replay.description=再次執行此 Webhook。 +settings.webhook.replay.description_disabled=要重新執行此 Webhook,請啟用它。 settings.webhook.delivery.success=已將事件加入到傳送佇列,可能需要等待幾分鐘才會出現於傳送紀錄。 settings.githooks_desc=Git Hook 是 Git 本身提供的功能。您可以在下方編輯 hook 檔案以設定自訂作業。 settings.githook_edit_desc=如果 Hook 未啟動,則會顯示範例文件中的內容。如果想要刪除某個 Hook,則送出空白內容即可。 @@ -1894,8 +2295,8 @@ settings.update_githook=更新 Hook settings.add_webhook_desc=Gitea 會發送含有指定 Content Type 的 POST 請求到目標 URL。 在 Webhook 指南閱讀更多內容。 settings.payload_url=目標 URL settings.http_method=HTTP 請求方法 -settings.content_type=POST Content Type -settings.secret=Secret +settings.content_type=POST 內容類型 +settings.secret=密鑰 settings.slack_username=服務名稱 settings.slack_icon_url=圖示 URL settings.slack_color=顏色 @@ -1917,6 +2318,7 @@ settings.event_wiki_desc=建立、重新命名、編輯、刪除 Wiki 頁面。 settings.event_release=版本發布 settings.event_release_desc=在儲存庫中發布、更新或刪除版本。 settings.event_push=推送 +settings.event_force_push=強制推送 settings.event_push_desc=推送到儲存庫。 settings.event_repository=儲存庫 settings.event_repository_desc=建立或刪除儲存庫。 @@ -1946,9 +2348,14 @@ settings.event_pull_request_review=合併請求審核 settings.event_pull_request_review_desc=核准、退回或提出審核留言。 settings.event_pull_request_sync=合併請求同步 settings.event_pull_request_sync_desc=合併請求同步。 +settings.event_pull_request_review_request=合併請求審核請求 +settings.event_pull_request_review_request_desc=合併請求審核請求或審核請求已移除。 +settings.event_pull_request_approvals=合併請求核可 +settings.event_pull_request_merge=合併請求合併 settings.event_package=套件 settings.event_package_desc=套件已在儲存庫中建立或刪除。 settings.branch_filter=分支篩選 +settings.branch_filter_desc=推送、建立分支、刪除分支事件的白名單,請使用 glob 比對模式。如果留白或輸入*,所有分支的事件都會被回報。語法參見 github.com/gobwas/glob。範例:master, {master,release*}。 settings.authorization_header=Authorization 標頭 settings.authorization_header_desc=存在時將將包含此 Authorization 標頭在請求中。例: %s。 settings.active=啟用 @@ -1994,26 +2401,72 @@ settings.deploy_key_deletion=刪除部署金鑰 settings.deploy_key_deletion_desc=移除部署金鑰將拒絕它存取此儲存庫。是否繼續? settings.deploy_key_deletion_success=部署金鑰已移除。 settings.branches=分支 +settings.protected_branch=分支保護 settings.protected_branch.save_rule=儲存規則 settings.protected_branch.delete_rule=刪除規則 +settings.protected_branch_can_push=允許推送? +settings.protected_branch_can_push_yes=你可以推送 +settings.protected_branch_can_push_no=你不能推送 +settings.branch_protection=%s 的分支保護 settings.protect_this_branch=啟用分支保護 settings.protect_this_branch_desc=防止刪除分支,並限制 Git 推送與合併到分支。 settings.protect_disable_push=停用推送 settings.protect_disable_push_desc=不允許推送到此分支。 +settings.protect_disable_force_push=停用強制推送 +settings.protect_disable_force_push_desc=不允許強制推送到此分支。 settings.protect_enable_push=啟用推送 settings.protect_enable_push_desc=任何擁有寫入權限的使用者將可推送至該分支(但不可使用force push)。 +settings.protect_enable_force_push_all=啟用強制推送 +settings.protect_enable_force_push_all_desc=任何有推送權限的人都可以強制推送到此分支。 +settings.protect_enable_force_push_allowlist=允許名單限制強制推送 +settings.protect_enable_force_push_allowlist_desc=只有推送權限的允許名單內的使用者或團隊可以強制推送到此分支。 settings.protect_enable_merge=啟用合併 settings.protect_enable_merge_desc=任何有寫入權限的人都可將合併請求合併到此分支 +settings.protect_whitelist_committers=使用白名單控管推送 +settings.protect_whitelist_committers_desc=僅允許白名單內的使用者或團隊推送至該分支(但不可使用force push)。 +settings.protect_whitelist_deploy_keys=將擁有寫入權限的部署金鑰加入白名單。 +settings.protect_whitelist_users=允許推送的使用者: +settings.protect_whitelist_teams=允許推送的團隊: +settings.protect_force_push_allowlist_users=允許強制推送的使用者: +settings.protect_force_push_allowlist_teams=允許強制推送的團隊: +settings.protect_force_push_allowlist_deploy_keys=允許強制推送的部署金鑰。 +settings.protect_merge_whitelist_committers=啟用合併白名單 +settings.protect_merge_whitelist_committers_desc=僅允許白名單內的使用者或團隊將合併請求合併至該分支。 +settings.protect_merge_whitelist_users=允許合併的使用者: +settings.protect_merge_whitelist_teams=允許合併的團隊: settings.protect_check_status_contexts=啟用狀態檢查 +settings.protect_status_check_patterns=狀態檢查模式: +settings.protect_status_check_patterns_desc=輸入模式以指定其他分支在合併到受此規則保護的分支前必須通過的狀態檢查。每行指定一個模式,模式不得為空白。 +settings.protect_check_status_contexts_desc=合併前必須先通過狀態檢查。選擇合併前必須通過的檢查。啟用時,必須先將提交推送到另一個分支,通過狀態檢查後再合併或直接推送到符合規則的分支。如果未選擇任何項目,最一個提交必將成功通過狀態檢查。 settings.protect_check_status_contexts_list=此儲存庫一週內曾進行過狀態檢查 +settings.protect_status_check_matched=符合 +settings.protect_invalid_status_check_pattern=狀態檢查模式無效: 「%s」。 +settings.protect_no_valid_status_check_patterns=沒有有效的狀態檢查模式。 settings.protect_required_approvals=需要的核可數量: +settings.protect_required_approvals_desc=只有在獲得足夠數量的核可後才能進行合併。 +settings.protect_approvals_whitelist_enabled=使用白名單控管審核人員與團隊 +settings.protect_approvals_whitelist_enabled_desc=只有白名單內的使用者與團隊會被計入需要的核可數量。未使用白名單時,將計算任何有寫入權限之人的核可。 +settings.protect_approvals_whitelist_users=審核者白名單: +settings.protect_approvals_whitelist_teams=審核團隊白名單: settings.dismiss_stale_approvals=捨棄過時的核可 settings.dismiss_stale_approvals_desc=當新的提交有修改到合併請求的內容,並被推送到此分支時,將捨棄舊的核可。 +settings.ignore_stale_approvals=忽略過時的核可 +settings.ignore_stale_approvals_desc=不計算在較舊提交上進行的核可(過時的審核)作為合併請求的核可數量。如果過時的審核已經被捨棄,則無關緊要。 settings.require_signed_commits=僅接受經簽署的提交 settings.require_signed_commits_desc=拒絕未經簽署或未經驗證的提交推送到此分支。 settings.protect_branch_name_pattern=受保護的分支名稱模式 +settings.protect_branch_name_pattern_desc=受保護的分支名稱模式。請參閱 文件 以了解模式語法。範例:main, release/** +settings.protect_patterns=模式 settings.protect_protected_file_patterns=受保護的檔案模式 (以分號區隔「;」): +settings.protect_protected_file_patterns_desc=即便使用者有權限新增、修改、刪除此分支的檔案,仍不允許直接修改受保護的檔案。可以用半形分號「;」分隔多個模式。請於 github.com/gobwas/glob 文件查看模式格式。範例: .drone.yml, /docs/**/*.txt。 settings.protect_unprotected_file_patterns=未受保護的檔案模式 (以分號區隔「;」): +settings.protect_unprotected_file_patterns_desc=當使用者有寫入權限時,可繞過推送限制,直接修改未受保護的檔案。可以用半形分號「;」分隔多個模式。請於 github.com/gobwas/glob 文件查看模式格式。範例: .drone.yml, /docs/**/*.txt。 +settings.add_protected_branch=啟用保護 +settings.delete_protected_branch=停用保護 +settings.update_protect_branch_success=已更新「%s」的分支保護規則。 +settings.remove_protected_branch_success=已刪除「%s」的分支保護規則。 +settings.remove_protected_branch_failed=刪除分支保護規則「%s」失敗。 +settings.protected_branch_deletion=停用分支保護 settings.protected_branch_deletion_desc=停用分支保護將允許有寫入權限的使用者推送至該分支,是否繼續? settings.block_rejected_reviews=有退回的審核時阻擋合併 settings.block_rejected_reviews_desc=如果官方審核人員提出變更請求,即使有足夠的核可也不允許進行合併。 @@ -2021,6 +2474,8 @@ settings.block_on_official_review_requests=有官方的審核請求時阻擋合 settings.block_on_official_review_requests_desc=如果有官方的審核請求時,即使有足夠的核可也不允許進行合併。 settings.block_outdated_branch=如果合併請求已經過時則阻擋合併 settings.block_outdated_branch_desc=當 head 分支落後於基礎分支時不得合併。 +settings.block_admin_merge_override=管理員必須遵守分支保護規則 +settings.block_admin_merge_override_desc=管理員必須遵守分支保護規則,不能繞過它。 settings.default_branch_desc=請選擇用來提交程式碼和合併請求的預設分支。 settings.merge_style_desc=合併方式 settings.default_merge_style_desc=預設合併方式 @@ -2039,18 +2494,39 @@ settings.tags.protection.allowed.teams=允許的團隊 settings.tags.protection.allowed.noone=無 settings.tags.protection.create=保護標籤 settings.tags.protection.none=沒有受保護的標籤。 -settings.bot_token=Bot Token -settings.chat_id=Chat ID -settings.matrix.homeserver_url=Homeserver 網址 +settings.tags.protection.pattern.description=您可以使用單一名稱或 glob 模式或正則表達式來匹配多個標籤。詳情請參閱 受保護標籤指南。 +settings.bot_token=機器人 Token +settings.chat_id=聊天 ID +settings.thread_id=線程 ID +settings.matrix.homeserver_url=主伺服器網址 settings.matrix.room_id=聊天室 ID settings.matrix.message_type=訊息類型 +settings.visibility.private.button=設為私人 +settings.visibility.private.text=將可見性更改為私人不僅會使儲存庫僅對允許的成員可見,還可能會移除它與 fork、關注者和星標之間的關係。 +settings.visibility.private.bullet_title=更改可見性為私人將: +settings.visibility.private.bullet_one=使儲存庫僅對允許的成員可見。 +settings.visibility.private.bullet_two=可能會移除它與 fork關注者星標 之間的關係。 +settings.visibility.public.button=設為公開 +settings.visibility.public.text=將可見性更改為公開將使儲存庫對任何人可見。 +settings.visibility.public.bullet_title=更改可見性為公開將: +settings.visibility.public.bullet_one=使儲存庫對任何人可見。 +settings.visibility.success=儲存庫可見性已更改。 +settings.visibility.error=嘗試更改儲存庫可見性時發生錯誤。 +settings.visibility.fork_error=無法更改 fork 儲存庫的可見性。 settings.archive.button=封存儲存庫 settings.archive.header=封存本儲存庫 +settings.archive.text=封存儲存庫將使其完全變為唯讀。它將從儀表板中隱藏。沒有人(甚至包括您!)將能夠進行新的提交,或打開任何問題或合併請求。 settings.archive.success=此儲存庫已被封存 settings.archive.error=嘗試封存儲存庫時發生錯誤。查看日誌檔以獲得更多資訊。 settings.archive.error_ismirror=無法封存鏡像儲存庫。 settings.archive.branchsettings_unavailable=已封存的儲存庫無法使用分支設定。 settings.archive.tagsettings_unavailable=已封存的儲存庫無法使用標籤設定。 +settings.archive.mirrors_unavailable=如果儲存庫已封存,則無法使用鏡像。 +settings.unarchive.button=取消封存儲存庫 +settings.unarchive.header=取消封存此儲存庫 +settings.unarchive.text=取消封存儲存庫將恢復其接收提交和推送的能力,以及新問題和合併請求。 +settings.unarchive.success=儲存庫已成功取消封存。 +settings.unarchive.error=嘗試取消封存儲存庫時發生錯誤。查看日誌檔以獲得更多資訊。 settings.update_avatar_success=已更新儲存庫的大頭貼。 settings.lfs=LFS settings.lfs_filelist=存放在本儲存庫的 LFS 檔案 @@ -2115,8 +2591,9 @@ diff.file_suppressed_line_too_long=檔案差異因為一行或多行太長而無 diff.too_many_files=本差異變更的檔案數量過多導致部分檔案未顯示 diff.show_more=顯示更多 diff.load=載入差異 -diff.generated=generated -diff.vendored=vendored +diff.generated=已產生 +diff.vendored=已供應 +diff.comment.add_line_comment=新增行評論 diff.comment.placeholder=留言... diff.comment.add_single_comment=加入單獨的留言 diff.comment.add_review_comment=新增留言 @@ -2147,6 +2624,7 @@ release.new_release=發布新版本 release.draft=草稿 release.prerelease=預發布版本 release.stable=穩定 +release.latest=最新 release.compare=比較 release.edit=編輯 release.ahead.commits=%d 次提交 @@ -2160,7 +2638,9 @@ release.target=目標分支 release.tag_helper=新增或選擇現有的標籤。 release.tag_helper_new=新標籤,將在目標上建立此標籤。 release.tag_helper_existing=現有的標籤。 +release.title=版本標題 release.title_empty=標題不可為空。 +release.message=描述此版本 release.prerelease_desc=標記為 Pre-Release release.prerelease_helper=標記此版本不適合生產使用。 release.cancel=取消 @@ -2170,6 +2650,7 @@ release.edit_release=更新發布 release.delete_release=刪除發布 release.delete_tag=刪除標籤 release.deletion=刪除發布 +release.deletion_desc=刪除版本發布只會將其從 Gitea 中移除。它不會影響 Git 標籤、儲存庫的內容或其歷史。是否繼續? release.deletion_success=已刪除此版本發布。 release.deletion_tag_desc=即將從儲存庫移除此標籤。儲存庫內容和歷史將保持不變,是否繼續? release.deletion_tag_success=已刪除此標籤。 @@ -2189,6 +2670,7 @@ branch.already_exists=已存在名為「%s」的分支。 branch.delete_head=刪除 branch.delete=刪除分支「%s」 branch.delete_html=刪除分支 +branch.delete_desc=刪除分支是永久的。雖然被刪除的分支可能會在實際移除前繼續存在一段時間,但在大多數情況下無法撤銷。是否繼續? branch.deletion_success=已刪除分支「%s」。 branch.deletion_failed=刪除分支「%s」失敗。 branch.delete_branch_has_new_commits=因為合併後已加入了新的提交,「%s」分支無法被刪除。 @@ -2197,6 +2679,7 @@ branch.create_from=從「%s」 branch.create_success=已建立分支「%s」。 branch.branch_already_exists=此儲存庫已有名為「%s」的分支。 branch.branch_name_conflict=分支名稱「%s」與現有分支「%s」衝突。 +branch.tag_collision=無法建立「%s」分支,因為此儲存庫中已有同名的標籤。 branch.deleted_by=由 %s 刪除 branch.restore_success=已還原分支「%s」。 branch.restore_failed=還原分支「%s」失敗。 @@ -2204,10 +2687,13 @@ branch.protected_deletion_failed=分支「%s」已被保護,不能刪除。 branch.default_deletion_failed=分支「%s」為預設分支,不能刪除。 branch.restore=還原分支「%s」 branch.download=下載分支「%s」 +branch.rename=重新命名分支「%s」 branch.included_desc=此分支是預設分支的一部分 branch.included=包含 branch.create_new_branch=從下列分支建立分支: branch.confirm_create_branch=建立分支 +branch.warning_rename_default_branch=您正在重新命名預設分支。 +branch.rename_branch_to=重新命名「%s」為: branch.confirm_rename_branch=重新命名分支 branch.create_branch_operation=建立分支 branch.new_branch=建立新分支 @@ -2223,6 +2709,8 @@ tag.create_success=已建立標籤「%s」。 topic.manage_topics=管理主題 topic.done=完成 +topic.count_prompt=您最多能選擇 25 個主題 +topic.format_prompt=主題必須以字母或數字開頭,可以包含破折號 ('-') 和點 ('.'),最多可以有 35 個字元。字母必須是小寫。 find_file.go_to_file=移至檔案 find_file.no_matching=找不到符合的檔案 @@ -2230,8 +2718,16 @@ find_file.no_matching=找不到符合的檔案 error.csv.too_large=無法渲染此檔案,因為它太大了。 error.csv.unexpected=無法渲染此檔案,因為它包含了未預期的字元,於第 %d 行第 %d 列。 error.csv.invalid_field_count=無法渲染此檔案,因為它第 %d 行的欄位數量有誤。 +error.broken_git_hook=此儲存庫的 Git hooks 似乎已損壞。請按照 文件 進行修復,然後推送一些提交以刷新狀態。 [graphs] +component_loading=正在載入 %s... +component_loading_failed=無法載入 %s +component_loading_info=這可能需要一點時間… +component_failed_to_load=發生意外錯誤。 +code_frequency.what=程式碼頻率 +contributors.what=貢獻 +recent_commits.what=最近提交 [org] org_name_holder=組織名稱 @@ -2257,11 +2753,13 @@ team_unit_desc=允許存取的儲存庫區域 team_unit_disabled=(已停用) form.name_reserved=「%s」是保留的組織名稱。 +form.name_pattern_not_allowed=組織名稱不可包含字元「%s」。 form.create_org_not_allowed=此帳號禁止建立組織。 settings=設定 settings.options=組織 settings.full_name=組織全名 +settings.email=聯絡電子郵件 settings.website=官方網站 settings.location=所在地區 settings.permission=權限 @@ -2275,6 +2773,7 @@ settings.visibility.private_shortname=私有 settings.update_settings=更新設定 settings.update_setting_success=組織設定已更新。 +settings.change_orgname_prompt=注意:更改組織名稱將同時更改組織的 URL 並釋放舊名稱。 settings.change_orgname_redirect_prompt=舊的名稱被領用前,會重新導向新名稱。 settings.update_avatar_success=已更新組織的大頭貼。 settings.delete=刪除組織 @@ -2342,6 +2841,7 @@ teams.add_nonexistent_repo=您嘗試新增的儲存庫不存在,請先建立 teams.add_duplicate_users=使用者已經是團隊成員了。 teams.repos.none=這個團隊沒有可以存取的儲存庫。 teams.members.none=這個團隊沒有任何成員。 +teams.members.blocked_user=無法新增使用者,因為它被組織封鎖。 teams.specific_repositories=指定儲存庫 teams.specific_repositories_helper=成員只能存取明確加入此團隊的儲存庫。選擇這個選項不會自動移除透過所有儲存庫加入的儲存庫。 teams.all_repositories=所有儲存庫 @@ -2349,15 +2849,21 @@ teams.all_repositories_helper=團隊擁有可存取所有儲存庫。選擇此 teams.all_repositories_read_permission_desc=這個團隊擁有所有儲存庫讀取 權限:成員可以查看和 Clone 儲存庫。 teams.all_repositories_write_permission_desc=這個團隊擁有所有儲存庫寫入 權限:成員可以讀取和推送到儲存庫。 teams.all_repositories_admin_permission_desc=這個團隊擁有所有儲存庫管理員 權限:成員可以讀取、推送和增加協作者到儲存庫。 +teams.invite.title=您已被邀請加入組織 %s 中的團隊 %s。 teams.invite.by=邀請人 %s teams.invite.description=請點擊下方按鈕加入團隊。 [admin] +maintenance=維護 dashboard=資訊主頁 +self_check=自我檢查 +identity_access=身份與存取 users=使用者帳戶 organizations=組織 +assets=程式碼資產 repositories=儲存庫 hooks=Webhook +integrations=整合 authentication=認證來源 emails=使用者電子信箱 config=組態 @@ -2368,8 +2874,11 @@ monitor=應用監控面版 first_page=首頁 last_page=末頁 total=總計:%d +settings=管理員設定 +dashboard.new_version_hint=現已推出 Gitea %s,您正在執行 %s。詳情請參閱部落格的說明。 dashboard.statistic=摘要 +dashboard.maintenance_operations=維護操作 dashboard.system_status=系統狀態 dashboard.operation_name=作業名稱 dashboard.operation_switch=開關 @@ -2378,11 +2887,13 @@ dashboard.clean_unbind_oauth=清理未綁定的 OAuth 連結 dashboard.clean_unbind_oauth_success=所有未綁定的 OAuth 連結已刪除。 dashboard.task.started=已開始的任務: %[1]s dashboard.task.process=任務: %[1]s +dashboard.task.cancelled=任務: %[1]s 已取消: %[3]s dashboard.task.error=任務中的錯誤: %[1]s: %[3]s dashboard.task.finished=任務: 已完成由 %[2]s 啟動的 %[1]s dashboard.task.unknown=未知的任務: %[1]s dashboard.cron.started=已開始的 Cron: %[1]s dashboard.cron.process=Cron: %[1]s +dashboard.cron.cancelled=Cron: %[1]s 已取消: %[3]s dashboard.cron.error=Cron 中的錯誤: %s: %[3]s dashboard.cron.finished=Cron: %[1]s 已完成 dashboard.delete_inactive_accounts=刪除所有未啟用帳戶 @@ -2392,6 +2903,8 @@ dashboard.delete_repo_archives.started=刪除所有儲存庫存檔的任務已 dashboard.delete_missing_repos=刪除所有遺失 Git 檔案的儲存庫 dashboard.delete_missing_repos.started=刪除所有遺失 Git 檔案的儲存庫的任務已啟動。 dashboard.delete_generated_repository_avatars=刪除產生的儲存庫大頭貼 +dashboard.sync_repo_branches=從 Git 資料同步遺漏的分支到資料庫 +dashboard.sync_repo_tags=從 Git 資料同步標籤到資料庫 dashboard.update_mirrors=更新鏡像 dashboard.repo_health_check=對所有儲存庫進行健康檢查 dashboard.check_repo_stats=檢查所有儲存庫的統計資料 @@ -2406,6 +2919,7 @@ dashboard.reinit_missing_repos=重新初始化所有記錄存在但遺失的 Git dashboard.sync_external_users=同步外部使用者資料 dashboard.cleanup_hook_task_table=清理 hook_task 資料表 dashboard.cleanup_packages=清理已過期的套件 +dashboard.cleanup_actions=清理過期的操作資源 dashboard.server_uptime=服務執行時間 dashboard.current_goroutine=目前的 Goroutines 數量 dashboard.current_memory_usage=目前記憶體使用量 @@ -2435,9 +2949,19 @@ dashboard.total_gc_time=總 GC 暫停時間 dashboard.total_gc_pause=總 GC 暫停時間 dashboard.last_gc_pause=上次 GC 暫停時間 dashboard.gc_times=GC 執行次數 +dashboard.delete_old_actions=從資料庫刪除所有舊行為 +dashboard.delete_old_actions.started=從資料庫刪除所有舊行為的任務已啟動。 dashboard.update_checker=更新檢查器 dashboard.delete_old_system_notices=從資料庫刪除所有舊系統提示 dashboard.gc_lfs=對 LFS meta objects 進行垃圾回收 +dashboard.stop_zombie_tasks=停止殭屍任務 +dashboard.stop_endless_tasks=停止永不停止的任務 +dashboard.cancel_abandoned_jobs=取消已放棄的工作 +dashboard.start_schedule_tasks=啟動動作排程任務 +dashboard.sync_branch.started=分支同步已開始 +dashboard.sync_tag.started=標籤同步已開始 +dashboard.rebuild_issue_indexer=重建問題索引器 +dashboard.sync_repo_licenses=同步儲存庫許可證 users.user_manage_panel=使用者帳戶管理 users.new_account=建立使用者帳戶 @@ -2446,6 +2970,9 @@ users.full_name=全名 users.activated=已啟用 users.admin=管理員 users.restricted=受限 +users.reserved=保留 +users.bot=機器人 (Bot) +users.remote=遠端 users.2fa=兩步驟驗證 users.repos=儲存庫數 users.created=建立時間 @@ -2492,6 +3019,7 @@ users.list_status_filter.is_prohibit_login=禁止登入 users.list_status_filter.not_prohibit_login=允許登入 users.list_status_filter.is_2fa_enabled=已啟用兩步驟驗證 users.list_status_filter.not_2fa_enabled=未啟用兩步驟驗證 +users.details=使用者詳細資訊 emails.email_manage_panel=使用者電子信箱管理 emails.primary=主要 @@ -2504,6 +3032,11 @@ emails.updated=信箱已更新 emails.not_updated=電子信箱更新失敗: %v emails.duplicate_active=此信箱已被其他使用者使用 emails.change_email_header=更新電子信箱屬性 +emails.change_email_text=您確定要更新此電子郵件地址嗎? +emails.delete=刪除電子郵件 +emails.delete_desc=您確定要刪除此電子郵件地址嗎? +emails.deletion_success=電子郵件地址已被刪除。 +emails.delete_primary_email_error=您不能刪除主要的電子郵件地址。 orgs.org_manage_panel=組織管理 orgs.name=名稱 @@ -2519,10 +3052,13 @@ repos.name=名稱 repos.private=私有 repos.issues=問題數 repos.size=大小 +repos.lfs_size=LFS 大小 packages.package_manage_panel=套件管理 packages.total_size=總大小: %s packages.unreferenced_size=未參考大小: %s +packages.cleanup=清理已逾期的資料 +packages.cleanup.success=已成功清理過期的資料 packages.owner=擁有者 packages.creator=建立者 packages.name=名稱 @@ -2533,10 +3069,12 @@ packages.size=大小 packages.published=已發布 defaulthooks=預設 Webhook +defaulthooks.desc=當某些 Gitea 事件觸發時,Webhook 會自動發出 HTTP POST 請求到伺服器。此處定義的 Webhook 是預設值,將會複製到所有新儲存庫中。詳情請參閱 webhooks 指南。 defaulthooks.add_webhook=新增預設 Webhook defaulthooks.update_webhook=更新預設 Webhook systemhooks=系統 Webhook +systemhooks.desc=當某些 Gitea 事件觸發時,Webhook 會自動發出 HTTP POST 請求到伺服器。此處定義的 Webhook 將作用於系統上的所有儲存庫,因此請考慮這可能對效能產生的影響。詳情請參閱 webhooks 指南。 systemhooks.add_webhook=新增系統 Webhook systemhooks.update_webhook=更新系統 Webhook @@ -2629,8 +3167,20 @@ auths.sspi_default_language=使用者預設語言 auths.sspi_default_language_helper=SSPI 認證方法自動建立之使用者的預設語言,留白以自動偵測。 auths.tips=幫助提示 auths.tips.oauth2.general=OAuth2 認證 +auths.tips.oauth2.general.tip=註冊新的 OAuth2 認證時,回調/重定向 URL 應為: auths.tip.oauth2_provider=OAuth2 提供者 +auths.tip.bitbucket=註冊新的 OAuth 客戶端並加入權限「Account - Read」。網址:https://bitbucket.org/account/user//oauth-consumers/new auths.tip.nextcloud=在您的執行個體中,於選單「設定 -> 安全性 -> OAuth 2.0 客戶端」註冊新的 OAuth 客戶端 +auths.tip.dropbox=建立新的 App。網址:https://www.dropbox.com/developers/apps +auths.tip.facebook=註冊新的應用程式並新增產品「Facebook 登入」。網址:https://developers.facebook.com/apps +auths.tip.github=註冊新的 OAuth 應用程式。網址:https://github.com/settings/applications/new +auths.tip.gitlab_new=註冊新的應用程式。網址:https://discordapp.com/developers/applications/me +auths.tip.google_plus=從 Google API 控制台取得 OAuth2 用戶端憑證。網址:https://console.developers.google.com/ +auths.tip.openid_connect=使用 OpenID 連接探索 URL (/.well-known/openid-configuration) 來指定節點 +auths.tip.twitter=建立應用程式並確保有啟用「Allow this application to be used to Sign in with Twitter」。網址:https://dev.twitter.com/apps +auths.tip.discord=註冊新的應用程式。網址:https://discordapp.com/developers/applications/me +auths.tip.gitea=註冊新的 OAuth2 應用程式。指南可在 %s 找到 +auths.tip.yandex=建立新的應用程式,從「Yandex.Passport API」區塊選擇「Access to email address」、「Access to user avatar」和「Access to username, first name and surname, gender」。網址:https://oauth.yandex.com/client/new auths.tip.mastodon=輸入您欲認證的 Mastodon 執行個體的自訂網址 (或使用預設值) auths.edit=修改認證來源 auths.activated=該認證來源已啟用 @@ -2659,6 +3209,7 @@ config.disable_router_log=關閉路由日誌 config.run_user=以使用者名稱執行 config.run_mode=執行模式 config.git_version=Git 版本 +config.app_data_path=應用程式資料路徑 config.repo_root_path=儲存庫目錄 config.lfs_root_path=LFS 根目錄 config.log_file_root_path=日誌路徑 @@ -2733,6 +3284,7 @@ config.mailer_sendmail_timeout=Sendmail 逾時 config.mailer_use_dummy=Dummy config.test_email_placeholder=電子信箱 (例:test@example.com) config.send_test_mail=傳送測試郵件 +config.send_test_mail_submit=傳送 config.test_mail_failed=傳送測試郵件到「%s」時失敗: %v config.test_mail_sent=測試郵件已傳送到「%s」。 @@ -2744,6 +3296,10 @@ config.cache_adapter=Cache 適配器 config.cache_interval=Cache 週期 config.cache_conn=Cache 連接字符串 config.cache_item_ttl=快取項目 TTL +config.cache_test=測試快取 +config.cache_test_failed=測試快取失敗: %v +config.cache_test_slow=快取測試成功,但回應速度慢: %s +config.cache_test_succeeded=快取測試成功,回應時間為 %s config.session_config=Session 組態 config.session_provider=Session 提供者 @@ -2758,6 +3314,7 @@ config.picture_config=圖片和大頭貼組態 config.picture_service=圖片服務 config.disable_gravatar=停用 Gravatar config.enable_federated_avatar=啟用 Federated Avatars +config.open_with_editor_app_help=「開啟方式」編輯器用於克隆選單。如果留空,將使用預設值。展開以查看預設值。 config.git_config=Git 組態 config.git_disable_diff_highlight=停用比較語法高亮 @@ -2772,12 +3329,15 @@ config.git_pull_timeout=Pull 作業逾時 config.git_gc_timeout=GC 作業逾時 config.log_config=日誌組態 +config.logger_name_fmt=記錄器: %s config.disabled_logger=已停用 config.access_log_mode=存取日誌模式 +config.access_log_template=存取日誌範本 config.xorm_log_sql=記錄 SQL config.set_setting_failed=寫入設定值 %s 失敗 +monitor.stats=統計 monitor.cron=Cron 任務 monitor.name=名稱 @@ -2786,11 +3346,16 @@ monitor.next=下次執行時間 monitor.previous=上次執行時間 monitor.execute_times=執行次數 monitor.process=執行中的處理程序 +monitor.stacktrace=堆疊追蹤 +monitor.processes_count=%d 個處理程序 +monitor.download_diagnosis_report=下載診斷報告 monitor.desc=描述 monitor.start=開始時間 monitor.execute_time=已執行時間 monitor.last_execution_result=結果 monitor.process.cancel=結束處理程序 +monitor.process.cancel_desc=結束處理程序可能造成資料遺失 +monitor.process.cancel_notices=結束: %s? monitor.process.children=子程序 monitor.queues=佇列 @@ -2799,14 +3364,19 @@ monitor.queue.name=名稱 monitor.queue.type=類型 monitor.queue.exemplar=型別 monitor.queue.numberworkers=工作者數量 +monitor.queue.activeworkers=活躍工作者 monitor.queue.maxnumberworkers=最大工作者數量 monitor.queue.numberinqueue=佇列中的數量 +monitor.queue.review_add=審查 / 增加工作者 monitor.queue.settings.title=集區設定 +monitor.queue.settings.desc=集區會根據工作者佇列的阻塞情況動態增長。 monitor.queue.settings.maxnumberworkers=最大工作者數量 monitor.queue.settings.maxnumberworkers.placeholder=目前 %[1]d monitor.queue.settings.maxnumberworkers.error=最大工作者數量必須是數字 monitor.queue.settings.submit=更新設定 monitor.queue.settings.changed=已更新設定 +monitor.queue.settings.remove_all_items=全部移除 +monitor.queue.settings.remove_all_items_done=佇列中的所有項目已被移除。 notices.system_notice_list=系統提示 notices.view_detail_header=查看提示細節 @@ -2823,6 +3393,14 @@ notices.desc=描述 notices.op=操作 notices.delete_success=已刪除系統提示。 +self_check.no_problem_found=尚未發現任何問題。 +self_check.startup_warnings=啟動警告: +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 ..." SQL 語句來修復問題。 +self_check.location_origin_mismatch=當前 URL (%[1]s) 與 Gitea 看到的 URL (%[2]s) 不匹配。如果您使用了反向代理,請確保 "Host" 和 "X-Forwarded-Proto" 標頭設置正確。 [action] create_repo=建立了儲存庫 %s @@ -2850,6 +3428,7 @@ mirror_sync_create=從鏡像同步了新參考 %[3]s%[3]s 刪除了參考 %[2]s approve_pull_request=`核可了 %[3]s#%[2]s` reject_pull_request=`提出了修改建議 %[3]s#%[2]s` +publish_release=`發布了 %[3]s "%[4]s" ` review_dismissed=`取消了 %[4]s%[3]s#%[2]s 的審核` review_dismissed_reason=原因: create_branch=在 %[4]s 中建立了分支 %[3]s @@ -2878,6 +3457,7 @@ raw_minutes=分鐘 [dropzone] default_message=拖放檔案或是點擊此處上傳。 +invalid_input_type=您無法上傳此類型的檔案 file_too_big=檔案大小({{filesize}} MB) 超過了最大允許大小({{maxFilesize}} MB) remove_file=移除文件 @@ -2915,8 +3495,10 @@ error.unit_not_allowed=您未被允許訪問此儲存庫區域 title=套件 desc=管理儲存庫套件。 empty=目前還沒有套件。 +no_metadata=沒有元數據。 empty.documentation=關於套件註冊中心的詳情請參閱說明文件。 empty.repo=已經上傳了一個套件,但是沒有顯示在這裡嗎?打開套件設定並將其連結到這個儲存庫。 +registry.documentation=有關 %s 註冊中心的更多資訊,請參閱說明文件。 filter.type=類型 filter.type.all=所有 filter.no_result=沒有篩選結果。 @@ -2948,6 +3530,8 @@ alpine.repository=儲存庫資訊 alpine.repository.branches=分支 alpine.repository.repositories=儲存庫 alpine.repository.architectures=架構 +arch.registry=在 /etc/pacman.conf 中新增伺服器及相關儲存庫和架構: +arch.install=使用 pacman 同步套件: arch.repository=儲存庫資訊 arch.repository.repositories=儲存庫 arch.repository.architectures=架構 @@ -2973,12 +3557,17 @@ container.layers=映像檔 Layers container.labels=標籤 container.labels.key=鍵 container.labels.value=值 +cran.registry=在您的 Rprofile.site 檔設定此註冊中心: cran.install=執行下列命令安裝此套件: debian.registry=透過下列命令設定此註冊中心: +debian.registry.info=從下列清單選擇$distribution和$component debian.install=執行下列命令安裝此套件: debian.repository=儲存庫資訊 +debian.repository.distributions=發行版 +debian.repository.components=元件 debian.repository.architectures=架構 generic.download=透過下列命令下載套件: +go.install=透過下列命令安裝套件: helm.registry=透過下列命令設定此註冊中心: helm.install=執行下列命令安裝此套件: maven.registry=在您專案的 pom.xml 檔設定此註冊中心: @@ -2993,6 +3582,7 @@ npm.install=執行下列命令以使用 npm 安裝此套件: npm.install2=或將它加到 package.json 檔: npm.dependencies=相依性 npm.dependencies.development=開發相依性 +npm.dependencies.bundle=捆綁相依性 npm.dependencies.peer=Peer 相依性 npm.dependencies.optional=選用相依性 npm.details.tag=標籤 @@ -3000,9 +3590,12 @@ pub.install=執行下列命令以使用 Dart 安裝此套件: pypi.requires=需要 Python pypi.install=執行下列命令以使用 pip 安裝此套件: rpm.registry=透過下列命令設定此註冊中心: +rpm.distros.redhat=在基於 RedHat 的發行版上 +rpm.distros.suse=在基於 SUSE 的發行版上 rpm.install=執行下列命令安裝此套件: rpm.repository=儲存庫資訊 rpm.repository.architectures=架構 +rpm.repository.multiple_groups=此套件在多個群組中可用。 rubygems.install=執行下列命令以使用 gem 安裝此套件: rubygems.install2=或將它加到 Gemfile: rubygems.dependencies.runtime=執行階段相依性 @@ -3026,14 +3619,17 @@ settings.delete.success=已刪除該套件。 settings.delete.error=刪除套件失敗。 owner.settings.cargo.title=Cargo Registry 索引 owner.settings.cargo.initialize=初始化索引 +owner.settings.cargo.initialize.description=使用 Cargo 註冊中心需要一個特殊的索引 Git 儲存庫。使用此選項將會 (重新) 建立儲存庫並自動配置它。 owner.settings.cargo.initialize.error=初始化 Cargo 索引失敗: %v owner.settings.cargo.initialize.success=成功建立了 Cargo 索引。 owner.settings.cargo.rebuild=重建索引 +owner.settings.cargo.rebuild.description=如果索引與儲存的 Cargo 套件不同步,重建索引可能會有幫助。 owner.settings.cargo.rebuild.error=重建 Cargo 索引失敗: %v owner.settings.cargo.rebuild.success=成功重建了 Cargo 索引。 owner.settings.cleanuprules.title=管理清理規則 owner.settings.cleanuprules.add=加入清理規則 owner.settings.cleanuprules.edit=編輯清理規則 +owner.settings.cleanuprules.none=沒有清理規則可用。請參閱說明文件。 owner.settings.cleanuprules.preview=清理規則預覽 owner.settings.cleanuprules.preview.overview=已排定要移除 %d 個套件。 owner.settings.cleanuprules.preview.none=清理規則不符合任何套件。 @@ -3052,6 +3648,7 @@ owner.settings.cleanuprules.success.update=已更新清理規則。 owner.settings.cleanuprules.success.delete=已刪除清理規則。 owner.settings.chef.title=Chef Registry owner.settings.chef.keypair=產生密鑰組 +owner.settings.chef.keypair.description=驗證 Chef 註冊中心需要一個密鑰組。如果您之前已生成過密鑰組,生成新密鑰組將會丟棄舊的密鑰組。 [secrets] secrets=Secret @@ -3066,6 +3663,7 @@ deletion=移除 Secret deletion.description=移除 Secret 是永久的且不可還原,是否繼續? deletion.success=已移除此 Secret。 deletion.failed=移除 Secret 失敗。 +management=Secret 管理 [actions] actions=Actions @@ -3077,6 +3675,7 @@ status.waiting=正在等候 status.running=正在執行 status.success=成功 status.failure=失敗 +status.cancelled=已取消 status.skipped=已略過 status.blocked=已阻塞 @@ -3093,6 +3692,7 @@ runners.labels=標籤 runners.last_online=最後上線時間 runners.runner_title=Runner runners.task_list=最近在此 Runner 上的任務 +runners.task_list.no_tasks=目前還沒有任務。 runners.task_list.run=執行 runners.task_list.status=狀態 runners.task_list.repository=儲存庫 @@ -3113,25 +3713,69 @@ runners.status.idle=閒置 runners.status.active=啟用 runners.status.offline=離線 runners.version=版本 +runners.reset_registration_token=重設註冊 Token runners.reset_registration_token_success=成功重設了 Runner 註冊 Token runs.all_workflows=所有工作流程 runs.commit=提交 +runs.scheduled=已排程 +runs.pushed_by=推送者 runs.invalid_workflow_helper=工作流程設定檔無效。請檢查您的設定檔: %s +runs.no_matching_online_runner_helper=沒有符合標籤的線上 Runner: %s +runs.no_job_without_needs=工作流程必須包含至少一個沒有依賴的工作。 +runs.no_job=工作流程必須包含至少一個工作 +runs.actor=執行者 runs.status=狀態 +runs.actors_no_select=所有執行者 +runs.status_no_select=所有狀態 +runs.no_results=沒有符合的結果。 +runs.no_workflows=目前還沒有工作流程。 +runs.no_workflows.quick_start=不知道如何開始使用 Gitea Actions?請參閱快速入門指南。 +runs.no_workflows.documentation=有關 Gitea Actions 的更多資訊,請參閱文件。 runs.no_runs=工作流程沒有執行過。 +runs.empty_commit_message=(空的提交訊息) +runs.expire_log_message=日誌已被清除,因為它們太舊了。 workflow.disable=停用工作流程 workflow.disable_success=已成功停用工作流程「%s」。 workflow.enable=啟用工作流程 workflow.enable_success=已成功啟用工作流程「%s」。 +workflow.disabled=工作流程已停用。 +workflow.run=執行工作流程 +workflow.not_found=找不到工作流程「%s」。 +workflow.run_success=工作流程「%s」執行成功。 +workflow.from_ref=使用工作流程來自 +workflow.has_workflow_dispatch=此工作流程有一個 workflow_dispatch 事件觸發器。 need_approval_desc=來自 Frok 儲存庫的合併請求需要核可才能執行工作流程。 +variables=變數 +variables.management=變數管理 +variables.creation=新增變數 +variables.none=還沒有任何變數。 +variables.deletion=移除變數 +variables.deletion.description=移除變數是永久的且不可還原,是否繼續? +variables.description=變數會被傳送到某些 Action 且無法以其他方式讀取。 +variables.id_not_exist=ID 為 %d 的變數不存在。 +variables.edit=編輯變數 +variables.deletion.failed=移除變數失敗。 +variables.deletion.success=已刪除變數。 +variables.creation.failed=新增變數失敗。 +variables.creation.success=已新增變數「%s」。 +variables.update.failed=編輯變數失敗。 +variables.update.success=已編輯變數。 [projects] +deleted.display_name=已刪除的專案 +type-1.display_name=個人專案 +type-2.display_name=儲存庫專案 +type-3.display_name=組織專案 [git.filemode] ; Ordered by git filemode value, ascending. E.g. directory has "040000", normal file has "100644", … +directory=目錄 +normal_file=一般檔案 +executable_file=可執行檔 symbolic_link=符號連結 +submodule=子模組 diff --git a/package-lock.json b/package-lock.json index 53bd5bc4f1f..60edfa95d07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@github/relative-time-element": "4.4.4", "@github/text-expander-element": "2.8.0", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", - "@primer/octicons": "19.13.0", + "@primer/octicons": "19.14.0", "@silverwind/vue3-calendar-heatmap": "2.0.6", "add-asset-webpack-plugin": "3.0.0", "ansi_up": "6.0.2", @@ -30,15 +30,15 @@ "esbuild-loader": "4.2.2", "escape-goat": "4.0.0", "fast-glob": "3.3.2", - "htmx.org": "2.0.3", + "htmx.org": "2.0.4", "idiomorph": "0.3.0", "jquery": "3.7.1", - "katex": "0.16.11", + "katex": "0.16.18", "license-checker-webpack-plugin": "0.2.1", "mermaid": "11.4.1", "mini-css-extract-plugin": "2.9.2", "minimatch": "10.0.1", - "monaco-editor": "0.52.0", + "monaco-editor": "0.52.2", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.0", "perfect-debounce": "1.0.0", @@ -47,7 +47,7 @@ "postcss-nesting": "13.0.1", "sortablejs": "1.15.6", "swagger-ui-dist": "5.18.2", - "tailwindcss": "3.4.16", + "tailwindcss": "3.4.17", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", "tippy.js": "6.3.7", @@ -60,15 +60,16 @@ "vue-bar-graph": "2.2.0", "vue-chartjs": "5.3.2", "vue-loader": "17.4.2", - "webpack": "5.97.0", + "webpack": "5.97.1", "webpack-cli": "5.1.4", "wrap-ansi": "9.0.0" }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "4.4.1", - "@playwright/test": "1.49.0", + "@playwright/test": "1.49.1", + "@silverwind/vue-tsc": "2.1.13", "@stoplight/spectral-cli": "6.14.2", - "@stylistic/eslint-plugin-js": "2.11.0", + "@stylistic/eslint-plugin-js": "2.12.1", "@stylistic/stylelint-plugin": "3.1.1", "@types/dropzone": "5.7.9", "@types/jquery": "3.5.32", @@ -80,19 +81,19 @@ "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/toastify-js": "1.12.3", - "@typescript-eslint/eslint-plugin": "8.17.0", - "@typescript-eslint/parser": "8.17.0", + "@typescript-eslint/eslint-plugin": "8.18.1", + "@typescript-eslint/parser": "8.18.1", "@vitejs/plugin-vue": "5.2.1", "eslint": "8.57.0", "eslint-import-resolver-typescript": "3.7.0", "eslint-plugin-array-func": "4.0.0", - "eslint-plugin-github": "5.1.3", - "eslint-plugin-import-x": "4.5.0", + "eslint-plugin-github": "5.1.4", + "eslint-plugin-import-x": "4.6.1", "eslint-plugin-no-jquery": "3.1.0", "eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-playwright": "2.1.0", "eslint-plugin-regexp": "2.7.0", - "eslint-plugin-sonarjs": "2.0.4", + "eslint-plugin-sonarjs": "3.0.1", "eslint-plugin-unicorn": "56.0.1", "eslint-plugin-vitest": "0.4.1", "eslint-plugin-vitest-globals": "1.5.0", @@ -101,18 +102,17 @@ "eslint-plugin-wc": "2.2.0", "happy-dom": "15.11.7", "markdownlint-cli": "0.43.0", - "nolyfill": "1.0.42", + "nolyfill": "1.0.43", "postcss-html": "1.7.0", - "stylelint": "16.11.0", + "stylelint": "16.12.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.6", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.3.2", - "type-fest": "4.30.0", - "updates": "16.4.0", + "type-fest": "4.30.2", + "updates": "16.4.1", "vite-string-plugin": "1.3.4", - "vitest": "2.1.8", - "vue-tsc": "2.1.10" + "vitest": "2.1.8" }, "engines": { "node": ">= 18.0.0" @@ -190,12 +190,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, "node_modules/@babel/compat-data": { "version": "7.26.3", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", @@ -207,22 +201,22 @@ } }, "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -248,9 +242,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.25.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.25.1.tgz", - "integrity": "sha512-Y956ghgTT4j7rKesabkh5WeqgSFZVFwaPR0IWFm7KFHFmmJ4afbG49SmfW4S+GyRPx0Dy5jxEWA5t0rpxfElWg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.25.9.tgz", + "integrity": "sha512-5UXfgpK0j0Xr/xIdgdLEhOFxaDZ0bRPWJJchRpqOSur/3rZoPbqqki5mm0p4NE2cs28krBEiSM2MB7//afRSQQ==", "dev": true, "license": "MIT", "dependencies": { @@ -333,16 +327,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -706,15 +690,15 @@ } }, "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.7.tgz", - "integrity": "sha512-RL9GR0pUG5Kc8BUWLNDm2T5OpYwSX15r98I0IkgmRQTXuELq/OynH8xtMTMvTJFjXbMWFVTKtYkTaYQsuAwQlQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz", + "integrity": "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-decorators": "^7.24.7" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-decorators": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -736,48 +720,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-decorators": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz", @@ -794,32 +736,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-flow": { "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz", @@ -868,32 +784,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", @@ -910,116 +800,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", @@ -1789,6 +1569,23 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-reserved-words": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", @@ -1954,94 +1751,80 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.4.tgz", - "integrity": "sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", + "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.4", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-option": "^7.24.8", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", + "@babel/compat-data": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.7", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.25.4", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoped-functions": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.25.4", - "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.25.4", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-dotall-regex": "^7.24.7", - "@babel/plugin-transform-duplicate-keys": "^7.24.7", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", - "@babel/plugin-transform-dynamic-import": "^7.24.7", - "@babel/plugin-transform-exponentiation-operator": "^7.24.7", - "@babel/plugin-transform-export-namespace-from": "^7.24.7", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.25.1", - "@babel/plugin-transform-json-strings": "^7.24.7", - "@babel/plugin-transform-literals": "^7.25.2", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-member-expression-literals": "^7.24.7", - "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-modules-systemjs": "^7.25.0", - "@babel/plugin-transform-modules-umd": "^7.24.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-new-target": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-object-super": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.25.4", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-property-literals": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-reserved-words": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.8", - "@babel/plugin-transform-unicode-escapes": "^7.24.7", - "@babel/plugin-transform-unicode-property-regex": "^7.24.7", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.4", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.25.9", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.25.9", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.25.9", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.25.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.25.9", + "@babel/plugin-transform-typeof-symbol": "^7.25.9", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.37.1", + "core-js-compat": "^3.38.1", "semver": "^6.3.1" }, "engines": { @@ -2062,15 +1845,15 @@ } }, "node_modules/@babel/preset-flow": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.24.7.tgz", - "integrity": "sha512-NL3Lo0NorCU607zU3NwRyJbpaB6E3t0xtd3LfAQKDfkeX4/ggcDXvkmkW42QWT5owUeW/jAe4hn+2qvkV1IbfQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.25.9.tgz", + "integrity": "sha512-EASHsAhE+SSlEzJ4bzfusnXSHiU+JfAYzj+jbw2vgQKgq5HrUr8qs+vgtiEL5dOH6sEweI+PNt2D7AqrDSHyqQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "@babel/plugin-transform-flow-strip-types": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-flow-strip-types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2095,18 +1878,18 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.7.tgz", - "integrity": "sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz", + "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "@babel/plugin-transform-react-display-name": "^7.24.7", - "@babel/plugin-transform-react-jsx": "^7.24.7", - "@babel/plugin-transform-react-jsx-development": "^7.24.7", - "@babel/plugin-transform-react-pure-annotations": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-react-display-name": "^7.25.9", + "@babel/plugin-transform-react-jsx": "^7.25.9", + "@babel/plugin-transform-react-jsx-development": "^7.25.9", + "@babel/plugin-transform-react-pure-annotations": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2143,9 +1926,9 @@ } }, "node_modules/@babel/traverse": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.3.tgz", - "integrity": "sha512-yTmc8J+Sj8yLzwr4PD5Xb/WF3bOYu2C2OoSZPzbuqRm4n98XirsbzaX+GloeO376UnSYIYJ4NCanwV5/ugZkwA==", + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", "dev": true, "license": "MIT", "dependencies": { @@ -3109,18 +2892,31 @@ "license": "MIT" }, "node_modules/@iconify/utils": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.33.tgz", - "integrity": "sha512-jP9h6v/g0BIZx0p7XGJJVtkVnydtbgTgt9mVNcGDYwaa7UhdHdI9dvoq+gKj9sijMSJKxUPEG2JyjsgXjxL7Kw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.2.1.tgz", + "integrity": "sha512-0/7J7hk4PqXmxo5PDBDxmnecw5PxklZJfNjIVG9FM0mEfVrvfudS22rYWsqVk6gR3UJ/mSYS90X4R3znXnqfNA==", "license": "MIT", "dependencies": { - "@antfu/install-pkg": "^0.4.0", + "@antfu/install-pkg": "^0.4.1", "@antfu/utils": "^0.7.10", "@iconify/types": "^2.0.0", - "debug": "^4.3.6", + "debug": "^4.4.0", + "globals": "^15.13.0", "kolorist": "^1.8.0", - "local-pkg": "^0.5.0", - "mlly": "^1.7.1" + "local-pkg": "^0.5.1", + "mlly": "^1.7.3" + } + }, + "node_modules/@iconify/utils/node_modules/globals": { + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@isaacs/cliui": { @@ -3214,9 +3010,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -3486,13 +3282,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz", - "integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", + "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.49.0" + "playwright": "1.49.1" }, "bin": { "playwright": "cli.js" @@ -3512,9 +3308,9 @@ } }, "node_modules/@primer/octicons": { - "version": "19.13.0", - "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.13.0.tgz", - "integrity": "sha512-U5g7Bv89At8qFXUy9MQlOUs4iPXR5XR4QPnSZWhmOn2oCgwf4LjdvfC+8OzhUFx2wC8k9TbRRAMVuCkqFLPlfQ==", + "version": "19.14.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.14.0.tgz", + "integrity": "sha512-9Ovw/xcUFHC/zbsNhr/Hkp1+m9XnNeQvnGHDHrI5vhlf6PRZVzSsdMnesV2xCzQh7jXP3EVRcaeXsUGlsZrfcA==", "license": "MIT", "dependencies": { "object-assign": "^4.1.1" @@ -3568,9 +3364,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.0.tgz", - "integrity": "sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", + "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==", "cpu": [ "arm" ], @@ -3582,9 +3378,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.0.tgz", - "integrity": "sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz", + "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==", "cpu": [ "arm64" ], @@ -3596,9 +3392,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.0.tgz", - "integrity": "sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz", + "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==", "cpu": [ "arm64" ], @@ -3610,9 +3406,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.0.tgz", - "integrity": "sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz", + "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==", "cpu": [ "x64" ], @@ -3624,9 +3420,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.0.tgz", - "integrity": "sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz", + "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==", "cpu": [ "arm64" ], @@ -3638,9 +3434,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.0.tgz", - "integrity": "sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz", + "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==", "cpu": [ "x64" ], @@ -3652,9 +3448,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.0.tgz", - "integrity": "sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz", + "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==", "cpu": [ "arm" ], @@ -3666,9 +3462,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.0.tgz", - "integrity": "sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz", + "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==", "cpu": [ "arm" ], @@ -3680,9 +3476,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.0.tgz", - "integrity": "sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz", + "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==", "cpu": [ "arm64" ], @@ -3694,9 +3490,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.0.tgz", - "integrity": "sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz", + "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==", "cpu": [ "arm64" ], @@ -3707,10 +3503,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz", + "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.0.tgz", - "integrity": "sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz", + "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==", "cpu": [ "ppc64" ], @@ -3722,9 +3532,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.0.tgz", - "integrity": "sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz", + "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==", "cpu": [ "riscv64" ], @@ -3736,9 +3546,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.0.tgz", - "integrity": "sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz", + "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==", "cpu": [ "s390x" ], @@ -3750,9 +3560,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.0.tgz", - "integrity": "sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz", + "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==", "cpu": [ "x64" ], @@ -3764,9 +3574,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.0.tgz", - "integrity": "sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz", + "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==", "cpu": [ "x64" ], @@ -3778,9 +3588,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.0.tgz", - "integrity": "sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz", + "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==", "cpu": [ "arm64" ], @@ -3792,9 +3602,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.0.tgz", - "integrity": "sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz", + "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==", "cpu": [ "ia32" ], @@ -3806,9 +3616,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.0.tgz", - "integrity": "sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz", + "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==", "cpu": [ "x64" ], @@ -3833,6 +3643,24 @@ "hasInstallScript": true, "license": "Apache-2.0" }, + "node_modules/@silverwind/vue-tsc": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@silverwind/vue-tsc/-/vue-tsc-2.1.13.tgz", + "integrity": "sha512-ejFxz1KZiUGAESbC+eURnjqt0N95qkU9eZU7W15wgF9zV+v2FEu3ZLduuXTC7D/Sg6lL1R/QjPfUbxbAbBQOsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~2.4.11", + "@vue/language-core": "2.1.10", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, "node_modules/@silverwind/vue3-calendar-heatmap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@silverwind/vue3-calendar-heatmap/-/vue3-calendar-heatmap-2.0.6.tgz", @@ -4365,9 +4193,9 @@ } }, "node_modules/@stylistic/eslint-plugin-js": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.11.0.tgz", - "integrity": "sha512-btchD0P3iij6cIk5RR5QMdEhtCCV0+L6cNheGhGCd//jaHILZMTi/EOqgEDAf1s4ZoViyExoToM+S2Iwa3U9DA==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.12.1.tgz", + "integrity": "sha512-5ybogtEgWIGCR6dMnaabztbWyVdAPDsf/5XOk6jBonWug875Q9/a6gm9QxnU3rhdyDEnckWKX7dduwYJMOWrVA==", "dev": true, "license": "MIT", "dependencies": { @@ -4682,6 +4510,13 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/doctrine": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", + "integrity": "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/dropzone": { "version": "5.7.9", "resolved": "https://registry.npmjs.org/@types/dropzone/-/dropzone-5.7.9.tgz", @@ -4729,9 +4564,9 @@ "license": "MIT" }, "node_modules/@types/geojson": { - "version": "7946.0.14", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", - "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "version": "7946.0.15", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.15.tgz", + "integrity": "sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA==", "license": "MIT" }, "node_modules/@types/hammerjs": { @@ -4794,9 +4629,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -4950,17 +4785,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz", - "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz", + "integrity": "sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/type-utils": "8.17.0", - "@typescript-eslint/utils": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/type-utils": "8.18.1", + "@typescript-eslint/utils": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -4975,25 +4810,21 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz", - "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.1.tgz", + "integrity": "sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/typescript-estree": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "debug": "^4.3.4" }, "engines": { @@ -5004,23 +4835,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", - "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.1.tgz", + "integrity": "sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0" + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5031,14 +4858,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz", - "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.1.tgz", + "integrity": "sha512-jAhTdK/Qx2NJPNOTxXpMwlOiSymtR2j283TtPqXkKBdH8OAMmhiUfP0kJjc/qSE51Xrq02Gj9NY7MwK+UxVwHQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.17.0", - "@typescript-eslint/utils": "8.17.0", + "@typescript-eslint/typescript-estree": "8.18.1", + "@typescript-eslint/utils": "8.18.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -5050,18 +4877,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", - "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.1.tgz", + "integrity": "sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==", "dev": true, "license": "MIT", "engines": { @@ -5073,14 +4896,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", - "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.1.tgz", + "integrity": "sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -5095,10 +4918,8 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { @@ -5118,16 +4939,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", - "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.1.tgz", + "integrity": "sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0" + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/typescript-estree": "8.18.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5137,22 +4958,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", - "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.1.tgz", + "integrity": "sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/types": "8.18.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -5164,9 +4981,9 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz", + "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==", "dev": true, "license": "ISC" }, @@ -5245,9 +5062,9 @@ } }, "node_modules/@vitest/mocker/node_modules/magic-string": { - "version": "0.30.14", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", - "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "license": "MIT", "dependencies": { @@ -5297,9 +5114,9 @@ } }, "node_modules/@vitest/snapshot/node_modules/magic-string": { - "version": "0.30.14", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", - "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "license": "MIT", "dependencies": { @@ -5335,30 +5152,30 @@ } }, "node_modules/@volar/language-core": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.10.tgz", - "integrity": "sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.11.tgz", + "integrity": "sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==", "dev": true, "license": "MIT", "dependencies": { - "@volar/source-map": "2.4.10" + "@volar/source-map": "2.4.11" } }, "node_modules/@volar/source-map": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.10.tgz", - "integrity": "sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.11.tgz", + "integrity": "sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==", "dev": true, "license": "MIT" }, "node_modules/@volar/typescript": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.10.tgz", - "integrity": "sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.11.tgz", + "integrity": "sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.10", + "@volar/language-core": "2.4.11", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } @@ -5404,9 +5221,9 @@ } }, "node_modules/@vue/compiler-sfc/node_modules/magic-string": { - "version": "0.30.14", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", - "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -5960,27 +5777,6 @@ "node": ">=8" } }, - "node_modules/array.prototype.findlast": { - "name": "@nolyfill/array.prototype.findlast", - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/@nolyfill/array.prototype.findlast/-/array.prototype.findlast-1.0.24.tgz", - "integrity": "sha512-yFCyZLs0iNNubzYnBINcOCJAiGtusxiR2F1DnwkOB1HQbWXl/zltkDIWIXO3cJxhQdngDlmM4ysTfyAfoB297g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nolyfill/shared": "1.0.24" - }, - "engines": { - "node": ">=12.4.0" - } - }, - "node_modules/array.prototype.findlast/node_modules/@nolyfill/shared": { - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/@nolyfill/shared/-/shared-1.0.24.tgz", - "integrity": "sha512-TGCpg3k5N7jj9AgU/1xFw9K1g4AC1vEE5ZFkW77oPNNLzprxT17PvFaNr/lr3BkkT5fJ5LNMntaTIq+pyWaeEA==", - "dev": true, - "license": "MIT" - }, "node_modules/array.prototype.findlastindex": { "name": "@nolyfill/array.prototype.findlastindex", "version": "1.0.24", @@ -6030,27 +5826,6 @@ "node": ">=12.4.0" } }, - "node_modules/array.prototype.tosorted": { - "name": "@nolyfill/array.prototype.tosorted", - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/@nolyfill/array.prototype.tosorted/-/array.prototype.tosorted-1.0.24.tgz", - "integrity": "sha512-lVo8TVDqaslOaOvEH7iL7glu/WdlX7ZrB+7FZY4BL25hg8TLHvg3e9pxafCp8vAQ96IOL+tdgBdfeoC7qLeQYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nolyfill/shared": "1.0.24" - }, - "engines": { - "node": ">=12.4.0" - } - }, - "node_modules/array.prototype.tosorted/node_modules/@nolyfill/shared": { - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/@nolyfill/shared/-/shared-1.0.24.tgz", - "integrity": "sha512-TGCpg3k5N7jj9AgU/1xFw9K1g4AC1vEE5ZFkW77oPNNLzprxT17PvFaNr/lr3BkkT5fJ5LNMntaTIq+pyWaeEA==", - "dev": true, - "license": "MIT" - }, "node_modules/as-table": { "version": "1.0.55", "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", @@ -6282,9 +6057,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "funding": [ { "type": "opencollective", @@ -6301,9 +6076,9 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { @@ -6402,9 +6177,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001686", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001686.tgz", - "integrity": "sha512-Y7deg0Aergpa24M3qLC5xjNklnKnhsmSyR/V89dLZ1n0ucJIFNs7PgR2Yfa/Zf6W79SbBicgtGxZr2juHkEUIA==", + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "funding": [ { "type": "opencollective", @@ -6915,13 +6690,13 @@ } }, "node_modules/css-tree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.1.tgz", - "integrity": "sha512-8Fxxv+tGhORlshCdCwnNJytvlvq46sOLSYEx2ZIGurahWvMucSRnyjPA3AmrMq4VPRYbHVpWj5VkiVasrM2H4Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.12.1", + "mdn-data": "2.12.2", "source-map-js": "^1.0.1" }, "engines": { @@ -7531,9 +7306,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -7567,20 +7342,6 @@ "node": ">=6" } }, - "node_modules/deep-equal": { - "name": "@nolyfill/deep-equal", - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@nolyfill/deep-equal/-/deep-equal-1.0.29.tgz", - "integrity": "sha512-EtrJBbOXHhVz8Y1gMYolKgPqh2u96UPqkZMHR0lcjn3y4TC4R7GuN3E4kEhDIpyK3q1+y7HHPHHkt5fGvW1crQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "dequal": "2.0.3" - }, - "engines": { - "node": ">=12.4.0" - } - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -7617,16 +7378,6 @@ "node": ">= 0.6.0" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -7719,9 +7470,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.2.tgz", - "integrity": "sha512-YMM+erhdZ2nkZ4fTNRTSI94mb7VG7uVF5vj5Zde7tImgnhZE3R6YW/IACGIHb2ux+QkEXMhe591N+5jWOmL4Zw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.3.tgz", + "integrity": "sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -7772,9 +7523,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.68", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.68.tgz", - "integrity": "sha512-FgMdJlma0OzUYlbrtZ4AeXjKxKPk6KT8WOP8BjcqxWtlg8qyJQjRzPJzUtUn5GBg1oQ26hFs7HOOHJMYiJRnvQ==", + "version": "1.5.74", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz", + "integrity": "sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -7861,27 +7612,6 @@ "node": ">=12.4.0" } }, - "node_modules/es-iterator-helpers": { - "name": "@nolyfill/es-iterator-helpers", - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@nolyfill/es-iterator-helpers/-/es-iterator-helpers-1.0.21.tgz", - "integrity": "sha512-i326KeE0nhW4STobcUhkxpXzZUddedCmfh7b/IyXR9kW0CFHiNNT80C3JSEy33mUlhZtk/ezX47nymcFxyBigg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nolyfill/shared": "1.0.21" - }, - "engines": { - "node": ">=12.4.0" - } - }, - "node_modules/es-iterator-helpers/node_modules/@nolyfill/shared": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@nolyfill/shared/-/shared-1.0.21.tgz", - "integrity": "sha512-qDc/NoaFU23E0hhiDPeUrvWzTXIPE+RbvRQtRWSeHHNmCIgYI9HS1jKzNYNJxv4jvZ/1VmM3L6rNVxbj+LBMNA==", - "dev": true, - "license": "MIT" - }, "node_modules/es-module-lexer": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", @@ -8223,9 +7953,9 @@ } }, "node_modules/eslint-plugin-github": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-github/-/eslint-plugin-github-5.1.3.tgz", - "integrity": "sha512-/0lyEqLLodXW3p+D9eYtmEp6e9DcJmV5FhnE9wNWV1bcqyShuZFXn5kOeJIvxSbFbdbrKiNO8zFiV/VXeSpRSw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-github/-/eslint-plugin-github-5.1.4.tgz", + "integrity": "sha512-j5IgIxsDoch06zJzeqPvenfzRXDKI9Z8YwfUg1pm2ay1q44tMSFwvEu6l0uEIrTpA3v8QdPyLr98LqDl1TIhSA==", "dev": true, "license": "MIT", "dependencies": { @@ -8315,9 +8045,9 @@ } }, "node_modules/eslint-plugin-github/node_modules/@eslint/js": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", - "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", "dev": true, "license": "MIT", "engines": { @@ -8353,9 +8083,9 @@ } }, "node_modules/eslint-plugin-github/node_modules/globals": { - "version": "15.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.13.0.tgz", - "integrity": "sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==", + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", "dev": true, "license": "MIT", "engines": { @@ -8430,16 +8160,18 @@ } }, "node_modules/eslint-plugin-import-x": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.5.0.tgz", - "integrity": "sha512-l0OTfnPF8RwmSXfjT75N8d6ZYLVrVYWpaGlgvVkVqFERCI5SyBfDP7QEMr3kt0zWi2sOa9EQ47clbdFsHkF83Q==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.6.1.tgz", + "integrity": "sha512-wluSUifMIb7UfwWXqx7Yx0lE/SGCcGXECLx/9bCmbY2nneLwvAZ4vkd1IXDjPKFvdcdUgr1BaRnaRpx3k2+Pfw==", "dev": true, "license": "MIT", "dependencies": { + "@types/doctrine": "^0.0.9", "@typescript-eslint/scope-manager": "^8.1.0", "@typescript-eslint/utils": "^8.1.0", "debug": "^4.3.4", "doctrine": "^3.0.0", + "enhanced-resolve": "^5.17.1", "eslint-import-resolver-node": "^0.3.9", "get-tsconfig": "^4.7.3", "is-glob": "^4.0.3", @@ -8668,117 +8400,6 @@ } } }, - "node_modules/eslint-plugin-react": { - "version": "7.36.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.36.1.tgz", - "integrity": "sha512-/qwbqNXZoq+VP30s1d4Nc1C5GTxjJQjk4Jzs4Wq2qzxFM7dSmuG2UkIjg2USMLh3A/aVcUNrK7v0J5U1XEGGwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.2", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.19", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.8", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.0", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.11", - "string.prototype.repeat": "^1.0.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/eslint-plugin-regexp": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.7.0.tgz", @@ -8802,256 +8423,33 @@ } }, "node_modules/eslint-plugin-sonarjs": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-2.0.4.tgz", - "integrity": "sha512-XVVAB/t0WSgHitHNajIcIDmviCO8kB9VSsrjy+4WUEVM3eieY9SDHEtCDaOMTjj6XMtcAr8BFDXCFaP005s+tg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.1.tgz", + "integrity": "sha512-RT6VgdPqizbMLmTryIc3fB169hRjvDFlqieSZEEswGtApPb4Dn9BndmN9qyfBV/By0hbseIX8zQWKBz5E7lyiQ==", "dev": true, "license": "LGPL-3.0-only", "dependencies": { - "@babel/core": "7.25.2", - "@babel/eslint-parser": "7.25.1", - "@babel/plugin-proposal-decorators": "7.24.7", - "@babel/preset-env": "7.25.4", - "@babel/preset-flow": "7.24.7", - "@babel/preset-react": "7.24.7", - "@eslint-community/regexpp": "4.11.1", - "@typescript-eslint/eslint-plugin": "7.16.1", - "@typescript-eslint/utils": "7.16.1", + "@babel/core": "7.26.0", + "@babel/eslint-parser": "7.25.9", + "@babel/plugin-proposal-decorators": "7.25.9", + "@babel/preset-env": "7.26.0", + "@babel/preset-flow": "7.25.9", + "@babel/preset-react": "7.26.3", + "@eslint-community/regexpp": "4.12.1", "builtin-modules": "3.3.0", "bytes": "3.1.2", - "eslint-plugin-import": "2.30.0", - "eslint-plugin-jsx-a11y": "6.10.0", - "eslint-plugin-react": "7.36.1", - "eslint-plugin-react-hooks": "4.6.2", - "eslint-scope": "8.1.0", "functional-red-black-tree": "1.0.1", "jsx-ast-utils": "3.3.5", - "minimatch": "10.0.1", + "minimatch": "9.0.5", "scslre": "0.3.0", "semver": "7.6.3", - "typescript": "5.6.2", - "vue-eslint-parser": "9.4.3" + "typescript": "^5" }, "peerDependencies": { "eslint": "^8.0.0 || ^9.0.0" } }, - "node_modules/eslint-plugin-sonarjs/node_modules/@eslint-community/regexpp": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz", - "integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/type-utils": "7.16.1", - "@typescript-eslint/utils": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "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-sonarjs/node_modules/@typescript-eslint/scope-manager": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz", - "integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", - "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/@typescript-eslint/type-utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz", - "integrity": "sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "7.16.1", - "@typescript-eslint/utils": "7.16.1", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", - "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz", - "integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.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-sonarjs/node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "node_modules/eslint-plugin-sonarjs/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", @@ -9067,400 +8465,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/eslint-plugin-sonarjs/node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "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-sonarjs/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "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-sonarjs/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/@typescript-eslint/utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz", - "integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/typescript-estree": "7.16.1" - }, - "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-sonarjs/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", - "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz", - "integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.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-sonarjs/node_modules/@typescript-eslint/utils/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz", - "integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.16.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-sonarjs/node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", - "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "deep-equal": "^2.0.5" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/eslint-plugin-import": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", - "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.9.0", - "hasown": "^2.0.2", - "is-core-module": "^2.15.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.0", - "semver": "^6.3.1", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/eslint-plugin-jsx-a11y": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.0.tgz", - "integrity": "sha512-ySOHvXX8eSN6zz8Bywacm7CvGNhUtdjvqfQDVe6020TUK34Cywkw7m0KsCCk1Qtm9G1FayfTN1/7mMYnYO2Bhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "aria-query": "~5.1.3", - "array-includes": "^3.1.8", - "array.prototype.flatmap": "^1.3.2", - "ast-types-flow": "^0.0.8", - "axe-core": "^4.10.0", - "axobject-query": "^4.1.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "es-iterator-helpers": "^1.0.19", - "hasown": "^2.0.2", - "jsx-ast-utils": "^3.3.5", - "language-tags": "^1.0.9", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "safe-regex-test": "^1.0.3", - "string.prototype.includes": "^2.0.0" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/eslint-scope": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", - "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-sonarjs/node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/eslint-plugin-unicorn": { "version": "56.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-56.0.1.tgz", @@ -9496,9 +8500,9 @@ } }, "node_modules/eslint-plugin-unicorn/node_modules/globals": { - "version": "15.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.13.0.tgz", - "integrity": "sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==", + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", "dev": true, "license": "MIT", "engines": { @@ -10557,9 +9561,9 @@ } }, "node_modules/htmx.org": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.3.tgz", - "integrity": "sha512-AeoJUAjkCVVajbfKX+3sVQBTCt8Ct4lif1T+z/tptTXo8+8yyq3QIMQQe/IT+R8ssfrO1I0DeX4CAronzCL6oA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.4.tgz", + "integrity": "sha512-HLxMCdfXDOJirs3vBZl/ZLoY+c7PfM4Ahr2Ad4YXh6d22T5ltbTXFFkpx9Tgb2vvmWFMbIc3LqN2ToNkZJvyYQ==", "license": "0BSD" }, "node_modules/iconv-lite": { @@ -10977,9 +9981,9 @@ } }, "node_modules/jiti": { - "version": "1.21.6", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", - "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -10999,10 +10003,9 @@ "license": "MIT" }, "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, "node_modules/js-types": { @@ -11048,9 +10051,9 @@ } }, "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", "bin": { @@ -11170,9 +10173,9 @@ "license": "MIT" }, "node_modules/katex": { - "version": "0.16.11", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz", - "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==", + "version": "0.16.18", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.18.tgz", + "integrity": "sha512-LRuk0rPdXrecAFwQucYjMiIs0JFefk6N1q/04mlw14aVIVgxq1FO0MA9RiIIGVaKOB5GIP5GH4aBBNraZERmaQ==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" @@ -11537,26 +10540,6 @@ "dev": true, "license": "MIT" }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/loose-envify/node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/loupe": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", @@ -11575,13 +10558,13 @@ } }, "node_modules/lru-cache": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", - "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", - "engines": { - "node": "20 || >=22" + "dependencies": { + "yallist": "^3.0.2" } }, "node_modules/magic-string": { @@ -11749,9 +10732,9 @@ } }, "node_modules/mdn-data": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.1.tgz", - "integrity": "sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", "dev": true, "license": "CC0-1.0" }, @@ -11941,9 +10924,9 @@ } }, "node_modules/monaco-editor": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.0.tgz", - "integrity": "sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==", + "version": "0.52.2", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", + "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", "license": "MIT" }, "node_modules/monaco-editor-webpack-plugin": { @@ -12061,9 +11044,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "license": "MIT" }, "node_modules/node-sarif-builder": { @@ -12081,9 +11064,9 @@ } }, "node_modules/nolyfill": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/nolyfill/-/nolyfill-1.0.42.tgz", - "integrity": "sha512-OG++zEsBoEbEmsuF/yjh91dLCTxt8tSJdV7ZzufpszpvG69opWsK9MfAnfjntVVQhreEeBNvwnftI/mCZVBh6w==", + "version": "1.0.43", + "resolved": "https://registry.npmjs.org/nolyfill/-/nolyfill-1.0.43.tgz", + "integrity": "sha512-wi8cCDStYl2cmKOWoF5Jv/0PZXkFcRgKpk9rtxWk+7/VDlI6WDBdPM5nUOsphZjibudajsyAh1+QQvxCyLnbMA==", "dev": true, "license": "MIT", "bin": { @@ -12180,20 +11163,6 @@ "node": ">=12.4.0" } }, - "node_modules/object.entries": { - "name": "@nolyfill/object.entries", - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/@nolyfill/object.entries/-/object.entries-1.0.28.tgz", - "integrity": "sha512-2t4PayP6Sx7Z20HJjcf8XhhPBO8/H31bwMdP0yEdDcxSXeEhl90Ibb9E3XKzSlcsGf43nXyfabHNrnfvdWE4Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nolyfill/shared": "1.0.28" - }, - "engines": { - "node": ">=12.4.0" - } - }, "node_modules/object.fromentries": { "name": "@nolyfill/object.fromentries", "version": "1.0.28", @@ -12416,6 +11385,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -12566,13 +11545,13 @@ } }, "node_modules/playwright": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz", - "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.49.0" + "playwright-core": "1.49.1" }, "bin": { "playwright": "cli.js" @@ -12585,9 +11564,9 @@ } }, "node_modules/playwright-core": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz", - "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -12677,6 +11656,13 @@ "node": "^12 || >=14" } }, + "node_modules/postcss-html/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/postcss-import": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", @@ -12792,9 +11778,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.1.0.tgz", - "integrity": "sha512-rm0bdSv4jC3BDma3s9H19ZddW0aHX6EoqwDYU2IfZhRN+53QrufTRo2IdkAbRqLx4R2IYbZnbjKKxg4VN5oU9Q==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "license": "MIT", "dependencies": { "icss-utils": "^5.0.0", @@ -13109,18 +12095,6 @@ "dev": true, "license": "Unlicense" }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/proto-props": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/proto-props/-/proto-props-2.0.0.tgz", @@ -13179,13 +12153,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, - "license": "MIT" - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -13420,6 +12387,19 @@ "node": ">=4" } }, + "node_modules/regexpu-core/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/regexpu-core/node_modules/regjsparser": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", @@ -13491,12 +12471,12 @@ } }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.9", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz", + "integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==", "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -13698,9 +12678,9 @@ "license": "ISC" }, "node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -13709,7 +12689,7 @@ "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", @@ -14107,34 +13087,6 @@ "node": ">=12.4.0" } }, - "node_modules/string.prototype.matchall": { - "name": "@nolyfill/string.prototype.matchall", - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/@nolyfill/string.prototype.matchall/-/string.prototype.matchall-1.0.28.tgz", - "integrity": "sha512-k74WKi7WmtRV847QWlY1ndg6XU1loeAyO9+NVoXrd7RL5lEjBtovp4CPZkifipBMBrZrZu2WwrQqkGrvLNZYpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nolyfill/shared": "1.0.28" - }, - "engines": { - "node": ">=12.4.0" - } - }, - "node_modules/string.prototype.repeat": { - "name": "@nolyfill/string.prototype.repeat", - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/@nolyfill/string.prototype.repeat/-/string.prototype.repeat-1.0.28.tgz", - "integrity": "sha512-8ww39xe0r4qki8HwAaXTRamO0KpkHHyYoG+PCOFGaBZ8rrlAKcGQcJhu5aB2axauggqsnUfU25j5snEC0aJvYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nolyfill/shared": "1.0.28" - }, - "engines": { - "node": ">=12.4.0" - } - }, "node_modules/string.prototype.trimend": { "name": "@nolyfill/string.prototype.trimend", "version": "1.0.28", @@ -14218,9 +13170,9 @@ "license": "ISC" }, "node_modules/stylelint": { - "version": "16.11.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.11.0.tgz", - "integrity": "sha512-zrl4IrKmjJQ+h9FoMp69UMCq5SxeHk0URhxUBj4d3ISzo/DplOFBJZc7t7Dr6otB+1bfbbKNLOmCDpzKSlW+Nw==", + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.12.0.tgz", + "integrity": "sha512-F8zZ3L/rBpuoBZRvI4JVT20ZanPLXfQLzMOZg1tzPflRVh9mKpOZ8qcSIhh1my3FjAjZWG4T2POwGnmn6a6hbg==", "dev": true, "funding": [ { @@ -14270,7 +13222,7 @@ "string-width": "^4.2.3", "supports-hyperlinks": "^3.1.0", "svg-tags": "^1.0.0", - "table": "^6.8.2", + "table": "^6.9.0", "write-file-atomic": "^5.0.1" }, "bin": { @@ -14783,9 +13735,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.16", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz", - "integrity": "sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==", + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -14829,9 +13781,9 @@ } }, "node_modules/terser": { - "version": "5.36.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", - "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", + "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -14847,16 +13799,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -14880,55 +13832,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -15135,9 +14038,9 @@ } }, "node_modules/type-fest": { - "version": "4.30.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.30.0.tgz", - "integrity": "sha512-G6zXWS1dLj6eagy6sVhOMQiLtJdxQBHIA9Z6HFUNLOlr6MFOgzV8wvmidtPONfPtEUv0uZsy77XJNzTAfwPDaA==", + "version": "4.30.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.30.2.tgz", + "integrity": "sha512-UJShLPYi1aWqCdq9HycOL/gwsuqda1OISdBO3t8RlXQC4QvtuIz4b5FCfe2dQIWEpmlRExKmcTBfP1r9bhY7ig==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -15161,15 +14064,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.17.0.tgz", - "integrity": "sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.1.tgz", + "integrity": "sha512-Mlaw6yxuaDEPQvb/2Qwu3/TfgeBHy9iTJ3mTwe7OvpPmF6KPQjVOfGyEJpPv6Ez2C34OODChhXrzYw/9phI0MQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.17.0", - "@typescript-eslint/parser": "8.17.0", - "@typescript-eslint/utils": "8.17.0" + "@typescript-eslint/eslint-plugin": "8.18.1", + "@typescript-eslint/parser": "8.18.1", + "@typescript-eslint/utils": "8.18.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -15179,18 +14082,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/typo-js": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.4.tgz", - "integrity": "sha512-Oy/k+tFle5NAA3J/yrrYGfvEnPVrDZ8s8/WCwjUE75k331QyKIsFss7byQ/PzBmXLY6h1moRnZbnaxWBe3I3CA==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.5.tgz", + "integrity": "sha512-F45vFWdGX8xahIk/sOp79z2NJs8ETMYsmMChm9D5Hlx3+9j7VnCyQyvij5MOCrNY3NNe8noSyokRjQRfq+Bc7A==", "license": "BSD-3-Clause" }, "node_modules/uc.micro": { @@ -15303,9 +14202,9 @@ } }, "node_modules/updates": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/updates/-/updates-16.4.0.tgz", - "integrity": "sha512-HtkG1MgXbQ5gpqu5eX4qVOwclMHbygiTYjSkFMVDEXuwb5clwkDh75xRb11PzRX9ozVOfcVUHl7lpBNDPquXrw==", + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/updates/-/updates-16.4.1.tgz", + "integrity": "sha512-dYsXzAISSiTFk6mg2ZB7IOlhIN5CO4qINxpbN7rrE9ffpuycq82SZ8rvLSc2QpBEO98BBY7wKm3d8SdA94o5TQ==", "dev": true, "license": "BSD-2-Clause", "bin": { @@ -15500,9 +14399,9 @@ } }, "node_modules/vite/node_modules/rollup": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.0.tgz", - "integrity": "sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", + "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", "dev": true, "license": "MIT", "dependencies": { @@ -15516,24 +14415,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.28.0", - "@rollup/rollup-android-arm64": "4.28.0", - "@rollup/rollup-darwin-arm64": "4.28.0", - "@rollup/rollup-darwin-x64": "4.28.0", - "@rollup/rollup-freebsd-arm64": "4.28.0", - "@rollup/rollup-freebsd-x64": "4.28.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.28.0", - "@rollup/rollup-linux-arm-musleabihf": "4.28.0", - "@rollup/rollup-linux-arm64-gnu": "4.28.0", - "@rollup/rollup-linux-arm64-musl": "4.28.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.28.0", - "@rollup/rollup-linux-riscv64-gnu": "4.28.0", - "@rollup/rollup-linux-s390x-gnu": "4.28.0", - "@rollup/rollup-linux-x64-gnu": "4.28.0", - "@rollup/rollup-linux-x64-musl": "4.28.0", - "@rollup/rollup-win32-arm64-msvc": "4.28.0", - "@rollup/rollup-win32-ia32-msvc": "4.28.0", - "@rollup/rollup-win32-x64-msvc": "4.28.0", + "@rollup/rollup-android-arm-eabi": "4.28.1", + "@rollup/rollup-android-arm64": "4.28.1", + "@rollup/rollup-darwin-arm64": "4.28.1", + "@rollup/rollup-darwin-x64": "4.28.1", + "@rollup/rollup-freebsd-arm64": "4.28.1", + "@rollup/rollup-freebsd-x64": "4.28.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", + "@rollup/rollup-linux-arm-musleabihf": "4.28.1", + "@rollup/rollup-linux-arm64-gnu": "4.28.1", + "@rollup/rollup-linux-arm64-musl": "4.28.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", + "@rollup/rollup-linux-riscv64-gnu": "4.28.1", + "@rollup/rollup-linux-s390x-gnu": "4.28.1", + "@rollup/rollup-linux-x64-gnu": "4.28.1", + "@rollup/rollup-linux-x64-musl": "4.28.1", + "@rollup/rollup-win32-arm64-msvc": "4.28.1", + "@rollup/rollup-win32-ia32-msvc": "4.28.1", + "@rollup/rollup-win32-x64-msvc": "4.28.1", "fsevents": "~2.3.2" } }, @@ -15604,9 +14504,9 @@ } }, "node_modules/vitest/node_modules/magic-string": { - "version": "0.30.14", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", - "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "license": "MIT", "dependencies": { @@ -15780,24 +14680,6 @@ } } }, - "node_modules/vue-tsc": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.1.10.tgz", - "integrity": "sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/typescript": "~2.4.8", - "@vue/language-core": "2.1.10", - "semver": "^7.5.4" - }, - "bin": { - "vue-tsc": "bin/vue-tsc.js" - }, - "peerDependencies": { - "typescript": ">=5.0.0" - } - }, "node_modules/watchpack": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", @@ -15822,9 +14704,9 @@ } }, "node_modules/webpack": { - "version": "5.97.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.0.tgz", - "integrity": "sha512-CWT8v7ShSfj7tGs4TLRtaOLmOCPWhoKEvp+eA7FVx8Xrjb3XfT0aXdxDItnRZmE8sHcH+a8ayDrJCOjXKxVFfQ==", + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", diff --git a/package.json b/package.json index 3a81e648228..9f5fbe3fdd8 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "@github/relative-time-element": "4.4.4", "@github/text-expander-element": "2.8.0", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", - "@primer/octicons": "19.13.0", + "@primer/octicons": "19.14.0", "@silverwind/vue3-calendar-heatmap": "2.0.6", "add-asset-webpack-plugin": "3.0.0", "ansi_up": "6.0.2", @@ -29,15 +29,15 @@ "esbuild-loader": "4.2.2", "escape-goat": "4.0.0", "fast-glob": "3.3.2", - "htmx.org": "2.0.3", + "htmx.org": "2.0.4", "idiomorph": "0.3.0", "jquery": "3.7.1", - "katex": "0.16.11", + "katex": "0.16.18", "license-checker-webpack-plugin": "0.2.1", "mermaid": "11.4.1", "mini-css-extract-plugin": "2.9.2", "minimatch": "10.0.1", - "monaco-editor": "0.52.0", + "monaco-editor": "0.52.2", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.0", "perfect-debounce": "1.0.0", @@ -46,7 +46,7 @@ "postcss-nesting": "13.0.1", "sortablejs": "1.15.6", "swagger-ui-dist": "5.18.2", - "tailwindcss": "3.4.16", + "tailwindcss": "3.4.17", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", "tippy.js": "6.3.7", @@ -59,15 +59,16 @@ "vue-bar-graph": "2.2.0", "vue-chartjs": "5.3.2", "vue-loader": "17.4.2", - "webpack": "5.97.0", + "webpack": "5.97.1", "webpack-cli": "5.1.4", "wrap-ansi": "9.0.0" }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "4.4.1", - "@playwright/test": "1.49.0", + "@playwright/test": "1.49.1", + "@silverwind/vue-tsc": "2.1.13", "@stoplight/spectral-cli": "6.14.2", - "@stylistic/eslint-plugin-js": "2.11.0", + "@stylistic/eslint-plugin-js": "2.12.1", "@stylistic/stylelint-plugin": "3.1.1", "@types/dropzone": "5.7.9", "@types/jquery": "3.5.32", @@ -79,19 +80,19 @@ "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/toastify-js": "1.12.3", - "@typescript-eslint/eslint-plugin": "8.17.0", - "@typescript-eslint/parser": "8.17.0", + "@typescript-eslint/eslint-plugin": "8.18.1", + "@typescript-eslint/parser": "8.18.1", "@vitejs/plugin-vue": "5.2.1", "eslint": "8.57.0", "eslint-import-resolver-typescript": "3.7.0", "eslint-plugin-array-func": "4.0.0", - "eslint-plugin-github": "5.1.3", - "eslint-plugin-import-x": "4.5.0", + "eslint-plugin-github": "5.1.4", + "eslint-plugin-import-x": "4.6.1", "eslint-plugin-no-jquery": "3.1.0", "eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-playwright": "2.1.0", "eslint-plugin-regexp": "2.7.0", - "eslint-plugin-sonarjs": "2.0.4", + "eslint-plugin-sonarjs": "3.0.1", "eslint-plugin-unicorn": "56.0.1", "eslint-plugin-vitest": "0.4.1", "eslint-plugin-vitest-globals": "1.5.0", @@ -100,18 +101,17 @@ "eslint-plugin-wc": "2.2.0", "happy-dom": "15.11.7", "markdownlint-cli": "0.43.0", - "nolyfill": "1.0.42", + "nolyfill": "1.0.43", "postcss-html": "1.7.0", - "stylelint": "16.11.0", + "stylelint": "16.12.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.6", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.3.2", - "type-fest": "4.30.0", - "updates": "16.4.0", + "type-fest": "4.30.2", + "updates": "16.4.1", "vite-string-plugin": "1.3.4", - "vitest": "2.1.8", - "vue-tsc": "2.1.10" + "vitest": "2.1.8" }, "browserslist": [ "defaults" diff --git a/routers/api/actions/runner/main_test.go b/routers/api/actions/runner/main_test.go new file mode 100644 index 00000000000..1e80a4f5caf --- /dev/null +++ b/routers/api/actions/runner/main_test.go @@ -0,0 +1,14 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package runner + +import ( + "testing" + + "code.gitea.io/gitea/models/unittest" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m) +} diff --git a/routers/api/actions/runner/utils.go b/routers/api/actions/runner/utils.go index ff6ec5bd54c..539be8d8890 100644 --- a/routers/api/actions/runner/utils.go +++ b/routers/api/actions/runner/utils.go @@ -162,28 +162,56 @@ func findTaskNeeds(ctx context.Context, task *actions_model.ActionTask) (map[str return nil, fmt.Errorf("FindRunJobs: %w", err) } - ret := make(map[string]*runnerv1.TaskNeed, len(needs)) + jobIDJobs := make(map[string][]*actions_model.ActionRunJob) for _, job := range jobs { - if !needs.Contains(job.JobID) { + jobIDJobs[job.JobID] = append(jobIDJobs[job.JobID], job) + } + + ret := make(map[string]*runnerv1.TaskNeed, len(needs)) + for jobID, jobsWithSameID := range jobIDJobs { + if !needs.Contains(jobID) { continue } - if job.TaskID == 0 || !job.Status.IsDone() { - // it shouldn't happen, or the job has been rerun - continue + var jobOutputs map[string]string + for _, job := range jobsWithSameID { + if job.TaskID == 0 || !job.Status.IsDone() { + // it shouldn't happen, or the job has been rerun + continue + } + got, err := actions_model.FindTaskOutputByTaskID(ctx, job.TaskID) + if err != nil { + return nil, fmt.Errorf("FindTaskOutputByTaskID: %w", err) + } + outputs := make(map[string]string, len(got)) + for _, v := range got { + outputs[v.OutputKey] = v.OutputValue + } + if len(jobOutputs) == 0 { + jobOutputs = outputs + } else { + jobOutputs = mergeTwoOutputs(outputs, jobOutputs) + } } - outputs := make(map[string]string) - got, err := actions_model.FindTaskOutputByTaskID(ctx, job.TaskID) - if err != nil { - return nil, fmt.Errorf("FindTaskOutputByTaskID: %w", err) - } - for _, v := range got { - outputs[v.OutputKey] = v.OutputValue - } - ret[job.JobID] = &runnerv1.TaskNeed{ - Outputs: outputs, - Result: runnerv1.Result(job.Status), + ret[jobID] = &runnerv1.TaskNeed{ + Outputs: jobOutputs, + Result: runnerv1.Result(actions_model.AggregateJobStatus(jobsWithSameID)), } } return ret, nil } + +// mergeTwoOutputs merges two outputs from two different ActionRunJobs +// Values with the same output name may be overridden. The user should ensure the output names are unique. +// See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#using-job-outputs-in-a-matrix-job +func mergeTwoOutputs(o1, o2 map[string]string) map[string]string { + ret := make(map[string]string, len(o1)) + for k1, v1 := range o1 { + if len(v1) > 0 { + ret[k1] = v1 + } else { + ret[k1] = o2[k1] + } + } + return ret +} diff --git a/routers/api/actions/runner/utils_test.go b/routers/api/actions/runner/utils_test.go new file mode 100644 index 00000000000..d7a6f84550f --- /dev/null +++ b/routers/api/actions/runner/utils_test.go @@ -0,0 +1,28 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package runner + +import ( + "context" + "testing" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func Test_findTaskNeeds(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 51}) + + ret, err := findTaskNeeds(context.Background(), task) + assert.NoError(t, err) + assert.Len(t, ret, 1) + assert.Contains(t, ret, "job1") + assert.Len(t, ret["job1"].Outputs, 2) + assert.Equal(t, "abc", ret["job1"].Outputs["output_a"]) + assert.Equal(t, "bbb", ret["job1"].Outputs["output_b"]) +} diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index b0f40084da3..3f4a73dcad5 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -9,10 +9,12 @@ import ( "fmt" "net/http" - "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" + org_model "code.gitea.io/gitea/models/organization" + packages_model "code.gitea.io/gitea/models/packages" + repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/password" "code.gitea.io/gitea/modules/log" @@ -247,7 +249,7 @@ func EditUser(ctx *context.APIContext) { } if err := user_service.UpdateUser(ctx, ctx.ContextUser, opts); err != nil { - if models.IsErrDeleteLastAdminUser(err) { + if user_model.IsErrDeleteLastAdminUser(err) { ctx.Error(http.StatusBadRequest, "LastAdmin", err) } else { ctx.Error(http.StatusInternalServerError, "UpdateUser", err) @@ -299,10 +301,10 @@ func DeleteUser(ctx *context.APIContext) { } if err := user_service.DeleteUser(ctx, ctx.ContextUser, ctx.FormBool("purge")); err != nil { - if models.IsErrUserOwnRepos(err) || - models.IsErrUserHasOrgs(err) || - models.IsErrUserOwnPackages(err) || - models.IsErrDeleteLastAdminUser(err) { + if repo_model.IsErrUserOwnRepos(err) || + org_model.IsErrUserHasOrgs(err) || + packages_model.IsErrUserOwnPackages(err) || + user_model.IsErrDeleteLastAdminUser(err) { ctx.Error(http.StatusUnprocessableEntity, "", err) } else { ctx.Error(http.StatusInternalServerError, "DeleteUser", err) diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index bc50960b61b..8164d2cfe99 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -573,19 +573,19 @@ func GetTeamRepos(ctx *context.APIContext) { // "$ref": "#/responses/notFound" team := ctx.Org.Team - teamRepos, err := organization.GetTeamRepositories(ctx, &organization.SearchTeamRepoOptions{ + teamRepos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{ ListOptions: utils.GetListOptions(ctx), TeamID: team.ID, }) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err) + ctx.Error(http.StatusInternalServerError, "GetTeamRepositories", err) return } repos := make([]*api.Repository, len(teamRepos)) for i, repo := range teamRepos { permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err) + ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) return } repos[i] = convert.ToRepo(ctx, repo, permission) diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 946203e97ec..9a31aec3144 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -9,7 +9,6 @@ import ( "fmt" "net/http" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/organization" @@ -24,6 +23,7 @@ import ( "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" pull_service "code.gitea.io/gitea/services/pull" + release_service "code.gitea.io/gitea/services/release" repo_service "code.gitea.io/gitea/services/repository" ) @@ -247,7 +247,7 @@ func CreateBranch(ctx *context.APIContext) { if err != nil { if git_model.IsErrBranchNotExist(err) { ctx.Error(http.StatusNotFound, "", "The old branch does not exist") - } else if models.IsErrTagAlreadyExists(err) { + } else if release_service.IsErrTagAlreadyExists(err) { ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.") } else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { ctx.Error(http.StatusConflict, "", "The branch already exists.") diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 959a4b952a1..83848b7add5 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -15,7 +15,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -30,6 +29,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/services/context" + pull_service "code.gitea.io/gitea/services/pull" archiver_service "code.gitea.io/gitea/services/repository/archiver" files_service "code.gitea.io/gitea/services/repository/files" ) @@ -736,12 +736,12 @@ func UpdateFile(ctx *context.APIContext) { } func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) { - if models.IsErrUserCannotCommit(err) || models.IsErrFilePathProtected(err) { + if files_service.IsErrUserCannotCommit(err) || pull_service.IsErrFilePathProtected(err) { ctx.Error(http.StatusForbidden, "Access", err) return } - if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || - models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) { + if git_model.IsErrBranchAlreadyExists(err) || files_service.IsErrFilenameInvalid(err) || pull_service.IsErrSHADoesNotMatch(err) || + files_service.IsErrFilePathInvalid(err) || files_service.IsErrRepoFileAlreadyExists(err) { ctx.Error(http.StatusUnprocessableEntity, "Invalid", err) return } @@ -887,17 +887,17 @@ func DeleteFile(ctx *context.APIContext) { } if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil { - if git.IsErrBranchNotExist(err) || models.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) { + if git.IsErrBranchNotExist(err) || files_service.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) { ctx.Error(http.StatusNotFound, "DeleteFile", err) return } else if git_model.IsErrBranchAlreadyExists(err) || - models.IsErrFilenameInvalid(err) || - models.IsErrSHADoesNotMatch(err) || - models.IsErrCommitIDDoesNotMatch(err) || - models.IsErrSHAOrCommitIDNotProvided(err) { + files_service.IsErrFilenameInvalid(err) || + pull_service.IsErrSHADoesNotMatch(err) || + files_service.IsErrCommitIDDoesNotMatch(err) || + files_service.IsErrSHAOrCommitIDNotProvided(err) { ctx.Error(http.StatusBadRequest, "DeleteFile", err) return - } else if models.IsErrUserCannotCommit(err) { + } else if files_service.IsErrUserCannotCommit(err) { ctx.Error(http.StatusForbidden, "DeleteFile", err) return } diff --git a/routers/api/v1/repo/issue_dependency.go b/routers/api/v1/repo/issue_dependency.go index 712c71a6823..ae7502c661b 100644 --- a/routers/api/v1/repo/issue_dependency.go +++ b/routers/api/v1/repo/issue_dependency.go @@ -338,7 +338,7 @@ func GetIssueBlocks(ctx *context.APIContext) { } skip := (page - 1) * limit - max := page * limit + maxNum := page * limit deps, err := issue.BlockingDependencies(ctx) if err != nil { @@ -352,7 +352,7 @@ func GetIssueBlocks(ctx *context.APIContext) { repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission for i, depMeta := range deps { - if i < skip || i >= max { + if i < skip || i >= maxNum { continue } diff --git a/routers/api/v1/repo/key.go b/routers/api/v1/repo/key.go index e5115980eb0..060694d085f 100644 --- a/routers/api/v1/repo/key.go +++ b/routers/api/v1/repo/key.go @@ -279,7 +279,7 @@ func DeleteDeploykey(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - if err := asymkey_service.DeleteDeployKey(ctx, ctx.Doer, ctx.PathParamInt64(":id")); err != nil { + if err := asymkey_service.DeleteDeployKey(ctx, ctx.Repo.Repository, ctx.PathParamInt64(":id")); err != nil { if asymkey_model.IsErrKeyAccessDenied(err) { ctx.Error(http.StatusForbidden, "", "You do not have access to this key") } else { diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go index dcbd8f3dd50..452825c0a3e 100644 --- a/routers/api/v1/repo/migrate.go +++ b/routers/api/v1/repo/migrate.go @@ -10,13 +10,13 @@ import ( "net/http" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -27,7 +27,6 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" - "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/migrations" notify_service "code.gitea.io/gitea/services/notify" repo_service "code.gitea.io/gitea/services/repository" @@ -104,7 +103,7 @@ func Migrate(ctx *context.APIContext) { } } - remoteAddr, err := forms.ParseRemoteAddr(form.CloneAddr, form.AuthUsername, form.AuthPassword) + remoteAddr, err := git.ParseRemoteAddr(form.CloneAddr, form.AuthUsername, form.AuthPassword) if err == nil { err = migrations.IsMigrateURLAllowed(remoteAddr, ctx.Doer) } @@ -237,7 +236,7 @@ func handleMigrateError(ctx *context.APIContext, repoOwner *user_model.User, err ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' contains invalid characters.", err.(db.ErrNameCharsNotAllowed).Name)) case db.IsErrNamePatternNotAllowed(err): ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(db.ErrNamePatternNotAllowed).Pattern)) - case models.IsErrInvalidCloneAddr(err): + case git.IsErrInvalidCloneAddr(err): ctx.Error(http.StatusUnprocessableEntity, "", err) case base.IsErrNotSupported(err): ctx.Error(http.StatusUnprocessableEntity, "", err) @@ -256,8 +255,8 @@ func handleMigrateError(ctx *context.APIContext, repoOwner *user_model.User, err } func handleRemoteAddrError(ctx *context.APIContext, err error) { - if models.IsErrInvalidCloneAddr(err) { - addrErr := err.(*models.ErrInvalidCloneAddr) + if git.IsErrInvalidCloneAddr(err) { + addrErr := err.(*git.ErrInvalidCloneAddr) switch { case addrErr.IsURLError: ctx.Error(http.StatusUnprocessableEntity, "", err) diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go index 310b82881ea..047203501e7 100644 --- a/routers/api/v1/repo/mirror.go +++ b/routers/api/v1/repo/mirror.go @@ -9,10 +9,10 @@ import ( "net/http" "time" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -20,7 +20,6 @@ import ( "code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" - "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/migrations" mirror_service "code.gitea.io/gitea/services/mirror" ) @@ -344,7 +343,7 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro return } - address, err := forms.ParseRemoteAddr(mirrorOption.RemoteAddress, mirrorOption.RemoteUsername, mirrorOption.RemotePassword) + address, err := git.ParseRemoteAddr(mirrorOption.RemoteAddress, mirrorOption.RemoteUsername, mirrorOption.RemotePassword) if err == nil { err = migrations.IsMigrateURLAllowed(address, ctx.ContextUser) } @@ -397,8 +396,8 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro } func HandleRemoteAddressError(ctx *context.APIContext, err error) { - if models.IsErrInvalidCloneAddr(err) { - addrErr := err.(*models.ErrInvalidCloneAddr) + if git.IsErrInvalidCloneAddr(err) { + addrErr := err.(*git.ErrInvalidCloneAddr) switch { case addrErr.IsProtocolInvalid: ctx.Error(http.StatusBadRequest, "CreatePushMirror", "Invalid mirror protocol") diff --git a/routers/api/v1/repo/patch.go b/routers/api/v1/repo/patch.go index 0e0601b7d93..5e24dcf8912 100644 --- a/routers/api/v1/repo/patch.go +++ b/routers/api/v1/repo/patch.go @@ -7,13 +7,13 @@ import ( "net/http" "time" - "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/context" + pull_service "code.gitea.io/gitea/services/pull" "code.gitea.io/gitea/services/repository/files" ) @@ -92,12 +92,12 @@ func ApplyDiffPatch(ctx *context.APIContext) { fileResponse, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts) if err != nil { - if models.IsErrUserCannotCommit(err) || models.IsErrFilePathProtected(err) { + if files.IsErrUserCannotCommit(err) || pull_service.IsErrFilePathProtected(err) { ctx.Error(http.StatusForbidden, "Access", err) return } - if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || - models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) { + if git_model.IsErrBranchAlreadyExists(err) || files.IsErrFilenameInvalid(err) || pull_service.IsErrSHADoesNotMatch(err) || + files.IsErrFilePathInvalid(err) || files.IsErrRepoFileAlreadyExists(err) { ctx.Error(http.StatusUnprocessableEntity, "Invalid", err) return } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index cfd84fd5c3e..9971e4512fc 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -12,7 +12,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" activities_model "code.gitea.io/gitea/models/activities" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -803,7 +802,7 @@ func EditPullRequest(ctx *context.APIContext) { } else if issues_model.IsErrIssueIsClosed(err) { ctx.Error(http.StatusUnprocessableEntity, "IsErrIssueIsClosed", err) return - } else if models.IsErrPullRequestHasMerged(err) { + } else if pull_service.IsErrPullRequestHasMerged(err) { ctx.Error(http.StatusConflict, "IsErrPullRequestHasMerged", err) return } @@ -979,7 +978,7 @@ func MergePullRequest(ctx *context.APIContext) { ctx.Error(http.StatusMethodNotAllowed, "PR is a work in progress", "Work in progress PRs cannot be merged") } else if errors.Is(err, pull_service.ErrNotMergeableState) { ctx.Error(http.StatusMethodNotAllowed, "PR not in mergeable state", "Please try again later") - } else if models.IsErrDisallowedToMerge(err) { + } else if pull_service.IsErrDisallowedToMerge(err) { ctx.Error(http.StatusMethodNotAllowed, "PR is not ready to be merged", err) } else if asymkey_service.IsErrWontSign(err) { ctx.Error(http.StatusMethodNotAllowed, fmt.Sprintf("Protected branch %s requires signed commits but this merge would not be signed", pr.BaseBranch), err) @@ -992,7 +991,7 @@ func MergePullRequest(ctx *context.APIContext) { // handle manually-merged mark if manuallyMerged { if err := pull_service.MergedManually(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil { - if models.IsErrInvalidMergeStyle(err) { + if pull_service.IsErrInvalidMergeStyle(err) { ctx.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do))) return } @@ -1042,20 +1041,20 @@ func MergePullRequest(ctx *context.APIContext) { } if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil { - if models.IsErrInvalidMergeStyle(err) { + if pull_service.IsErrInvalidMergeStyle(err) { ctx.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do))) - } else if models.IsErrMergeConflicts(err) { - conflictError := err.(models.ErrMergeConflicts) + } else if pull_service.IsErrMergeConflicts(err) { + conflictError := err.(pull_service.ErrMergeConflicts) ctx.JSON(http.StatusConflict, conflictError) - } else if models.IsErrRebaseConflicts(err) { - conflictError := err.(models.ErrRebaseConflicts) + } else if pull_service.IsErrRebaseConflicts(err) { + conflictError := err.(pull_service.ErrRebaseConflicts) ctx.JSON(http.StatusConflict, conflictError) - } else if models.IsErrMergeUnrelatedHistories(err) { - conflictError := err.(models.ErrMergeUnrelatedHistories) + } else if pull_service.IsErrMergeUnrelatedHistories(err) { + conflictError := err.(pull_service.ErrMergeUnrelatedHistories) ctx.JSON(http.StatusConflict, conflictError) } else if git.IsErrPushOutOfDate(err) { ctx.Error(http.StatusConflict, "Merge", "merge push out of date") - } else if models.IsErrSHADoesNotMatch(err) { + } else if pull_service.IsErrSHADoesNotMatch(err) { ctx.Error(http.StatusConflict, "Merge", "head out of date") } else if git.IsErrPushRejected(err) { errPushRej := err.(*git.ErrPushRejected) @@ -1217,10 +1216,10 @@ func UpdatePullRequest(ctx *context.APIContext) { message := fmt.Sprintf("Merge branch '%s' into %s", pr.BaseBranch, pr.HeadBranch) if err = pull_service.Update(ctx, pr, ctx.Doer, message, rebase); err != nil { - if models.IsErrMergeConflicts(err) { + if pull_service.IsErrMergeConflicts(err) { ctx.Error(http.StatusConflict, "Update", "merge failed because of conflict") return - } else if models.IsErrRebaseConflicts(err) { + } else if pull_service.IsErrRebaseConflicts(err) { ctx.Error(http.StatusConflict, "Update", "rebase failed because of conflict") return } diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index 478bdbd797a..141f8121729 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -7,7 +7,6 @@ import ( "fmt" "net/http" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" @@ -250,7 +249,7 @@ func CreateRelease(ctx *context.APIContext) { if err := release_service.CreateRelease(ctx.Repo.GitRepo, rel, nil, ""); err != nil { if repo_model.IsErrReleaseAlreadyExist(err) { ctx.Error(http.StatusConflict, "ReleaseAlreadyExist", err) - } else if models.IsErrProtectedTagName(err) { + } else if release_service.IsErrProtectedTagName(err) { ctx.Error(http.StatusUnprocessableEntity, "ProtectedTagName", err) } else if git.IsErrNotExist(err) { ctx.Error(http.StatusNotFound, "ErrNotExist", fmt.Errorf("target \"%v\" not found: %w", rel.Target, err)) @@ -408,7 +407,7 @@ func DeleteRelease(ctx *context.APIContext) { return } if err := release_service.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, false); err != nil { - if models.IsErrProtectedTagName(err) { + if release_service.IsErrProtectedTagName(err) { ctx.Error(http.StatusUnprocessableEntity, "delTag", "user not allowed to delete protected tag") return } diff --git a/routers/api/v1/repo/release_tags.go b/routers/api/v1/repo/release_tags.go index 6df47af8d98..99f7a8cbf21 100644 --- a/routers/api/v1/repo/release_tags.go +++ b/routers/api/v1/repo/release_tags.go @@ -6,11 +6,10 @@ package repo import ( "net/http" - "code.gitea.io/gitea/models" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" - releaseservice "code.gitea.io/gitea/services/release" + release_service "code.gitea.io/gitea/services/release" ) // GetReleaseByTag get a single release of a repository by tag name @@ -112,8 +111,8 @@ func DeleteReleaseByTag(ctx *context.APIContext) { return } - if err = releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, release, ctx.Doer, false); err != nil { - if models.IsErrProtectedTagName(err) { + if err = release_service.DeleteReleaseByID(ctx, ctx.Repo.Repository, release, ctx.Doer, false); err != nil { + if release_service.IsErrProtectedTagName(err) { ctx.Error(http.StatusUnprocessableEntity, "delTag", "user not allowed to delete protected tag") return } diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go index a72df78666e..fe0910c735a 100644 --- a/routers/api/v1/repo/tag.go +++ b/routers/api/v1/repo/tag.go @@ -9,7 +9,6 @@ import ( "net/http" "strings" - "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" @@ -19,7 +18,7 @@ import ( "code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" - releaseservice "code.gitea.io/gitea/services/release" + release_service "code.gitea.io/gitea/services/release" ) // ListTags list all the tags of a repository @@ -205,12 +204,12 @@ func CreateTag(ctx *context.APIContext) { return } - if err := releaseservice.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, commit.ID.String(), form.TagName, form.Message); err != nil { - if models.IsErrTagAlreadyExists(err) { + if err := release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, commit.ID.String(), form.TagName, form.Message); err != nil { + if release_service.IsErrTagAlreadyExists(err) { ctx.Error(http.StatusConflict, "tag exist", err) return } - if models.IsErrProtectedTagName(err) { + if release_service.IsErrProtectedTagName(err) { ctx.Error(http.StatusUnprocessableEntity, "CreateNewTag", "user not allowed to create protected tag") return } @@ -280,8 +279,8 @@ func DeleteTag(ctx *context.APIContext) { return } - if err = releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, tag, ctx.Doer, true); err != nil { - if models.IsErrProtectedTagName(err) { + if err = release_service.DeleteReleaseByID(ctx, ctx.Repo.Repository, tag, ctx.Doer, true); err != nil { + if release_service.IsErrProtectedTagName(err) { ctx.Error(http.StatusUnprocessableEntity, "delTag", "user not allowed to delete protected tag") return } diff --git a/routers/api/v1/repo/teams.go b/routers/api/v1/repo/teams.go index 82ecaf30201..42fb0a1d753 100644 --- a/routers/api/v1/repo/teams.go +++ b/routers/api/v1/repo/teams.go @@ -42,7 +42,7 @@ func ListTeams(ctx *context.APIContext) { return } - teams, err := organization.GetRepoTeams(ctx, ctx.Repo.Repository) + teams, err := organization.GetRepoTeams(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID) if err != nil { ctx.InternalServerError(err) return diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go index 776b336761f..787ec34404f 100644 --- a/routers/api/v1/repo/transfer.go +++ b/routers/api/v1/repo/transfer.go @@ -8,7 +8,6 @@ import ( "fmt" "net/http" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -108,7 +107,7 @@ func Transfer(ctx *context.APIContext) { oldFullname := ctx.Repo.Repository.FullName() if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, ctx.Repo.Repository, teams); err != nil { - if models.IsErrRepoTransferInProgress(err) { + if repo_model.IsErrRepoTransferInProgress(err) { ctx.Error(http.StatusConflict, "StartRepositoryTransfer", err) return } @@ -213,9 +212,9 @@ func RejectTransfer(ctx *context.APIContext) { } func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error { - repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) + repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) if err != nil { - if models.IsErrNoPendingTransfer(err) { + if repo_model.IsErrNoPendingTransfer(err) { ctx.NotFound() return nil } diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index c7065c1d9df..f9906ed250d 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -308,7 +308,7 @@ func ListWikiPages(ctx *context.APIContext) { } skip := (page - 1) * limit - max := page * limit + maxNum := page * limit entries, err := commit.ListEntries() if err != nil { @@ -317,7 +317,7 @@ func ListWikiPages(ctx *context.APIContext) { } pages := make([]*api.WikiPageMetaData, 0, len(entries)) for i, entry := range entries { - if i < skip || i >= max || !entry.IsRegular() { + if i < skip || i >= maxNum || !entry.IsRegular() { continue } c, err := wikiRepo.GetCommitByPath(entry.Name()) diff --git a/routers/private/hook_post_receive_test.go b/routers/private/hook_post_receive_test.go index 658557d3cfc..a089739d156 100644 --- a/routers/private/hook_post_receive_test.go +++ b/routers/private/hook_post_receive_test.go @@ -39,7 +39,7 @@ func TestHandlePullRequestMerging(t *testing.T) { }, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, []*repo_module.PushUpdateOptions{ {NewCommitID: "01234567"}, }) - assert.Equal(t, 0, len(resp.Body.String())) + assert.Empty(t, resp.Body.String()) pr, err = issues_model.GetPullRequestByID(db.DefaultContext, pr.ID) assert.NoError(t, err) assert.True(t, pr.HasMerged) diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 73fe9b886cf..eb7bb2b480a 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -8,7 +8,6 @@ import ( "net/http" "os" - "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -237,7 +236,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r if len(globs) > 0 { _, err := pull_service.CheckFileProtection(gitRepo, branchName, oldCommitID, newCommitID, globs, 1, ctx.env) if err != nil { - if !models.IsErrFilePathProtected(err) { + if !pull_service.IsErrFilePathProtected(err) { log.Error("Unable to check file protection for commits from %s to %s in %-v: %v", oldCommitID, newCommitID, repo, err) ctx.JSON(http.StatusInternalServerError, private.Response{ Err: fmt.Sprintf("Unable to check file protection for commits from %s to %s: %v", oldCommitID, newCommitID, err), @@ -246,7 +245,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r } changedProtectedfiles = true - protectedFilePath = err.(models.ErrFilePathProtected).Path + protectedFilePath = err.(pull_service.ErrFilePathProtected).Path } } @@ -374,7 +373,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r // Check all status checks and reviews are ok if err := pull_service.CheckPullBranchProtections(ctx, pr, true); err != nil { - if models.IsErrDisallowedToMerge(err) { + if pull_service.IsErrDisallowedToMerge(err) { log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", ctx.opts.UserID, branchName, repo, pr.Index, err.Error()) ctx.JSON(http.StatusForbidden, private.Response{ UserMsg: fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, ctx.opts.PullRequestID, err.Error()), diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index a6b0b5c78bb..1eaa37bfbde 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -11,10 +11,10 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" org_model "code.gitea.io/gitea/models/organization" + packages_model "code.gitea.io/gitea/models/packages" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/password" @@ -446,7 +446,7 @@ func EditUserPost(ctx *context.Context) { } if err := user_service.UpdateUser(ctx, u, opts); err != nil { - if models.IsErrDeleteLastAdminUser(err) { + if user_model.IsErrDeleteLastAdminUser(err) { ctx.RenderWithErr(ctx.Tr("auth.last_admin"), tplUserEdit, &form) } else { ctx.ServerError("UpdateUser", err) @@ -501,16 +501,16 @@ func DeleteUser(ctx *context.Context) { if err = user_service.DeleteUser(ctx, u, ctx.FormBool("purge")); err != nil { switch { - case models.IsErrUserOwnRepos(err): + case repo_model.IsErrUserOwnRepos(err): ctx.Flash.Error(ctx.Tr("admin.users.still_own_repo")) ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam(":userid"))) - case models.IsErrUserHasOrgs(err): + case org_model.IsErrUserHasOrgs(err): ctx.Flash.Error(ctx.Tr("admin.users.still_has_org")) ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam(":userid"))) - case models.IsErrUserOwnPackages(err): + case packages_model.IsErrUserOwnPackages(err): ctx.Flash.Error(ctx.Tr("admin.users.still_own_packages")) ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam(":userid"))) - case models.IsErrDeleteLastAdminUser(err): + case user_model.IsErrDeleteLastAdminUser(err): ctx.Flash.Error(ctx.Tr("auth.last_admin")) ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam(":userid"))) default: diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 494ada4323a..551019c7170 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -8,8 +8,8 @@ import ( "net/http" "net/url" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" + packages_model "code.gitea.io/gitea/models/packages" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" @@ -178,10 +178,10 @@ func SettingsDelete(ctx *context.Context) { } if err := org_service.DeleteOrganization(ctx, ctx.Org.Organization, false); err != nil { - if models.IsErrUserOwnRepos(err) { + if repo_model.IsErrUserOwnRepos(err) { ctx.Flash.Error(ctx.Tr("form.org_still_own_repo")) ctx.Redirect(ctx.Org.OrgLink + "/settings/delete") - } else if models.IsErrUserOwnPackages(err) { + } else if packages_model.IsErrUserOwnPackages(err) { ctx.Flash.Error(ctx.Tr("form.org_still_own_packages")) ctx.Redirect(ctx.Org.OrgLink + "/settings/delete") } else { diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index bd788321032..b03b18bd9c7 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -410,11 +410,15 @@ func TeamRepositories(ctx *context.Context) { return } - if err := ctx.Org.Team.LoadRepositories(ctx); err != nil { - ctx.ServerError("GetRepositories", err) + repos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{ + TeamID: ctx.Org.Team.ID, + }) + if err != nil { + ctx.ServerError("GetTeamRepositories", err) return } ctx.Data["Units"] = unit_model.Units + ctx.Data["TeamRepos"] = repos ctx.HTML(http.StatusOK, tplTeamRepositories) } diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index 7ed37ea26b2..1de18359365 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -5,6 +5,7 @@ package actions import ( "bytes" + stdCtx "context" "fmt" "net/http" "slices" @@ -245,7 +246,7 @@ func List(ctx *context.Context) { return } - if err := loadIsRefDeleted(ctx, runs); err != nil { + if err := loadIsRefDeleted(ctx, ctx.Repo.Repository.ID, runs); err != nil { log.Error("LoadIsRefDeleted", err) } @@ -273,7 +274,7 @@ func List(ctx *context.Context) { // loadIsRefDeleted loads the IsRefDeleted field for each run in the list. // TODO: move this function to models/actions/run_list.go but now it will result in a circular import. -func loadIsRefDeleted(ctx *context.Context, runs actions_model.RunList) error { +func loadIsRefDeleted(ctx stdCtx.Context, repoID int64, runs actions_model.RunList) error { branches := make(container.Set[string], len(runs)) for _, run := range runs { refName := git.RefName(run.Ref) @@ -285,14 +286,14 @@ func loadIsRefDeleted(ctx *context.Context, runs actions_model.RunList) error { return nil } - branchInfos, err := git_model.GetBranches(ctx, ctx.Repo.Repository.ID, branches.Values(), false) + branchInfos, err := git_model.GetBranches(ctx, repoID, branches.Values(), false) if err != nil { return err } branchSet := git_model.BranchesToNamesSet(branchInfos) for _, run := range runs { refName := git.RefName(run.Ref) - if refName.IsBranch() && !branchSet.Contains(run.Ref) { + if refName.IsBranch() && !branchSet.Contains(refName.ShortName()) { run.IsRefDeleted = true } } diff --git a/routers/web/repo/actions/actions_test.go b/routers/web/repo/actions/actions_test.go index 194704d14eb..6a976ed65c0 100644 --- a/routers/web/repo/actions/actions_test.go +++ b/routers/web/repo/actions/actions_test.go @@ -7,6 +7,10 @@ import ( "strings" "testing" + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + unittest "code.gitea.io/gitea/models/unittest" + act_model "github.com/nektos/act/pkg/model" "github.com/stretchr/testify/assert" ) @@ -154,3 +158,21 @@ func TestReadWorkflow_WorkflowDispatchConfig(t *testing.T) { Type: "boolean", }, workflowDispatch.Inputs[2]) } + +func Test_loadIsRefDeleted(t *testing.T) { + unittest.PrepareTestEnv(t) + + runs, total, err := db.FindAndCount[actions_model.ActionRun](db.DefaultContext, + actions_model.FindRunOptions{RepoID: 4, Ref: "refs/heads/test"}) + assert.NoError(t, err) + assert.Len(t, runs, 1) + assert.EqualValues(t, 1, total) + for _, run := range runs { + assert.False(t, run.IsRefDeleted) + } + + assert.NoError(t, loadIsRefDeleted(db.DefaultContext, 4, runs)) + for _, run := range runs { + assert.True(t, run.IsRefDeleted) + } +} diff --git a/routers/web/repo/actions/main_test.go b/routers/web/repo/actions/main_test.go new file mode 100644 index 00000000000..a82f9c6672e --- /dev/null +++ b/routers/web/repo/actions/main_test.go @@ -0,0 +1,14 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "testing" + + "code.gitea.io/gitea/models/unittest" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m) +} diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index b711038da06..ba17fa427d1 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -205,8 +205,11 @@ func ViewPost(ctx *context_module.Context) { } } + // TODO: "ComposeMetas" (usually for comment) is not quite right, but it is still the same as what template "RenderCommitMessage" does. + // need to be refactored together in the future metas := ctx.Repo.Repository.ComposeMetas(ctx) + // the title for the "run" is from the commit message resp.State.Run.Title = run.Title resp.State.Run.TitleHTML = templates.NewRenderUtils(ctx).RenderCommitMessage(run.Title, metas) resp.State.Run.Link = run.Link() diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index c918cd7a72f..3e1c6fc61cb 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -11,7 +11,6 @@ import ( "net/url" "strings" - "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -26,6 +25,7 @@ import ( "code.gitea.io/gitea/routers/utils" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" + pull_service "code.gitea.io/gitea/services/pull" release_service "code.gitea.io/gitea/services/release" repo_service "code.gitea.io/gitea/services/repository" ) @@ -203,14 +203,14 @@ func CreateBranch(ctx *context.Context) { err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.CommitID, form.NewBranchName) } if err != nil { - if models.IsErrProtectedTagName(err) { + if release_service.IsErrProtectedTagName(err) { ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected")) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) return } - if models.IsErrTagAlreadyExists(err) { - e := err.(models.ErrTagAlreadyExists) + if release_service.IsErrTagAlreadyExists(err) { + e := err.(release_service.ErrTagAlreadyExists) ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName)) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) return @@ -267,7 +267,7 @@ func MergeUpstream(ctx *context.Context) { if errors.Is(err, util.ErrNotExist) { ctx.JSONError(ctx.Tr("error.not_found")) return - } else if models.IsErrMergeConflicts(err) { + } else if pull_service.IsErrMergeConflicts(err) { ctx.JSONError(ctx.Tr("repo.pulls.merge_conflict")) return } diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go index 61aff78d498..71671ec628d 100644 --- a/routers/web/repo/cherry_pick.go +++ b/routers/web/repo/cherry_pick.go @@ -8,7 +8,6 @@ import ( "errors" "strings" - "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/base" @@ -131,7 +130,7 @@ func CherryPickPost(ctx *context.Context) { ctx.Data["Err_NewBranchName"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form) return - } else if models.IsErrCommitIDDoesNotMatch(err) { + } else if files.IsErrCommitIDDoesNotMatch(err) { ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplPatchFile, &form) return } @@ -168,7 +167,7 @@ func CherryPickPost(ctx *context.Context) { ctx.Data["Err_NewBranchName"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form) return - } else if models.IsErrCommitIDDoesNotMatch(err) { + } else if files.IsErrCommitIDDoesNotMatch(err) { ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplPatchFile, &form) return } diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 9396115b0d2..afc69bffdfd 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -10,7 +10,6 @@ import ( "path" "strings" - "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -303,12 +302,12 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b } else if git_model.IsErrLFSFileLocked(err) { ctx.Data["Err_TreePath"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.upload_file_is_locked", err.(git_model.ErrLFSFileLocked).Path, err.(git_model.ErrLFSFileLocked).UserName), tplEditFile, &form) - } else if models.IsErrFilenameInvalid(err) { + } else if files_service.IsErrFilenameInvalid(err) { ctx.Data["Err_TreePath"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", form.TreePath), tplEditFile, &form) - } else if models.IsErrFilePathInvalid(err) { + } else if files_service.IsErrFilePathInvalid(err) { ctx.Data["Err_TreePath"] = true - if fileErr, ok := err.(models.ErrFilePathInvalid); ok { + if fileErr, ok := err.(files_service.ErrFilePathInvalid); ok { switch fileErr.Type { case git.EntryModeSymlink: ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplEditFile, &form) @@ -322,7 +321,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b } else { ctx.Error(http.StatusInternalServerError, err.Error()) } - } else if models.IsErrRepoFileAlreadyExists(err) { + } else if files_service.IsErrRepoFileAlreadyExists(err) { ctx.Data["Err_TreePath"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.file_already_exists", form.TreePath), tplEditFile, &form) } else if git.IsErrBranchNotExist(err) { @@ -340,7 +339,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b } else { ctx.Error(http.StatusInternalServerError, err.Error()) } - } else if models.IsErrCommitIDDoesNotMatch(err) { + } else if files_service.IsErrCommitIDDoesNotMatch(err) { ctx.RenderWithErr(ctx.Tr("repo.editor.commit_id_not_matching"), tplEditFile, &form) } else if git.IsErrPushOutOfDate(err) { ctx.RenderWithErr(ctx.Tr("repo.editor.push_out_of_date"), tplEditFile, &form) @@ -506,14 +505,14 @@ func DeleteFilePost(ctx *context.Context) { Signoff: form.Signoff, }); err != nil { // This is where we handle all the errors thrown by repofiles.DeleteRepoFile - if git.IsErrNotExist(err) || models.IsErrRepoFileDoesNotExist(err) { + if git.IsErrNotExist(err) || files_service.IsErrRepoFileDoesNotExist(err) { ctx.RenderWithErr(ctx.Tr("repo.editor.file_deleting_no_longer_exists", ctx.Repo.TreePath), tplDeleteFile, &form) - } else if models.IsErrFilenameInvalid(err) { + } else if files_service.IsErrFilenameInvalid(err) { ctx.Data["Err_TreePath"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", ctx.Repo.TreePath), tplDeleteFile, &form) - } else if models.IsErrFilePathInvalid(err) { + } else if files_service.IsErrFilePathInvalid(err) { ctx.Data["Err_TreePath"] = true - if fileErr, ok := err.(models.ErrFilePathInvalid); ok { + if fileErr, ok := err.(files_service.ErrFilePathInvalid); ok { switch fileErr.Type { case git.EntryModeSymlink: ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplDeleteFile, &form) @@ -541,7 +540,7 @@ func DeleteFilePost(ctx *context.Context) { } else { ctx.Error(http.StatusInternalServerError, err.Error()) } - } else if models.IsErrCommitIDDoesNotMatch(err) || git.IsErrPushOutOfDate(err) { + } else if files_service.IsErrCommitIDDoesNotMatch(err) || git.IsErrPushOutOfDate(err) { ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_deleting", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(form.LastCommit)+"..."+util.PathEscapeSegments(ctx.Repo.CommitID)), tplDeleteFile, &form) } else if git.IsErrPushRejected(err) { errPushRej := err.(*git.ErrPushRejected) @@ -715,12 +714,12 @@ func UploadFilePost(ctx *context.Context) { if git_model.IsErrLFSFileLocked(err) { ctx.Data["Err_TreePath"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.upload_file_is_locked", err.(git_model.ErrLFSFileLocked).Path, err.(git_model.ErrLFSFileLocked).UserName), tplUploadFile, &form) - } else if models.IsErrFilenameInvalid(err) { + } else if files_service.IsErrFilenameInvalid(err) { ctx.Data["Err_TreePath"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", form.TreePath), tplUploadFile, &form) - } else if models.IsErrFilePathInvalid(err) { + } else if files_service.IsErrFilePathInvalid(err) { ctx.Data["Err_TreePath"] = true - fileErr := err.(models.ErrFilePathInvalid) + fileErr := err.(files_service.ErrFilePathInvalid) switch fileErr.Type { case git.EntryModeSymlink: ctx.RenderWithErr(ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path), tplUploadFile, &form) @@ -731,7 +730,7 @@ func UploadFilePost(ctx *context.Context) { default: ctx.Error(http.StatusInternalServerError, err.Error()) } - } else if models.IsErrRepoFileAlreadyExists(err) { + } else if files_service.IsErrRepoFileAlreadyExists(err) { ctx.Data["Err_TreePath"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.file_already_exists", form.TreePath), tplUploadFile, &form) } else if git.IsErrBranchNotExist(err) { diff --git a/routers/web/repo/fork.go b/routers/web/repo/fork.go index 27e42a8f98e..86af7056176 100644 --- a/routers/web/repo/fork.go +++ b/routers/web/repo/fork.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" @@ -48,7 +49,7 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository { ctx.Data["repo_name"] = forkRepo.Name ctx.Data["description"] = forkRepo.Description ctx.Data["IsPrivate"] = forkRepo.IsPrivate || forkRepo.Owner.Visibility == structs.VisibleTypePrivate - canForkToUser := forkRepo.OwnerID != ctx.Doer.ID && !repo_model.HasForkedRepo(ctx, ctx.Doer.ID, forkRepo.ID) + canForkToUser := repository.CanUserForkBetweenOwners(forkRepo.OwnerID, ctx.Doer.ID) && !repo_model.HasForkedRepo(ctx, ctx.Doer.ID, forkRepo.ID) ctx.Data["ForkRepo"] = forkRepo @@ -66,7 +67,7 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository { traverseParentRepo := forkRepo for { - if ctx.Doer.ID == traverseParentRepo.OwnerID { + if !repository.CanUserForkBetweenOwners(ctx.Doer.ID, traverseParentRepo.OwnerID) { canForkToUser = false } else { for i, org := range orgs { @@ -162,7 +163,7 @@ func ForkPost(ctx *context.Context) { var err error traverseParentRepo := forkRepo for { - if ctxUser.ID == traverseParentRepo.OwnerID { + if !repository.CanUserForkBetweenOwners(ctxUser.ID, traverseParentRepo.OwnerID) { ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form) return } diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 58a2bdbab1c..a8a7a4bd79f 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -458,7 +458,6 @@ func serviceRPC(ctx *context.Context, h *serviceHandler, service string) { var stderr bytes.Buffer cmd.AddArguments("--stateless-rpc").AddDynamicArguments(h.getRepoDir()) - cmd.SetDescription(fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", h.getRepoDir())) if err := cmd.Run(&git.RunOpts{ Dir: h.getRepoDir(), Env: append(os.Environ(), h.environ...), diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go index 3eaf05f3833..a2c257940e1 100644 --- a/routers/web/repo/migrate.go +++ b/routers/web/repo/migrate.go @@ -9,12 +9,12 @@ import ( "net/url" "strings" - "code.gitea.io/gitea/models" admin_model "code.gitea.io/gitea/models/admin" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -123,8 +123,8 @@ func handleMigrateError(ctx *context.Context, owner *user_model.User, err error, } func handleMigrateRemoteAddrError(ctx *context.Context, err error, tpl base.TplName, form *forms.MigrateRepoForm) { - if models.IsErrInvalidCloneAddr(err) { - addrErr := err.(*models.ErrInvalidCloneAddr) + if git.IsErrInvalidCloneAddr(err) { + addrErr := err.(*git.ErrInvalidCloneAddr) switch { case addrErr.IsProtocolInvalid: ctx.RenderWithErr(ctx.Tr("repo.mirror_address_protocol_invalid"), tpl, form) @@ -176,7 +176,7 @@ func MigratePost(ctx *context.Context) { return } - remoteAddr, err := forms.ParseRemoteAddr(form.CloneAddr, form.AuthUsername, form.AuthPassword) + remoteAddr, err := git.ParseRemoteAddr(form.CloneAddr, form.AuthUsername, form.AuthPassword) if err == nil { err = migrations.IsMigrateURLAllowed(remoteAddr, ctx.Doer) } diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go index 0dee02dd9ca..5906ec6f3db 100644 --- a/routers/web/repo/patch.go +++ b/routers/web/repo/patch.go @@ -6,7 +6,6 @@ package repo import ( "strings" - "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/base" @@ -101,7 +100,7 @@ func NewDiffPatchPost(ctx *context.Context) { ctx.Data["Err_NewBranchName"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form) return - } else if models.IsErrCommitIDDoesNotMatch(err) { + } else if files.IsErrCommitIDDoesNotMatch(err) { ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplPatchFile, &form) return } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index a3907e738da..c6b3cf1af79 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -14,7 +14,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" @@ -701,9 +700,6 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi if ctx.Written() { return - } else if prInfo == nil { - ctx.NotFound("ViewPullFiles", nil) - return } headCommitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName()) @@ -971,8 +967,8 @@ func UpdatePullRequest(ctx *context.Context) { message := fmt.Sprintf("Merge branch '%s' into %s", issue.PullRequest.BaseBranch, issue.PullRequest.HeadBranch) if err = pull_service.Update(ctx, issue.PullRequest, ctx.Doer, message, rebase); err != nil { - if models.IsErrMergeConflicts(err) { - conflictError := err.(models.ErrMergeConflicts) + if pull_service.IsErrMergeConflicts(err) { + conflictError := err.(pull_service.ErrMergeConflicts) flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.pulls.merge_conflict"), "Summary": ctx.Tr("repo.pulls.merge_conflict_summary"), @@ -985,8 +981,8 @@ func UpdatePullRequest(ctx *context.Context) { ctx.Flash.Error(flashError) ctx.Redirect(issue.Link()) return - } else if models.IsErrRebaseConflicts(err) { - conflictError := err.(models.ErrRebaseConflicts) + } else if pull_service.IsErrRebaseConflicts(err) { + conflictError := err.(pull_service.ErrRebaseConflicts) flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)), "Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"), @@ -1050,7 +1046,7 @@ func MergePullRequest(ctx *context.Context) { ctx.JSONError(ctx.Tr("repo.pulls.no_merge_wip")) case errors.Is(err, pull_service.ErrNotMergeableState): ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready")) - case models.IsErrDisallowedToMerge(err): + case pull_service.IsErrDisallowedToMerge(err): ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready")) case asymkey_service.IsErrWontSign(err): ctx.JSONError(err.Error()) // has no translation ... @@ -1067,7 +1063,7 @@ func MergePullRequest(ctx *context.Context) { if manuallyMerged { if err := pull_service.MergedManually(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil { switch { - case models.IsErrInvalidMergeStyle(err): + case pull_service.IsErrInvalidMergeStyle(err): ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option")) case strings.Contains(err.Error(), "Wrong commit ID"): ctx.JSONError(ctx.Tr("repo.pulls.wrong_commit_id")) @@ -1114,10 +1110,10 @@ func MergePullRequest(ctx *context.Context) { } if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil { - if models.IsErrInvalidMergeStyle(err) { + if pull_service.IsErrInvalidMergeStyle(err) { ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option")) - } else if models.IsErrMergeConflicts(err) { - conflictError := err.(models.ErrMergeConflicts) + } else if pull_service.IsErrMergeConflicts(err) { + conflictError := err.(pull_service.ErrMergeConflicts) flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.editor.merge_conflict"), "Summary": ctx.Tr("repo.editor.merge_conflict_summary"), @@ -1129,8 +1125,8 @@ func MergePullRequest(ctx *context.Context) { } ctx.Flash.Error(flashError) ctx.JSONRedirect(issue.Link()) - } else if models.IsErrRebaseConflicts(err) { - conflictError := err.(models.ErrRebaseConflicts) + } else if pull_service.IsErrRebaseConflicts(err) { + conflictError := err.(pull_service.ErrRebaseConflicts) flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{ "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)), "Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"), @@ -1142,7 +1138,7 @@ func MergePullRequest(ctx *context.Context) { } ctx.Flash.Error(flashError) ctx.JSONRedirect(issue.Link()) - } else if models.IsErrMergeUnrelatedHistories(err) { + } else if pull_service.IsErrMergeUnrelatedHistories(err) { log.Debug("MergeUnrelatedHistories error: %v", err) ctx.Flash.Error(ctx.Tr("repo.pulls.unrelated_histories")) ctx.JSONRedirect(issue.Link()) @@ -1150,7 +1146,7 @@ func MergePullRequest(ctx *context.Context) { log.Debug("MergePushOutOfDate error: %v", err) ctx.Flash.Error(ctx.Tr("repo.pulls.merge_out_of_date")) ctx.JSONRedirect(issue.Link()) - } else if models.IsErrSHADoesNotMatch(err) { + } else if pull_service.IsErrSHADoesNotMatch(err) { log.Debug("MergeHeadOutOfDate error: %v", err) ctx.Flash.Error(ctx.Tr("repo.pulls.head_out_of_date")) ctx.JSONRedirect(issue.Link()) @@ -1609,7 +1605,7 @@ func UpdatePullRequestTarget(ctx *context.Context) { "error": err.Error(), "user_error": errorMessage, }) - } else if models.IsErrPullRequestHasMerged(err) { + } else if pull_service.IsErrPullRequestHasMerged(err) { errorMessage := ctx.Tr("repo.pulls.has_merged") ctx.Flash.Error(errorMessage) diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index b3a91a60707..5c5191fc3b9 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -10,7 +10,6 @@ import ( "net/http" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/renderhelper" @@ -30,7 +29,7 @@ import ( "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context/upload" "code.gitea.io/gitea/services/forms" - releaseservice "code.gitea.io/gitea/services/release" + release_service "code.gitea.io/gitea/services/release" ) const ( @@ -432,27 +431,27 @@ func NewReleasePost(ctx *context.Context) { } if len(form.TagOnly) > 0 { - if err = releaseservice.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, form.Target, form.TagName, msg); err != nil { - if models.IsErrTagAlreadyExists(err) { - e := err.(models.ErrTagAlreadyExists) + if err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, form.Target, form.TagName, msg); err != nil { + if release_service.IsErrTagAlreadyExists(err) { + e := err.(release_service.ErrTagAlreadyExists) ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName)) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) return } - if models.IsErrInvalidTagName(err) { + if release_service.IsErrInvalidTagName(err) { ctx.Flash.Error(ctx.Tr("repo.release.tag_name_invalid")) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) return } - if models.IsErrProtectedTagName(err) { + if release_service.IsErrProtectedTagName(err) { ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected")) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) return } - ctx.ServerError("releaseservice.CreateNewTag", err) + ctx.ServerError("release_service.CreateNewTag", err) return } @@ -475,14 +474,14 @@ func NewReleasePost(ctx *context.Context) { IsTag: false, } - if err = releaseservice.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs, msg); err != nil { + if err = release_service.CreateRelease(ctx.Repo.GitRepo, rel, attachmentUUIDs, msg); err != nil { ctx.Data["Err_TagName"] = true switch { case repo_model.IsErrReleaseAlreadyExist(err): ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form) - case models.IsErrInvalidTagName(err): + case release_service.IsErrInvalidTagName(err): ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form) - case models.IsErrProtectedTagName(err): + case release_service.IsErrProtectedTagName(err): ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_protected"), tplReleaseNew, &form) default: ctx.ServerError("CreateRelease", err) @@ -504,7 +503,7 @@ func NewReleasePost(ctx *context.Context) { rel.PublisherID = ctx.Doer.ID rel.IsTag = false - if err = releaseservice.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, attachmentUUIDs, nil, nil); err != nil { + if err = release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, attachmentUUIDs, nil, nil); err != nil { ctx.Data["Err_TagName"] = true ctx.ServerError("UpdateRelease", err) return @@ -610,7 +609,7 @@ func EditReleasePost(ctx *context.Context) { rel.Note = form.Content rel.IsDraft = len(form.Draft) > 0 rel.IsPrerelease = form.Prerelease - if err = releaseservice.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, + if err = release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, addAttachmentUUIDs, delAttachmentUUIDs, editAttachments); err != nil { ctx.ServerError("UpdateRelease", err) return @@ -649,8 +648,8 @@ func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) { return } - if err := releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, isDelTag); err != nil { - if models.IsErrProtectedTagName(err) { + if err := release_service.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, isDelTag); err != nil { + if release_service.IsErrProtectedTagName(err) { ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected")) } else { ctx.Flash.Error("DeleteReleaseByID: " + err.Error()) diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index f5e59b0357b..4b017f61c4b 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -11,7 +11,6 @@ import ( "slices" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/organization" @@ -378,7 +377,7 @@ func Action(ctx *context.Context) { } func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error { - repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) + repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) if err != nil { return err } diff --git a/routers/web/repo/setting/collaboration.go b/routers/web/repo/setting/collaboration.go index cdf91edf4a2..df7cc5e39b6 100644 --- a/routers/web/repo/setting/collaboration.go +++ b/routers/web/repo/setting/collaboration.go @@ -32,7 +32,7 @@ func Collaboration(ctx *context.Context) { } ctx.Data["Collaborators"] = users - teams, err := organization.GetRepoTeams(ctx, ctx.Repo.Repository) + teams, err := organization.GetRepoTeams(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID) if err != nil { ctx.ServerError("GetRepoTeams", err) return diff --git a/routers/web/repo/setting/deploy_key.go b/routers/web/repo/setting/deploy_key.go index abc3eb4af18..193562528bf 100644 --- a/routers/web/repo/setting/deploy_key.go +++ b/routers/web/repo/setting/deploy_key.go @@ -99,7 +99,7 @@ func DeployKeysPost(ctx *context.Context) { // DeleteDeployKey response for deleting a deploy key func DeleteDeployKey(ctx *context.Context) { - if err := asymkey_service.DeleteDeployKey(ctx, ctx.Doer, ctx.FormInt64("id")); err != nil { + if err := asymkey_service.DeleteDeployKey(ctx, ctx.Repo.Repository, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteDeployKey: " + err.Error()) } else { ctx.Flash.Success(ctx.Tr("repo.settings.deploy_key_deletion_success")) diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 717d7cbce1d..1c7f9441f80 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -11,7 +11,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" @@ -223,7 +222,7 @@ func SettingsPost(ctx *context.Context) { form.MirrorPassword, _ = u.User.Password() } - address, err := forms.ParseRemoteAddr(form.MirrorAddress, form.MirrorUsername, form.MirrorPassword) + address, err := git.ParseRemoteAddr(form.MirrorAddress, form.MirrorUsername, form.MirrorPassword) if err == nil { err = migrations.IsMigrateURLAllowed(address, ctx.Doer) } @@ -385,7 +384,7 @@ func SettingsPost(ctx *context.Context) { return } - address, err := forms.ParseRemoteAddr(form.PushMirrorAddress, form.PushMirrorUsername, form.PushMirrorPassword) + address, err := git.ParseRemoteAddr(form.PushMirrorAddress, form.PushMirrorUsername, form.PushMirrorPassword) if err == nil { err = migrations.IsMigrateURLAllowed(address, ctx.Doer) } @@ -788,7 +787,7 @@ func SettingsPost(ctx *context.Context) { if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, repo, nil); err != nil { if repo_model.IsErrRepoAlreadyExist(err) { ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil) - } else if models.IsErrRepoTransferInProgress(err) { + } else if repo_model.IsErrRepoTransferInProgress(err) { ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil) } else if errors.Is(err, user_model.ErrBlockedUser) { ctx.RenderWithErr(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil) @@ -814,9 +813,9 @@ func SettingsPost(ctx *context.Context) { return } - repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) + repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) if err != nil { - if models.IsErrNoPendingTransfer(err) { + if repo_model.IsErrNoPendingTransfer(err) { ctx.Flash.Error("repo.settings.transfer_abort_invalid") ctx.Redirect(repo.Link() + "/settings") } else { @@ -980,8 +979,8 @@ func SettingsPost(ctx *context.Context) { } func handleSettingRemoteAddrError(ctx *context.Context, err error, form *forms.RepoSettingForm) { - if models.IsErrInvalidCloneAddr(err) { - addrErr := err.(*models.ErrInvalidCloneAddr) + if git.IsErrInvalidCloneAddr(err) { + addrErr := err.(*git.ErrInvalidCloneAddr) switch { case addrErr.IsProtocolInvalid: ctx.RenderWithErr(ctx.Tr("repo.mirror_address_protocol_invalid"), tplSettingsOptions, form) diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go index b81f2ea02e3..958ff802d40 100644 --- a/routers/web/repo/wiki_test.go +++ b/routers/web/repo/wiki_test.go @@ -135,7 +135,7 @@ func TestNewWikiPost(t *testing.T) { NewWikiPost(ctx) assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status()) assertWikiExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)) - assert.Equal(t, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)), content) + assert.Equal(t, content, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title))) } } @@ -194,7 +194,7 @@ func TestEditWikiPost(t *testing.T) { EditWikiPost(ctx) assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status()) assertWikiExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)) - assert.Equal(t, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)), content) + assert.Equal(t, content, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title))) if title != "Home" { assertWikiNotExists(t, ctx.Repo.Repository, "Home") } diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 7f2dece4169..00fe02c886d 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -10,7 +10,9 @@ import ( "net/http" "time" - "code.gitea.io/gitea/models" + org_model "code.gitea.io/gitea/models/organization" + packages_model "code.gitea.io/gitea/models/packages" + repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/password" "code.gitea.io/gitea/modules/base" @@ -301,16 +303,16 @@ func DeleteAccount(ctx *context.Context) { if err := user.DeleteUser(ctx, ctx.Doer, false); err != nil { switch { - case models.IsErrUserOwnRepos(err): + case repo_model.IsErrUserOwnRepos(err): ctx.Flash.Error(ctx.Tr("form.still_own_repo")) ctx.Redirect(setting.AppSubURL + "/user/settings/account") - case models.IsErrUserHasOrgs(err): + case org_model.IsErrUserHasOrgs(err): ctx.Flash.Error(ctx.Tr("form.still_has_org")) ctx.Redirect(setting.AppSubURL + "/user/settings/account") - case models.IsErrUserOwnPackages(err): + case packages_model.IsErrUserOwnPackages(err): ctx.Flash.Error(ctx.Tr("form.still_own_packages")) ctx.Redirect(setting.AppSubURL + "/user/settings/account") - case models.IsErrDeleteLastAdminUser(err): + case user_model.IsErrDeleteLastAdminUser(err): ctx.Flash.Error(ctx.Tr("auth.last_admin")) ctx.Redirect(setting.AppSubURL + "/user/settings/account") default: diff --git a/routers/web/web.go b/routers/web/web.go index 72ee47bb4c9..aa37d4dc105 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1020,14 +1020,15 @@ func registerRoutes(m *web.Router) { m.Get("/new", org.RenderNewProject) m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost) m.Group("/{id}", func() { - m.Post("", web.Bind(forms.EditProjectColumnForm{}), org.AddColumnToProjectPost) - m.Post("/move", project.MoveColumns) m.Post("/delete", org.DeleteProject) m.Get("/edit", org.RenderEditProject) m.Post("/edit", web.Bind(forms.CreateProjectForm{}), org.EditProjectPost) m.Post("/{action:open|close}", org.ChangeProjectStatus) + // TODO: improper name. Others are "delete project", "edit project", but this one is "move columns" + m.Post("/move", project.MoveColumns) + m.Post("/columns/new", web.Bind(forms.EditProjectColumnForm{}), org.AddColumnToProjectPost) m.Group("/{columnID}", func() { m.Put("", web.Bind(forms.EditProjectColumnForm{}), org.EditProjectColumn) m.Delete("", org.DeleteProjectColumn) @@ -1387,14 +1388,15 @@ func registerRoutes(m *web.Router) { m.Get("/new", repo.RenderNewProject) m.Post("/new", web.Bind(forms.CreateProjectForm{}), repo.NewProjectPost) m.Group("/{id}", func() { - m.Post("", web.Bind(forms.EditProjectColumnForm{}), repo.AddColumnToProjectPost) - m.Post("/move", project.MoveColumns) m.Post("/delete", repo.DeleteProject) m.Get("/edit", repo.RenderEditProject) m.Post("/edit", web.Bind(forms.CreateProjectForm{}), repo.EditProjectPost) m.Post("/{action:open|close}", repo.ChangeProjectStatus) + // TODO: improper name. Others are "delete project", "edit project", but this one is "move columns" + m.Post("/move", project.MoveColumns) + m.Post("/columns/new", web.Bind(forms.EditProjectColumnForm{}), repo.AddColumnToProjectPost) m.Group("/{columnID}", func() { m.Put("", web.Bind(forms.EditProjectColumnForm{}), repo.EditProjectColumn) m.Delete("", repo.DeleteProjectColumn) diff --git a/services/actions/auth_test.go b/services/actions/auth_test.go index 12db2bae565..85e74091052 100644 --- a/services/actions/auth_test.go +++ b/services/actions/auth_test.go @@ -17,19 +17,19 @@ import ( func TestCreateAuthorizationToken(t *testing.T) { var taskID int64 = 23 token, err := CreateAuthorizationToken(taskID, 1, 2) - assert.Nil(t, err) + assert.NoError(t, err) assert.NotEqual(t, "", token) claims := jwt.MapClaims{} _, err = jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) { return setting.GetGeneralTokenSigningSecret(), nil }) - assert.Nil(t, err) + assert.NoError(t, err) scp, ok := claims["scp"] assert.True(t, ok, "Has scp claim in jwt token") assert.Contains(t, scp, "Actions.Results:1:2") taskIDClaim, ok := claims["TaskID"] assert.True(t, ok, "Has TaskID claim in jwt token") - assert.Equal(t, float64(taskID), taskIDClaim, "Supplied taskid must match stored one") + assert.InDelta(t, float64(taskID), taskIDClaim, 0, "Supplied taskid must match stored one") acClaim, ok := claims["ac"] assert.True(t, ok, "Has ac claim in jwt token") ac, ok := acClaim.(string) @@ -43,14 +43,14 @@ func TestCreateAuthorizationToken(t *testing.T) { func TestParseAuthorizationToken(t *testing.T) { var taskID int64 = 23 token, err := CreateAuthorizationToken(taskID, 1, 2) - assert.Nil(t, err) + assert.NoError(t, err) assert.NotEqual(t, "", token) headers := http.Header{} headers.Set("Authorization", "Bearer "+token) rTaskID, err := ParseAuthorizationToken(&http.Request{ Header: headers, }) - assert.Nil(t, err) + assert.NoError(t, err) assert.Equal(t, taskID, rTaskID) } @@ -59,6 +59,6 @@ func TestParseAuthorizationTokenNoAuthHeader(t *testing.T) { rTaskID, err := ParseAuthorizationToken(&http.Request{ Header: headers, }) - assert.Nil(t, err) + assert.NoError(t, err) assert.Equal(t, int64(0), rTaskID) } diff --git a/services/asymkey/deploy_key.go b/services/asymkey/deploy_key.go index 324688c5340..9e5a6d6292b 100644 --- a/services/asymkey/deploy_key.go +++ b/services/asymkey/deploy_key.go @@ -5,21 +5,69 @@ package asymkey import ( "context" + "fmt" - "code.gitea.io/gitea/models" + asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" - user_model "code.gitea.io/gitea/models/user" + repo_model "code.gitea.io/gitea/models/repo" ) +// DeleteRepoDeployKeys deletes all deploy keys of a repository. permissions check should be done outside +func DeleteRepoDeployKeys(ctx context.Context, repoID int64) (int, error) { + deployKeys, err := db.Find[asymkey_model.DeployKey](ctx, asymkey_model.ListDeployKeysOptions{RepoID: repoID}) + if err != nil { + return 0, fmt.Errorf("listDeployKeys: %w", err) + } + + for _, dKey := range deployKeys { + if err := deleteDeployKeyFromDB(ctx, dKey); err != nil { + return 0, fmt.Errorf("deleteDeployKeys: %w", err) + } + } + return len(deployKeys), nil +} + +// deleteDeployKeyFromDB delete deploy keys from database +func deleteDeployKeyFromDB(ctx context.Context, key *asymkey_model.DeployKey) error { + if _, err := db.DeleteByID[asymkey_model.DeployKey](ctx, key.ID); err != nil { + return fmt.Errorf("delete deploy key [%d]: %w", key.ID, err) + } + + // Check if this is the last reference to same key content. + has, err := asymkey_model.IsDeployKeyExistByKeyID(ctx, key.KeyID) + if err != nil { + return err + } else if !has { + if _, err = db.DeleteByID[asymkey_model.PublicKey](ctx, key.KeyID); err != nil { + return err + } + } + + return nil +} + // DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed. -func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error { +// Permissions check should be done outside. +func DeleteDeployKey(ctx context.Context, repo *repo_model.Repository, id int64) error { dbCtx, committer, err := db.TxContext(ctx) if err != nil { return err } defer committer.Close() - if err := models.DeleteDeployKey(dbCtx, doer, id); err != nil { + key, err := asymkey_model.GetDeployKeyByID(ctx, id) + if err != nil { + if asymkey_model.IsErrDeployKeyNotExist(err) { + return nil + } + return fmt.Errorf("GetDeployKeyByID: %w", err) + } + + if key.RepoID != repo.ID { + return fmt.Errorf("deploy key %d does not belong to repository %d", id, repo.ID) + } + + if err := deleteDeployKeyFromDB(dbCtx, key); err != nil { return err } if err := committer.Commit(); err != nil { diff --git a/services/asymkey/main_test.go b/services/asymkey/main_test.go index 3505b26f699..1cdc39933d4 100644 --- a/services/asymkey/main_test.go +++ b/services/asymkey/main_test.go @@ -8,6 +8,7 @@ import ( "code.gitea.io/gitea/models/unittest" + _ "code.gitea.io/gitea/models" _ "code.gitea.io/gitea/models/actions" _ "code.gitea.io/gitea/models/activities" ) diff --git a/services/auth/oauth2_test.go b/services/auth/oauth2_test.go index 75c231ff7a4..b706847e8e1 100644 --- a/services/auth/oauth2_test.go +++ b/services/auth/oauth2_test.go @@ -28,7 +28,7 @@ func TestUserIDFromToken(t *testing.T) { o := OAuth2{} uid := o.userIDFromToken(context.Background(), token, ds) assert.Equal(t, int64(user_model.ActionsUserID), uid) - assert.Equal(t, ds["IsActionsToken"], true) + assert.Equal(t, true, ds["IsActionsToken"]) assert.Equal(t, ds["ActionsTaskID"], int64(RunningTaskID)) }) } diff --git a/services/auth/source/ldap/source.go b/services/auth/source/ldap/source.go index dc4cb2c9403..963cdba7c21 100644 --- a/services/auth/source/ldap/source.go +++ b/services/auth/source/ldap/source.go @@ -56,8 +56,7 @@ type Source struct { UserUID string // User Attribute listed in Group SkipLocalTwoFA bool `json:",omitempty"` // Skip Local 2fa for users authenticated with this source - // reference to the authSource - authSource *auth.Source + authSource *auth.Source // reference to the authSource } // FromDB fills up a LDAPConfig from serialized format. @@ -107,7 +106,7 @@ func (source *Source) UseTLS() bool { // ProvidesSSHKeys returns if this source provides SSH Keys func (source *Source) ProvidesSSHKeys() bool { - return len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0 + return strings.TrimSpace(source.AttributeSSHPublicKey) != "" } // SetAuthSource sets the related AuthSource diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 01cb7437205..6a6c60cd40e 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" auth_module "code.gitea.io/gitea/modules/auth" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" asymkey_service "code.gitea.io/gitea/services/asymkey" source_service "code.gitea.io/gitea/services/auth/source" @@ -31,13 +32,18 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return nil, user_model.ErrUserNotExist{Name: loginName} } // Fallback. - if len(sr.Username) == 0 { + // FIXME: this fallback would cause problems when the "Username" attribute is not set and a user inputs their email. + // In this case, the email would be used as the username, and will cause the "CreateUser" failure for the first login. + if sr.Username == "" { + if strings.Contains(userName, "@") { + log.Error("No username in search result (Username Attribute is not set properly?), using email as username might cause problems") + } sr.Username = userName } - if len(sr.Mail) == 0 { + if sr.Mail == "" { sr.Mail = fmt.Sprintf("%s@localhost.local", sr.Username) } - isAttributeSSHPublicKeySet := len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0 + isAttributeSSHPublicKeySet := strings.TrimSpace(source.AttributeSSHPublicKey) != "" // Update User admin flag if exist if isExist, err := user_model.IsUserExist(ctx, 0, sr.Username); err != nil { @@ -51,11 +57,11 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u } if user != nil && !user.ProhibitLogin { opts := &user_service.UpdateOptions{} - if len(source.AdminFilter) > 0 && user.IsAdmin != sr.IsAdmin { + if source.AdminFilter != "" && user.IsAdmin != sr.IsAdmin { // Change existing admin flag only if AdminFilter option is set opts.IsAdmin = optional.Some(sr.IsAdmin) } - if !sr.IsAdmin && len(source.RestrictedFilter) > 0 && user.IsRestricted != sr.IsRestricted { + if !sr.IsAdmin && source.RestrictedFilter != "" && user.IsRestricted != sr.IsRestricted { // Change existing restricted flag only if RestrictedFilter option is set opts.IsRestricted = optional.Some(sr.IsRestricted) } @@ -99,7 +105,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return user, err } } - if len(source.AttributeAvatar) > 0 { + if source.AttributeAvatar != "" { if err := user_service.UploadAvatar(ctx, user, sr.Avatar); err != nil { return user, err } diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index b20c90e791d..fa2c45ce4ad 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -147,7 +147,7 @@ func bindUser(l *ldap.Conn, userDN, passwd string) error { } func checkAdmin(l *ldap.Conn, ls *Source, userDN string) bool { - if len(ls.AdminFilter) == 0 { + if ls.AdminFilter == "" { return false } log.Trace("Checking admin with filter %s and base %s", ls.AdminFilter, userDN) @@ -169,7 +169,7 @@ func checkAdmin(l *ldap.Conn, ls *Source, userDN string) bool { } func checkRestricted(l *ldap.Conn, ls *Source, userDN string) bool { - if len(ls.RestrictedFilter) == 0 { + if ls.RestrictedFilter == "" { return false } if ls.RestrictedFilter == "*" { @@ -250,8 +250,17 @@ func (source *Source) getUserAttributeListedInGroup(entry *ldap.Entry) string { // SearchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchResult { + if MockedSearchEntry != nil { + return MockedSearchEntry(source, name, passwd, directBind) + } + return realSearchEntry(source, name, passwd, directBind) +} + +var MockedSearchEntry func(source *Source, name, passwd string, directBind bool) *SearchResult + +func realSearchEntry(source *Source, name, passwd string, directBind bool) *SearchResult { // See https://tools.ietf.org/search/rfc4513#section-5.1.2 - if len(passwd) == 0 { + if passwd == "" { log.Debug("Auth. failed for %s, password cannot be empty", name) return nil } @@ -323,17 +332,17 @@ func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchR return nil } - isAttributeSSHPublicKeySet := len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0 - isAtributeAvatarSet := len(strings.TrimSpace(source.AttributeAvatar)) > 0 + isAttributeSSHPublicKeySet := strings.TrimSpace(source.AttributeSSHPublicKey) != "" + isAttributeAvatarSet := strings.TrimSpace(source.AttributeAvatar) != "" attribs := []string{source.AttributeUsername, source.AttributeName, source.AttributeSurname, source.AttributeMail} - if len(strings.TrimSpace(source.UserUID)) > 0 { + if strings.TrimSpace(source.UserUID) != "" { attribs = append(attribs, source.UserUID) } if isAttributeSSHPublicKeySet { attribs = append(attribs, source.AttributeSSHPublicKey) } - if isAtributeAvatarSet { + if isAttributeAvatarSet { attribs = append(attribs, source.AttributeAvatar) } @@ -375,7 +384,7 @@ func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchR isRestricted = checkRestricted(l, source, userDN) } - if isAtributeAvatarSet { + if isAttributeAvatarSet { Avatar = sr.Entries[0].GetRawAttributeValue(source.AttributeAvatar) } @@ -440,14 +449,14 @@ func (source *Source) SearchEntries() ([]*SearchResult, error) { userFilter := fmt.Sprintf(source.Filter, "*") - isAttributeSSHPublicKeySet := len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0 - isAtributeAvatarSet := len(strings.TrimSpace(source.AttributeAvatar)) > 0 + isAttributeSSHPublicKeySet := strings.TrimSpace(source.AttributeSSHPublicKey) != "" + isAttributeAvatarSet := strings.TrimSpace(source.AttributeAvatar) != "" attribs := []string{source.AttributeUsername, source.AttributeName, source.AttributeSurname, source.AttributeMail, source.UserUID} if isAttributeSSHPublicKeySet { attribs = append(attribs, source.AttributeSSHPublicKey) } - if isAtributeAvatarSet { + if isAttributeAvatarSet { attribs = append(attribs, source.AttributeAvatar) } @@ -503,7 +512,7 @@ func (source *Source) SearchEntries() ([]*SearchResult, error) { user.SSHPublicKey = v.GetAttributeValues(source.AttributeSSHPublicKey) } - if isAtributeAvatarSet { + if isAttributeAvatarSet { user.Avatar = v.GetRawAttributeValue(source.AttributeAvatar) } diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index a6d6d2a0f2f..e817bf1fa93 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -25,7 +25,7 @@ import ( func (source *Source) Sync(ctx context.Context, updateExisting bool) error { log.Trace("Doing: SyncExternalUsers[%s]", source.authSource.Name) - isAttributeSSHPublicKeySet := len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0 + isAttributeSSHPublicKeySet := strings.TrimSpace(source.AttributeSSHPublicKey) != "" var sshKeysNeedUpdate bool // Find all users with this login type - FIXME: Should this be an iterator? @@ -86,26 +86,26 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { return db.ErrCancelledf("During update of %s before completed update of users", source.authSource.Name) default: } - if len(su.Username) == 0 && len(su.Mail) == 0 { + if su.Username == "" && su.Mail == "" { continue } var usr *user_model.User - if len(su.Username) > 0 { + if su.Username != "" { usr = usernameUsers[su.LowerName] } - if usr == nil && len(su.Mail) > 0 { + if usr == nil && su.Mail != "" { usr = mailUsers[strings.ToLower(su.Mail)] } if usr != nil { keepActiveUsers.Add(usr.ID) - } else if len(su.Username) == 0 { + } else if su.Username == "" { // we cannot create the user if su.Username is empty continue } - if len(su.Mail) == 0 { + if su.Mail == "" { su.Mail = fmt.Sprintf("%s@localhost.local", su.Username) } @@ -141,7 +141,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } } - if err == nil && len(source.AttributeAvatar) > 0 { + if err == nil && source.AttributeAvatar != "" { _ = user_service.UploadAvatar(ctx, usr, su.Avatar) } } else if updateExisting { @@ -151,8 +151,8 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } // Check if user data has changed - if (len(source.AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) || - (len(source.RestrictedFilter) > 0 && usr.IsRestricted != su.IsRestricted) || + if (source.AdminFilter != "" && usr.IsAdmin != su.IsAdmin) || + (source.RestrictedFilter != "" && usr.IsRestricted != su.IsRestricted) || !strings.EqualFold(usr.Email, su.Mail) || usr.FullName != fullName || !usr.IsActive { @@ -180,7 +180,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } if usr.IsUploadAvatarChanged(su.Avatar) { - if err == nil && len(source.AttributeAvatar) > 0 { + if err == nil && source.AttributeAvatar != "" { _ = user_service.UploadAvatar(ctx, usr, su.Avatar) } } diff --git a/services/auth/source/ldap/util.go b/services/auth/source/ldap/util.go index bd11e2d1193..05ae32c0fd2 100644 --- a/services/auth/source/ldap/util.go +++ b/services/auth/source/ldap/util.go @@ -6,11 +6,11 @@ package ldap // composeFullName composes a firstname surname or username func composeFullName(firstname, surname, username string) string { switch { - case len(firstname) == 0 && len(surname) == 0: + case firstname == "" && surname == "": return username - case len(firstname) == 0: + case firstname == "": return surname - case len(surname) == 0: + case surname == "": return firstname default: return firstname + " " + surname diff --git a/services/auth/source/oauth2/source_sync_test.go b/services/auth/source/oauth2/source_sync_test.go index 25408e8727e..893ed625028 100644 --- a/services/auth/source/oauth2/source_sync_test.go +++ b/services/auth/source/oauth2/source_sync_test.go @@ -64,8 +64,8 @@ func TestSource(t *testing.T) { ok, err := user_model.GetExternalLogin(context.Background(), e) assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, e.RefreshToken, "refresh") - assert.Equal(t, e.AccessToken, "token") + assert.Equal(t, "refresh", e.RefreshToken) + assert.Equal(t, "token", e.AccessToken) u, err := user_model.GetUserByID(context.Background(), user.ID) assert.NoError(t, err) @@ -89,8 +89,8 @@ func TestSource(t *testing.T) { ok, err := user_model.GetExternalLogin(context.Background(), e) assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, e.RefreshToken, "") - assert.Equal(t, e.AccessToken, "") + assert.Equal(t, "", e.RefreshToken) + assert.Equal(t, "", e.AccessToken) u, err := user_model.GetUserByID(context.Background(), user.ID) assert.NoError(t, err) diff --git a/services/context/repo.go b/services/context/repo.go index 9b544391103..e96916ca421 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -14,7 +14,6 @@ import ( "path" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -709,7 +708,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc { ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer { - repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) + repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) if err != nil { ctx.ServerError("GetPendingRepositoryTransfer", err) return cancel diff --git a/services/convert/pull.go b/services/convert/pull.go index ddaaa300a44..a1ab7eeb8ec 100644 --- a/services/convert/pull.go +++ b/services/convert/pull.go @@ -31,6 +31,11 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u err error ) + if err = pr.LoadIssue(ctx); err != nil { + log.Error("pr.LoadIssue[%d]: %v", pr.ID, err) + return nil + } + if err = pr.Issue.LoadRepo(ctx); err != nil { log.Error("pr.Issue.LoadRepo[%d]: %v", pr.ID, err) return nil diff --git a/services/convert/pull_review_test.go b/services/convert/pull_review_test.go index 68869502802..a1296fafd4e 100644 --- a/services/convert/pull_review_test.go +++ b/services/convert/pull_review_test.go @@ -40,7 +40,7 @@ func Test_ToPullReview(t *testing.T) { user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) prList, err := ToPullReviewList(db.DefaultContext, reviewList, user4) assert.NoError(t, err) - assert.Len(t, prList, 0) + assert.Empty(t, prList) }) t.Run("Admin User", func(t *testing.T) { diff --git a/services/convert/repository.go b/services/convert/repository.go index e026d0f4405..88ccd88fcfe 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -7,7 +7,6 @@ import ( "context" "time" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -158,8 +157,8 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR var transfer *api.RepoTransfer if repo.Status == repo_model.RepositoryPendingTransfer { - t, err := models.GetPendingRepositoryTransfer(ctx, repo) - if err != nil && !models.IsErrNoPendingTransfer(err) { + t, err := repo_model.GetPendingRepositoryTransfer(ctx, repo) + if err != nil && !repo_model.IsErrNoPendingTransfer(err) { log.Warn("GetPendingRepositoryTransfer: %v", err) } else { if err := t.LoadAttributes(ctx); err != nil { @@ -248,7 +247,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR } // ToRepoTransfer convert a models.RepoTransfer to a structs.RepeTransfer -func ToRepoTransfer(ctx context.Context, t *models.RepoTransfer) *api.RepoTransfer { +func ToRepoTransfer(ctx context.Context, t *repo_model.RepoTransfer) *api.RepoTransfer { teams, _ := ToTeams(ctx, t.Teams, false) return &api.RepoTransfer{ diff --git a/services/cron/tasks_test.go b/services/cron/tasks_test.go index 979371a0229..ab22403ede7 100644 --- a/services/cron/tasks_test.go +++ b/services/cron/tasks_test.go @@ -12,7 +12,7 @@ import ( ) func TestAddTaskToScheduler(t *testing.T) { - assert.Len(t, scheduler.Jobs(), 0) + assert.Empty(t, scheduler.Jobs()) defer scheduler.Clear() // no seconds diff --git a/services/feed/feed_test.go b/services/feed/feed_test.go index 6f1cb9a969b..1e4d029e18c 100644 --- a/services/feed/feed_test.go +++ b/services/feed/feed_test.go @@ -41,7 +41,7 @@ func TestGetFeeds(t *testing.T) { OnlyPerformedBy: false, }) assert.NoError(t, err) - assert.Len(t, actions, 0) + assert.Empty(t, actions) assert.Equal(t, int64(0), count) } @@ -57,7 +57,7 @@ func TestGetFeedsForRepos(t *testing.T) { IncludePrivate: true, }) assert.NoError(t, err) - assert.Len(t, actions, 0) + assert.Empty(t, actions) assert.Equal(t, int64(0), count) // public repo & no login @@ -119,7 +119,7 @@ func TestGetFeeds2(t *testing.T) { IncludeDeleted: true, }) assert.NoError(t, err) - assert.Len(t, actions, 0) + assert.Empty(t, actions) assert.Equal(t, int64(0), count) } diff --git a/services/feed/notifier.go b/services/feed/notifier.go index a8820aeb777..d941027c352 100644 --- a/services/feed/notifier.go +++ b/services/feed/notifier.go @@ -417,6 +417,12 @@ func (a *actionNotifier) SyncPushCommits(ctx context.Context, pusher *user_model } func (a *actionNotifier) SyncCreateRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) { + // ignore pull sync message for pull requests refs + // TODO: it's better to have a UI to let users chose + if refFullName.IsPull() { + return + } + if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ ActUserID: repo.OwnerID, ActUser: repo.MustOwner(ctx), @@ -431,6 +437,12 @@ func (a *actionNotifier) SyncCreateRef(ctx context.Context, doer *user_model.Use } func (a *actionNotifier) SyncDeleteRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName) { + // ignore pull sync message for pull requests refs + // TODO: it's better to have a UI to let users chose + if refFullName.IsPull() { + return + } + if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ ActUserID: repo.OwnerID, ActUser: repo.MustOwner(ctx), diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 7647c74e465..b14171787e4 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -6,10 +6,8 @@ package forms import ( "net/http" - "net/url" "strings" - "code.gitea.io/gitea/models" issues_model "code.gitea.io/gitea/models/issues" project_model "code.gitea.io/gitea/models/project" "code.gitea.io/gitea/modules/setting" @@ -90,27 +88,6 @@ func (f *MigrateRepoForm) Validate(req *http.Request, errs binding.Errors) bindi return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } -// ParseRemoteAddr checks if given remote address is valid, -// and returns composed URL with needed username and password. -func ParseRemoteAddr(remoteAddr, authUsername, authPassword string) (string, error) { - remoteAddr = strings.TrimSpace(remoteAddr) - // Remote address can be HTTP/HTTPS/Git URL or local path. - if strings.HasPrefix(remoteAddr, "http://") || - strings.HasPrefix(remoteAddr, "https://") || - strings.HasPrefix(remoteAddr, "git://") { - u, err := url.Parse(remoteAddr) - if err != nil { - return "", &models.ErrInvalidCloneAddr{IsURLError: true, Host: remoteAddr} - } - if len(authUsername)+len(authPassword) > 0 { - u.User = url.UserPassword(authUsername, authPassword) - } - remoteAddr = u.String() - } - - return remoteAddr, nil -} - // RepoSettingForm form for changing repository settings type RepoSettingForm struct { RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index bb1722039e1..0b5a855d42f 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1156,7 +1156,6 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi go func() { stderr := &bytes.Buffer{} - cmdDiff.SetDescription(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath)) if err := cmdDiff.Run(&git.RunOpts{ Timeout: time.Duration(setting.Git.Timeout.Default) * time.Second, Dir: repoPath, diff --git a/services/gitdiff/gitdiff_test.go b/services/gitdiff/gitdiff_test.go index adcac355a7b..2351c5da879 100644 --- a/services/gitdiff/gitdiff_test.go +++ b/services/gitdiff/gitdiff_test.go @@ -5,7 +5,6 @@ package gitdiff import ( - "fmt" "strconv" "strings" "testing" @@ -643,9 +642,9 @@ func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) { MaxFiles: setting.Git.MaxGitDiffFiles, WhitespaceBehavior: behavior, }) - assert.NoError(t, err, fmt.Sprintf("Error when diff with %s", behavior)) + assert.NoError(t, err, "Error when diff with %s", behavior) for _, f := range diffs.Files { - assert.True(t, len(f.Sections) > 0, fmt.Sprintf("%s should have sections", f.Name)) + assert.NotEmpty(t, f.Sections, "%s should have sections", f.Name) } } } diff --git a/services/migrations/gitlab_test.go b/services/migrations/gitlab_test.go index 0b9eeaed540..eccfc4def1f 100644 --- a/services/migrations/gitlab_test.go +++ b/services/migrations/gitlab_test.go @@ -50,7 +50,7 @@ func TestGitlabDownloadRepo(t *testing.T) { topics, err := downloader.GetTopics() assert.NoError(t, err) - assert.True(t, len(topics) == 2) + assert.Len(t, topics, 2) assert.EqualValues(t, []string{"migration", "test"}, topics) milestones, err := downloader.GetMilestones() diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go index d0ad6d0139a..51b22d6111b 100644 --- a/services/migrations/migrate.go +++ b/services/migrations/migrate.go @@ -12,10 +12,10 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/models" repo_model "code.gitea.io/gitea/models/repo" system_model "code.gitea.io/gitea/models/system" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/hostmatcher" "code.gitea.io/gitea/modules/log" base "code.gitea.io/gitea/modules/migration" @@ -43,16 +43,16 @@ func IsMigrateURLAllowed(remoteURL string, doer *user_model.User) error { // Remote address can be HTTP/HTTPS/Git URL or local path. u, err := url.Parse(remoteURL) if err != nil { - return &models.ErrInvalidCloneAddr{IsURLError: true, Host: remoteURL} + return &git.ErrInvalidCloneAddr{IsURLError: true, Host: remoteURL} } if u.Scheme == "file" || u.Scheme == "" { if !doer.CanImportLocal() { - return &models.ErrInvalidCloneAddr{Host: "", IsPermissionDenied: true, LocalPath: true} + return &git.ErrInvalidCloneAddr{Host: "", IsPermissionDenied: true, LocalPath: true} } isAbs := filepath.IsAbs(u.Host + u.Path) if !isAbs { - return &models.ErrInvalidCloneAddr{Host: "", IsInvalidPath: true, LocalPath: true} + return &git.ErrInvalidCloneAddr{Host: "", IsInvalidPath: true, LocalPath: true} } isDir, err := util.IsDir(u.Host + u.Path) if err != nil { @@ -60,18 +60,18 @@ func IsMigrateURLAllowed(remoteURL string, doer *user_model.User) error { return err } if !isDir { - return &models.ErrInvalidCloneAddr{Host: "", IsInvalidPath: true, LocalPath: true} + return &git.ErrInvalidCloneAddr{Host: "", IsInvalidPath: true, LocalPath: true} } return nil } if u.Scheme == "git" && u.Port() != "" && (strings.Contains(remoteURL, "%0d") || strings.Contains(remoteURL, "%0a")) { - return &models.ErrInvalidCloneAddr{Host: u.Host, IsURLError: true} + return &git.ErrInvalidCloneAddr{Host: u.Host, IsURLError: true} } if u.Opaque != "" || u.Scheme != "" && u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "git" { - return &models.ErrInvalidCloneAddr{Host: u.Host, IsProtocolInvalid: true, IsPermissionDenied: true, IsURLError: true} + return &git.ErrInvalidCloneAddr{Host: u.Host, IsProtocolInvalid: true, IsPermissionDenied: true, IsURLError: true} } hostName, _, err := net.SplitHostPort(u.Host) @@ -95,12 +95,12 @@ func checkByAllowBlockList(hostName string, addrList []net.IP) error { } var blockedError error if blockList.MatchHostName(hostName) || ipBlocked { - blockedError = &models.ErrInvalidCloneAddr{Host: hostName, IsPermissionDenied: true} + blockedError = &git.ErrInvalidCloneAddr{Host: hostName, IsPermissionDenied: true} } // if we have an allow-list, check the allow-list before return to get the more accurate error if !allowList.IsEmpty() { if !allowList.MatchHostName(hostName) && !ipAllowed { - return &models.ErrInvalidCloneAddr{Host: hostName, IsPermissionDenied: true} + return &git.ErrInvalidCloneAddr{Host: hostName, IsPermissionDenied: true} } } // otherwise, we always follow the blocked list diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index a81fe6e9bd3..22d380e8e68 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -46,11 +46,6 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error } cmd := git.NewCommand(ctx, "remote", "add").AddDynamicArguments(remoteName).AddArguments("--mirror=fetch").AddDynamicArguments(addr) - if strings.Contains(addr, "://") && strings.Contains(addr, "@") { - cmd.SetDescription(fmt.Sprintf("remote add %s --mirror=fetch %s [repo_path: %s]", remoteName, util.SanitizeCredentialURLs(addr), repoPath)) - } else { - cmd.SetDescription(fmt.Sprintf("remote add %s --mirror=fetch %s [repo_path: %s]", remoteName, addr, repoPath)) - } _, _, err = cmd.RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { return err @@ -66,11 +61,6 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error } cmd = git.NewCommand(ctx, "remote", "add").AddDynamicArguments(remoteName).AddArguments("--mirror=fetch").AddDynamicArguments(wikiRemotePath) - if strings.Contains(wikiRemotePath, "://") && strings.Contains(wikiRemotePath, "@") { - cmd.SetDescription(fmt.Sprintf("remote add %s --mirror=fetch %s [repo_path: %s]", remoteName, util.SanitizeCredentialURLs(wikiRemotePath), wikiPath)) - } else { - cmd.SetDescription(fmt.Sprintf("remote add %s --mirror=fetch %s [repo_path: %s]", remoteName, wikiRemotePath, wikiPath)) - } _, _, err = cmd.RunStdString(&git.RunOpts{Dir: wikiPath}) if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") { return err @@ -197,7 +187,6 @@ func pruneBrokenReferences(ctx context.Context, stderrBuilder.Reset() stdoutBuilder.Reset() pruneErr := git.NewCommand(ctx, "remote", "prune").AddDynamicArguments(m.GetRemoteName()). - SetDescription(fmt.Sprintf("Mirror.runSync %ssPrune references: %s ", wiki, m.Repo.FullName())). Run(&git.RunOpts{ Timeout: timeout, Dir: repoPath, @@ -248,15 +237,13 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo stdoutBuilder := strings.Builder{} stderrBuilder := strings.Builder{} - if err := cmd. - SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())). - Run(&git.RunOpts{ - Timeout: timeout, - Dir: repoPath, - Env: envs, - Stdout: &stdoutBuilder, - Stderr: &stderrBuilder, - }); err != nil { + if err := cmd.Run(&git.RunOpts{ + Timeout: timeout, + Dir: repoPath, + Env: envs, + Stdout: &stdoutBuilder, + Stderr: &stderrBuilder, + }); err != nil { stdout := stdoutBuilder.String() stderr := stderrBuilder.String() @@ -275,14 +262,12 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo // Successful prune - reattempt mirror stderrBuilder.Reset() stdoutBuilder.Reset() - if err = cmd. - SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())). - Run(&git.RunOpts{ - Timeout: timeout, - Dir: repoPath, - Stdout: &stdoutBuilder, - Stderr: &stderrBuilder, - }); err != nil { + if err = cmd.Run(&git.RunOpts{ + Timeout: timeout, + Dir: repoPath, + Stdout: &stdoutBuilder, + Stderr: &stderrBuilder, + }); err != nil { stdout := stdoutBuilder.String() stderr := stderrBuilder.String() @@ -346,7 +331,6 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo stderrBuilder.Reset() stdoutBuilder.Reset() if err := git.NewCommand(ctx, "remote", "update", "--prune").AddDynamicArguments(m.GetRemoteName()). - SetDescription(fmt.Sprintf("Mirror.runSync Wiki: %s ", m.Repo.FullName())). Run(&git.RunOpts{ Timeout: timeout, Dir: wikiPath, @@ -373,7 +357,6 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo stdoutBuilder.Reset() if err = git.NewCommand(ctx, "remote", "update", "--prune").AddDynamicArguments(m.GetRemoteName()). - SetDescription(fmt.Sprintf("Mirror.runSync Wiki: %s ", m.Repo.FullName())). Run(&git.RunOpts{ Timeout: timeout, Dir: wikiPath, diff --git a/services/mirror/mirror_push.go b/services/mirror/mirror_push.go index 21ba0afeffd..02ff97b1f03 100644 --- a/services/mirror/mirror_push.go +++ b/services/mirror/mirror_push.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "regexp" - "strings" "time" "code.gitea.io/gitea/models/db" @@ -31,11 +30,6 @@ var stripExitStatus = regexp.MustCompile(`exit status \d+ - `) func AddPushMirrorRemote(ctx context.Context, m *repo_model.PushMirror, addr string) error { addRemoteAndConfig := func(addr, path string) error { cmd := git.NewCommand(ctx, "remote", "add", "--mirror=push").AddDynamicArguments(m.RemoteName, addr) - if strings.Contains(addr, "://") && strings.Contains(addr, "@") { - cmd.SetDescription(fmt.Sprintf("remote add %s --mirror=push %s [repo_path: %s]", m.RemoteName, util.SanitizeCredentialURLs(addr), path)) - } else { - cmd.SetDescription(fmt.Sprintf("remote add %s --mirror=push %s [repo_path: %s]", m.RemoteName, addr, path)) - } if _, _, err := cmd.RunStdString(&git.RunOpts{Dir: path}); err != nil { return err } diff --git a/services/org/org.go b/services/org/org.go index c19572a1230..3d30ae21a39 100644 --- a/services/org/org.go +++ b/services/org/org.go @@ -7,17 +7,45 @@ import ( "context" "fmt" - "code.gitea.io/gitea/models" + actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" org_model "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" repo_model "code.gitea.io/gitea/models/repo" + secret_model "code.gitea.io/gitea/models/secret" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" repo_service "code.gitea.io/gitea/services/repository" ) +// deleteOrganization deletes models associated to an organization. +func deleteOrganization(ctx context.Context, org *org_model.Organization) error { + if org.Type != user_model.UserTypeOrganization { + return fmt.Errorf("%s is a user not an organization", org.Name) + } + + if err := db.DeleteBeans(ctx, + &org_model.Team{OrgID: org.ID}, + &org_model.OrgUser{OrgID: org.ID}, + &org_model.TeamUser{OrgID: org.ID}, + &org_model.TeamUnit{OrgID: org.ID}, + &org_model.TeamInvite{OrgID: org.ID}, + &secret_model.Secret{OwnerID: org.ID}, + &user_model.Blocking{BlockerID: org.ID}, + &actions_model.ActionRunner{OwnerID: org.ID}, + &actions_model.ActionRunnerToken{OwnerID: org.ID}, + ); err != nil { + return fmt.Errorf("DeleteBeans: %w", err) + } + + if _, err := db.GetEngine(ctx).ID(org.ID).Delete(new(user_model.User)); err != nil { + return fmt.Errorf("Delete: %w", err) + } + + return nil +} + // DeleteOrganization completely and permanently deletes everything of organization. func DeleteOrganization(ctx context.Context, org *org_model.Organization, purge bool) error { ctx, committer, err := db.TxContext(ctx) @@ -38,17 +66,17 @@ func DeleteOrganization(ctx context.Context, org *org_model.Organization, purge if err != nil { return fmt.Errorf("GetRepositoryCount: %w", err) } else if count > 0 { - return models.ErrUserOwnRepos{UID: org.ID} + return repo_model.ErrUserOwnRepos{UID: org.ID} } // Check ownership of packages. if ownsPackages, err := packages_model.HasOwnerPackages(ctx, org.ID); err != nil { return fmt.Errorf("HasOwnerPackages: %w", err) } else if ownsPackages { - return models.ErrUserOwnPackages{UID: org.ID} + return packages_model.ErrUserOwnPackages{UID: org.ID} } - if err := org_model.DeleteOrganization(ctx, org); err != nil { + if err := deleteOrganization(ctx, org); err != nil { return fmt.Errorf("DeleteOrganization: %w", err) } diff --git a/services/org/org_test.go b/services/org/org_test.go index e7d2a18ea96..791404c5c89 100644 --- a/services/org/org_test.go +++ b/services/org/org_test.go @@ -6,9 +6,9 @@ package org import ( "testing" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -30,7 +30,7 @@ func TestDeleteOrganization(t *testing.T) { org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) err := DeleteOrganization(db.DefaultContext, org, false) assert.Error(t, err) - assert.True(t, models.IsErrUserOwnRepos(err)) + assert.True(t, repo_model.IsErrUserOwnRepos(err)) user := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 5}) assert.Error(t, DeleteOrganization(db.DefaultContext, user, false)) diff --git a/services/org/team.go b/services/org/team.go index 3688e684339..ee3bd898ea7 100644 --- a/services/org/team.go +++ b/services/org/team.go @@ -141,11 +141,14 @@ func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeA // Update access for team members if needed. if authChanged { - if err = t.LoadRepositories(ctx); err != nil { - return fmt.Errorf("LoadRepositories: %w", err) + repos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{ + TeamID: t.ID, + }) + if err != nil { + return fmt.Errorf("GetTeamRepositories: %w", err) } - for _, repo := range t.Repos { + for _, repo := range repos { if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil { return fmt.Errorf("recalculateTeamAccesses: %w", err) } @@ -172,10 +175,6 @@ func DeleteTeam(ctx context.Context, t *organization.Team) error { } defer committer.Close() - if err := t.LoadRepositories(ctx); err != nil { - return err - } - if err := t.LoadMembers(ctx); err != nil { return err } @@ -301,8 +300,11 @@ func AddTeamMember(ctx context.Context, team *organization.Team, user *user_mode // FIXME: Update watch repos batchly if setting.Service.AutoWatchNewRepos { // Get team and its repositories. - if err := team.LoadRepositories(ctx); err != nil { - log.Error("team.LoadRepositories failed: %v", err) + repos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{ + TeamID: team.ID, + }) + if err != nil { + log.Error("GetTeamRepositories failed: %v", err) } // FIXME: in the goroutine, it can't access the "ctx", it could only use db.DefaultContext at the moment @@ -312,7 +314,7 @@ func AddTeamMember(ctx context.Context, team *organization.Team, user *user_mode log.Error("watch repo failed: %v", err) } } - }(team.Repos) + }(repos) } return nil @@ -332,7 +334,10 @@ func removeTeamMember(ctx context.Context, team *organization.Team, user *user_m team.NumMembers-- - if err := team.LoadRepositories(ctx); err != nil { + repos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{ + TeamID: team.ID, + }) + if err != nil { return err } @@ -350,7 +355,7 @@ func removeTeamMember(ctx context.Context, team *organization.Team, user *user_m } // Delete access to team repositories. - for _, repo := range team.Repos { + for _, repo := range repos { if err := access_model.RecalculateUserAccess(ctx, repo, user.ID); err != nil { return err } diff --git a/services/org/team_test.go b/services/org/team_test.go index 58b8e0803c4..3791776e46e 100644 --- a/services/org/team_test.go +++ b/services/org/team_test.go @@ -121,7 +121,7 @@ func TestDeleteTeam(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) accessMode, err := access_model.AccessLevel(db.DefaultContext, user, repo) assert.NoError(t, err) - assert.True(t, accessMode < perm.AccessModeWrite) + assert.Less(t, accessMode, perm.AccessModeWrite) } func TestAddTeamMember(t *testing.T) { @@ -189,9 +189,12 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { testTeamRepositories := func(teamID int64, repoIDs []int64) { team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) - assert.NoError(t, team.LoadRepositories(db.DefaultContext), "%s: GetRepositories", team.Name) - assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name) - assert.Len(t, team.Repos, len(repoIDs), "%s: repo count", team.Name) + repos, err := repo_model.GetTeamRepositories(db.DefaultContext, &repo_model.SearchTeamRepoOptions{ + TeamID: team.ID, + }) + assert.NoError(t, err, "%s: GetTeamRepositories", team.Name) + assert.Len(t, repos, team.NumRepos, "%s: len repo", team.Name) + assert.Len(t, repos, len(repoIDs), "%s: repo count", team.Name) for i, rid := range repoIDs { if rid > 0 { assert.True(t, repo_service.HasRepository(db.DefaultContext, team, rid), "%s: HasRepository(%d) %d", rid, i) @@ -310,5 +313,5 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { assert.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, rid), "DeleteRepository %d", i) } } - assert.NoError(t, organization.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization") + assert.NoError(t, DeleteOrganization(db.DefaultContext, org, false), "DeleteOrganization") } diff --git a/services/org/user.go b/services/org/user.go index 0627860fe7c..0e74d006bb7 100644 --- a/services/org/user.go +++ b/services/org/user.go @@ -60,7 +60,7 @@ func RemoveOrgUser(ctx context.Context, org *organization.Organization, user *us } // Delete all repository accesses and unwatch them. - env, err := organization.AccessibleReposEnv(ctx, org, user.ID) + env, err := repo_model.AccessibleReposEnv(ctx, org, user.ID) if err != nil { return fmt.Errorf("AccessibleReposEnv: %w", err) } diff --git a/services/packages/arch/repository.go b/services/packages/arch/repository.go index ab1b85ae958..6731d9a1ac7 100644 --- a/services/packages/arch/repository.go +++ b/services/packages/arch/repository.go @@ -371,11 +371,12 @@ func writeDescription(tw *tar.Writer, opts *entryOptions) error { {"BUILDDATE", fmt.Sprintf("%d", opts.FileMetadata.BuildDate)}, {"PACKAGER", opts.FileMetadata.Packager}, {"PROVIDES", strings.Join(opts.FileMetadata.Provides, "\n")}, + {"REPLACES", strings.Join(opts.FileMetadata.Replaces, "\n")}, + {"CONFLICTS", strings.Join(opts.FileMetadata.Conflicts, "\n")}, {"DEPENDS", strings.Join(opts.FileMetadata.Depends, "\n")}, {"OPTDEPENDS", strings.Join(opts.FileMetadata.OptDepends, "\n")}, {"MAKEDEPENDS", strings.Join(opts.FileMetadata.MakeDepends, "\n")}, {"CHECKDEPENDS", strings.Join(opts.FileMetadata.CheckDepends, "\n")}, - {"XDATA", strings.Join(opts.FileMetadata.XData, "\n")}, }) } diff --git a/services/pull/check.go b/services/pull/check.go index 736be4611be..bffca394a8b 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -11,7 +11,6 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -36,7 +35,7 @@ var prPatchCheckerQueue *queue.WorkerPoolQueue[string] var ( ErrIsClosed = errors.New("pull is closed") - ErrUserNotAllowedToMerge = models.ErrDisallowedToMerge{} + ErrUserNotAllowedToMerge = ErrDisallowedToMerge{} ErrHasMerged = errors.New("has already been merged") ErrIsWorkInProgress = errors.New("work in progress PRs cannot be merged") ErrIsChecking = errors.New("cannot merge while conflict checking is in progress") @@ -106,7 +105,7 @@ func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *acc } if err := CheckPullBranchProtections(ctx, pr, false); err != nil { - if !models.IsErrDisallowedToMerge(err) { + if !IsErrDisallowedToMerge(err) { log.Error("Error whilst checking pull branch protection for %-v: %v", pr, err) return err } diff --git a/services/pull/merge.go b/services/pull/merge.go index a3fbe4f627b..fba85f1e519 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -13,7 +13,6 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -30,6 +29,7 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" issue_service "code.gitea.io/gitea/services/issue" notify_service "code.gitea.io/gitea/services/notify" ) @@ -159,6 +159,27 @@ func GetDefaultMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr return getMergeMessage(ctx, baseGitRepo, pr, mergeStyle, nil) } +// ErrInvalidMergeStyle represents an error if merging with disabled merge strategy +type ErrInvalidMergeStyle struct { + ID int64 + Style repo_model.MergeStyle +} + +// IsErrInvalidMergeStyle checks if an error is a ErrInvalidMergeStyle. +func IsErrInvalidMergeStyle(err error) bool { + _, ok := err.(ErrInvalidMergeStyle) + return ok +} + +func (err ErrInvalidMergeStyle) Error() string { + return fmt.Sprintf("merge strategy is not allowed or is invalid [repo_id: %d, strategy: %s]", + err.ID, err.Style) +} + +func (err ErrInvalidMergeStyle) Unwrap() error { + return util.ErrInvalidArgument +} + // Merge merges pull request to base repository. // Caller should check PR is ready to be merged (review and status checks) func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, wasAutoMerged bool) error { @@ -179,7 +200,7 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U // Check if merge style is correct and allowed if !prConfig.IsMergeStyleAllowed(mergeStyle) { - return models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle} + return ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle} } releaser, err := globallock.Lock(ctx, getPullWorkingLockKey(pr.ID)) @@ -283,7 +304,7 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use return "", err } default: - return "", models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle} + return "", ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle} } // OK we should cache our current head and origin/headbranch @@ -374,13 +395,66 @@ func commitAndSignNoAuthor(ctx *mergeContext, message string) error { return nil } +// ErrMergeConflicts represents an error if merging fails with a conflict +type ErrMergeConflicts struct { + Style repo_model.MergeStyle + StdOut string + StdErr string + Err error +} + +// IsErrMergeConflicts checks if an error is a ErrMergeConflicts. +func IsErrMergeConflicts(err error) bool { + _, ok := err.(ErrMergeConflicts) + return ok +} + +func (err ErrMergeConflicts) Error() string { + return fmt.Sprintf("Merge Conflict Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) +} + +// ErrMergeUnrelatedHistories represents an error if merging fails due to unrelated histories +type ErrMergeUnrelatedHistories struct { + Style repo_model.MergeStyle + StdOut string + StdErr string + Err error +} + +// IsErrMergeUnrelatedHistories checks if an error is a ErrMergeUnrelatedHistories. +func IsErrMergeUnrelatedHistories(err error) bool { + _, ok := err.(ErrMergeUnrelatedHistories) + return ok +} + +func (err ErrMergeUnrelatedHistories) Error() string { + return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) +} + +// ErrMergeDivergingFastForwardOnly represents an error if a fast-forward-only merge fails because the branches diverge +type ErrMergeDivergingFastForwardOnly struct { + StdOut string + StdErr string + Err error +} + +// IsErrMergeDivergingFastForwardOnly checks if an error is a ErrMergeDivergingFastForwardOnly. +func IsErrMergeDivergingFastForwardOnly(err error) bool { + _, ok := err.(ErrMergeDivergingFastForwardOnly) + return ok +} + +func (err ErrMergeDivergingFastForwardOnly) Error() string { + return fmt.Sprintf("Merge DivergingFastForwardOnly Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) +} + func runMergeCommand(ctx *mergeContext, mergeStyle repo_model.MergeStyle, cmd *git.Command) error { if err := cmd.Run(ctx.RunOpts()); err != nil { // Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict if _, statErr := os.Stat(filepath.Join(ctx.tmpBasePath, ".git", "MERGE_HEAD")); statErr == nil { // We have a merge conflict error log.Debug("MergeConflict %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) - return models.ErrMergeConflicts{ + return ErrMergeConflicts{ Style: mergeStyle, StdOut: ctx.outbuf.String(), StdErr: ctx.errbuf.String(), @@ -388,7 +462,7 @@ func runMergeCommand(ctx *mergeContext, mergeStyle repo_model.MergeStyle, cmd *g } } else if strings.Contains(ctx.errbuf.String(), "refusing to merge unrelated histories") { log.Debug("MergeUnrelatedHistories %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) - return models.ErrMergeUnrelatedHistories{ + return ErrMergeUnrelatedHistories{ Style: mergeStyle, StdOut: ctx.outbuf.String(), StdErr: ctx.errbuf.String(), @@ -396,7 +470,7 @@ func runMergeCommand(ctx *mergeContext, mergeStyle repo_model.MergeStyle, cmd *g } } else if mergeStyle == repo_model.MergeStyleFastForwardOnly && strings.Contains(ctx.errbuf.String(), "Not possible to fast-forward, aborting") { log.Debug("MergeDivergingFastForwardOnly %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) - return models.ErrMergeDivergingFastForwardOnly{ + return ErrMergeDivergingFastForwardOnly{ StdOut: ctx.outbuf.String(), StdErr: ctx.errbuf.String(), Err: err, @@ -431,6 +505,25 @@ func IsUserAllowedToMerge(ctx context.Context, pr *issues_model.PullRequest, p a return false, nil } +// ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it. +type ErrDisallowedToMerge struct { + Reason string +} + +// IsErrDisallowedToMerge checks if an error is an ErrDisallowedToMerge. +func IsErrDisallowedToMerge(err error) bool { + _, ok := err.(ErrDisallowedToMerge) + return ok +} + +func (err ErrDisallowedToMerge) Error() string { + return fmt.Sprintf("not allowed to merge [reason: %s]", err.Reason) +} + +func (err ErrDisallowedToMerge) Unwrap() error { + return util.ErrPermissionDenied +} + // CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks) func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullRequest, skipProtectedFilesCheck bool) (err error) { if err = pr.LoadBaseRepo(ctx); err != nil { @@ -450,29 +543,29 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques return err } if !isPass { - return models.ErrDisallowedToMerge{ + return ErrDisallowedToMerge{ Reason: "Not all required status checks successful", } } if !issues_model.HasEnoughApprovals(ctx, pb, pr) { - return models.ErrDisallowedToMerge{ + return ErrDisallowedToMerge{ Reason: "Does not have enough approvals", } } if issues_model.MergeBlockedByRejectedReview(ctx, pb, pr) { - return models.ErrDisallowedToMerge{ + return ErrDisallowedToMerge{ Reason: "There are requested changes", } } if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pr) { - return models.ErrDisallowedToMerge{ + return ErrDisallowedToMerge{ Reason: "There are official review requests", } } if issues_model.MergeBlockedByOutdatedBranch(pb, pr) { - return models.ErrDisallowedToMerge{ + return ErrDisallowedToMerge{ Reason: "The head branch is behind the base branch", } } @@ -482,7 +575,7 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques } if pb.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) { - return models.ErrDisallowedToMerge{ + return ErrDisallowedToMerge{ Reason: "Changed protected files", } } @@ -511,7 +604,7 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use // Check if merge style is correct and allowed if !prConfig.IsMergeStyleAllowed(repo_model.MergeStyleManuallyMerged) { - return models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: repo_model.MergeStyleManuallyMerged} + return ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: repo_model.MergeStyleManuallyMerged} } objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName) diff --git a/services/pull/merge_prepare.go b/services/pull/merge_prepare.go index 88f6c037ebc..2e1cc8cf85c 100644 --- a/services/pull/merge_prepare.go +++ b/services/pull/merge_prepare.go @@ -14,7 +14,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -43,6 +42,23 @@ func (ctx *mergeContext) RunOpts() *git.RunOpts { } } +// ErrSHADoesNotMatch represents a "SHADoesNotMatch" kind of error. +type ErrSHADoesNotMatch struct { + Path string + GivenSHA string + CurrentSHA string +} + +// IsErrSHADoesNotMatch checks if an error is a ErrSHADoesNotMatch. +func IsErrSHADoesNotMatch(err error) bool { + _, ok := err.(ErrSHADoesNotMatch) + return ok +} + +func (err ErrSHADoesNotMatch) Error() string { + return fmt.Sprintf("sha does not match [given: %s, expected: %s]", err.GivenSHA, err.CurrentSHA) +} + func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, expectedHeadCommitID string) (mergeCtx *mergeContext, cancel context.CancelFunc, err error) { // Clone base repo. prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr) @@ -65,7 +81,7 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque } if strings.TrimSpace(trackingCommitID) != expectedHeadCommitID { defer cancel() - return nil, nil, models.ErrSHADoesNotMatch{ + return nil, nil, ErrSHADoesNotMatch{ GivenSHA: expectedHeadCommitID, CurrentSHA: trackingCommitID, } @@ -233,8 +249,27 @@ func getDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string, o return err } +// ErrRebaseConflicts represents an error if rebase fails with a conflict +type ErrRebaseConflicts struct { + Style repo_model.MergeStyle + CommitSHA string + StdOut string + StdErr string + Err error +} + +// IsErrRebaseConflicts checks if an error is a ErrRebaseConflicts. +func IsErrRebaseConflicts(err error) bool { + _, ok := err.(ErrRebaseConflicts) + return ok +} + +func (err ErrRebaseConflicts) Error() string { + return fmt.Sprintf("Rebase Error: %v: Whilst Rebasing: %s\n%s\n%s", err.Err, err.CommitSHA, err.StdErr, err.StdOut) +} + // rebaseTrackingOnToBase checks out the tracking branch as staging and rebases it on to the base branch -// if there is a conflict it will return a models.ErrRebaseConflicts +// if there is a conflict it will return an ErrRebaseConflicts func rebaseTrackingOnToBase(ctx *mergeContext, mergeStyle repo_model.MergeStyle) error { // Checkout head branch if err := git.NewCommand(ctx, "checkout", "-b").AddDynamicArguments(stagingBranch, trackingBranch). @@ -268,11 +303,11 @@ func rebaseTrackingOnToBase(ctx *mergeContext, mergeStyle repo_model.MergeStyle) } } if !ok { - log.Error("Unable to determine failing commit sha for failing rebase in temp repo for %-v. Cannot cast as models.ErrRebaseConflicts.", ctx.pr) + log.Error("Unable to determine failing commit sha for failing rebase in temp repo for %-v. Cannot cast as ErrRebaseConflicts.", ctx.pr) return fmt.Errorf("unable to git rebase staging on to base in temp repo for %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) } log.Debug("Conflict when rebasing staging on to base in %-v at %s: %v\n%s\n%s", ctx.pr, commitSha, err, ctx.outbuf.String(), ctx.errbuf.String()) - return models.ErrRebaseConflicts{ + return ErrRebaseConflicts{ CommitSHA: commitSha, Style: mergeStyle, StdOut: ctx.outbuf.String(), diff --git a/services/pull/patch.go b/services/pull/patch.go index 0934a86c89a..36ca9dbdb67 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -13,7 +13,6 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/unit" @@ -502,6 +501,29 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * return false, nil } +// ErrFilePathProtected represents a "FilePathProtected" kind of error. +type ErrFilePathProtected struct { + Message string + Path string +} + +// IsErrFilePathProtected checks if an error is an ErrFilePathProtected. +func IsErrFilePathProtected(err error) bool { + _, ok := err.(ErrFilePathProtected) + return ok +} + +func (err ErrFilePathProtected) Error() string { + if err.Message != "" { + return err.Message + } + return fmt.Sprintf("path is protected and can not be changed [path: %s]", err.Path) +} + +func (err ErrFilePathProtected) Unwrap() error { + return util.ErrPermissionDenied +} + // CheckFileProtection check file Protection func CheckFileProtection(repo *git.Repository, branchName, oldCommitID, newCommitID string, patterns []glob.Glob, limit int, env []string) ([]string, error) { if len(patterns) == 0 { @@ -525,7 +547,7 @@ func CheckFileProtection(repo *git.Repository, branchName, oldCommitID, newCommi } } if len(changedProtectedFiles) > 0 { - err = models.ErrFilePathProtected{ + err = ErrFilePathProtected{ Path: changedProtectedFiles[0], } } @@ -575,7 +597,7 @@ func checkPullFilesProtection(ctx context.Context, pr *issues_model.PullRequest, } pr.ChangedProtectedFiles, err = CheckFileProtection(gitRepo, pr.HeadBranch, pr.MergeBase, "tracking", pb.GetProtectedFilePatterns(), 10, os.Environ()) - if err != nil && !models.IsErrFilePathProtected(err) { + if err != nil && !IsErrFilePathProtected(err) { return err } return nil diff --git a/services/pull/pull.go b/services/pull/pull.go index 3362cb97ff7..0256c2c3f63 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -13,7 +13,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -224,6 +223,28 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error { return nil } +// ErrPullRequestHasMerged represents a "PullRequestHasMerged"-error +type ErrPullRequestHasMerged struct { + ID int64 + IssueID int64 + HeadRepoID int64 + BaseRepoID int64 + HeadBranch string + BaseBranch string +} + +// IsErrPullRequestHasMerged checks if an error is a ErrPullRequestHasMerged. +func IsErrPullRequestHasMerged(err error) bool { + _, ok := err.(ErrPullRequestHasMerged) + return ok +} + +// Error does pretty-printing :D +func (err ErrPullRequestHasMerged) Error() string { + return fmt.Sprintf("pull request has merged [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]", + err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch) +} + // ChangeTargetBranch changes the target branch of this pull request, as the given user. func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, targetBranch string) (err error) { releaser, err := globallock.Lock(ctx, getPullWorkingLockKey(pr.ID)) @@ -247,7 +268,7 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer } if pr.HasMerged { - return models.ErrPullRequestHasMerged{ + return ErrPullRequestHasMerged{ ID: pr.ID, IssueID: pr.Index, HeadRepoID: pr.HeadRepoID, @@ -654,7 +675,7 @@ func RetargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int6 if err = pr.Issue.LoadRepo(ctx); err != nil { errs = append(errs, err) } else if err = ChangeTargetBranch(ctx, pr, doer, targetBranch); err != nil && - !issues_model.IsErrIssueIsClosed(err) && !models.IsErrPullRequestHasMerged(err) && + !issues_model.IsErrIssueIsClosed(err) && !IsErrPullRequestHasMerged(err) && !issues_model.IsErrPullRequestAlreadyExists(err) { errs = append(errs, err) } diff --git a/services/pull/reviewer_test.go b/services/pull/reviewer_test.go index 1ff373bafb7..b106e2e89f5 100644 --- a/services/pull/reviewer_test.go +++ b/services/pull/reviewer_test.go @@ -30,7 +30,7 @@ func TestRepoGetReviewers(t *testing.T) { // should not include doer and remove the poster reviewers, err = pull_service.GetReviewers(ctx, repo1, 11, 2) assert.NoError(t, err) - assert.Len(t, reviewers, 0) + assert.Empty(t, reviewers) // should not include PR poster, if PR poster would be otherwise eligible reviewers, err = pull_service.GetReviewers(ctx, repo1, 11, 4) @@ -43,7 +43,7 @@ func TestRepoGetReviewers(t *testing.T) { reviewers, err = pull_service.GetReviewers(ctx, repo2, 2, 4) assert.NoError(t, err) assert.Len(t, reviewers, 1) - assert.EqualValues(t, reviewers[0].ID, 2) + assert.EqualValues(t, 2, reviewers[0].ID) // test private org repo repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) diff --git a/services/release/release.go b/services/release/release.go index 980a5e98e7f..84c60a105a4 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -9,7 +9,6 @@ import ( "fmt" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" @@ -26,6 +25,44 @@ import ( notify_service "code.gitea.io/gitea/services/notify" ) +// ErrInvalidTagName represents a "InvalidTagName" kind of error. +type ErrInvalidTagName struct { + TagName string +} + +// IsErrInvalidTagName checks if an error is a ErrInvalidTagName. +func IsErrInvalidTagName(err error) bool { + _, ok := err.(ErrInvalidTagName) + return ok +} + +func (err ErrInvalidTagName) Error() string { + return fmt.Sprintf("release tag name is not valid [tag_name: %s]", err.TagName) +} + +func (err ErrInvalidTagName) Unwrap() error { + return util.ErrInvalidArgument +} + +// ErrProtectedTagName represents a "ProtectedTagName" kind of error. +type ErrProtectedTagName struct { + TagName string +} + +// IsErrProtectedTagName checks if an error is a ErrProtectedTagName. +func IsErrProtectedTagName(err error) bool { + _, ok := err.(ErrProtectedTagName) + return ok +} + +func (err ErrProtectedTagName) Error() string { + return fmt.Sprintf("release tag name is protected [tag_name: %s]", err.TagName) +} + +func (err ErrProtectedTagName) Unwrap() error { + return util.ErrPermissionDenied +} + func createTag(ctx context.Context, gitRepo *git.Repository, rel *repo_model.Release, msg string) (bool, error) { err := rel.LoadAttributes(ctx) if err != nil { @@ -58,7 +95,7 @@ func createTag(ctx context.Context, gitRepo *git.Repository, rel *repo_model.Rel return false, err } if !isAllowed { - return false, models.ErrProtectedTagName{ + return false, ErrProtectedTagName{ TagName: rel.TagName, } } @@ -71,7 +108,7 @@ func createTag(ctx context.Context, gitRepo *git.Repository, rel *repo_model.Rel if len(msg) > 0 { if err = gitRepo.CreateAnnotatedTag(rel.TagName, msg, commit.ID.String()); err != nil { if strings.Contains(err.Error(), "is not a valid tag name") { - return false, models.ErrInvalidTagName{ + return false, ErrInvalidTagName{ TagName: rel.TagName, } } @@ -79,7 +116,7 @@ func createTag(ctx context.Context, gitRepo *git.Repository, rel *repo_model.Rel } } else if err = gitRepo.CreateTag(rel.TagName, commit.ID.String()); err != nil { if strings.Contains(err.Error(), "is not a valid tag name") { - return false, models.ErrInvalidTagName{ + return false, ErrInvalidTagName{ TagName: rel.TagName, } } @@ -159,13 +196,32 @@ func CreateRelease(gitRepo *git.Repository, rel *repo_model.Release, attachmentU return nil } +// ErrTagAlreadyExists represents an error that tag with such name already exists. +type ErrTagAlreadyExists struct { + TagName string +} + +// IsErrTagAlreadyExists checks if an error is an ErrTagAlreadyExists. +func IsErrTagAlreadyExists(err error) bool { + _, ok := err.(ErrTagAlreadyExists) + return ok +} + +func (err ErrTagAlreadyExists) Error() string { + return fmt.Sprintf("tag already exists [name: %s]", err.TagName) +} + +func (err ErrTagAlreadyExists) Unwrap() error { + return util.ErrAlreadyExist +} + // CreateNewTag creates a new repository tag func CreateNewTag(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commit, tagName, msg string) error { has, err := repo_model.IsReleaseExist(ctx, repo.ID, tagName) if err != nil { return err } else if has { - return models.ErrTagAlreadyExists{ + return ErrTagAlreadyExists{ TagName: tagName, } } @@ -320,13 +376,12 @@ func DeleteReleaseByID(ctx context.Context, repo *repo_model.Repository, rel *re return err } if !isAllowed { - return models.ErrProtectedTagName{ + return ErrProtectedTagName{ TagName: rel.TagName, } } 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") { log.Error("DeleteReleaseByID (git tag -d): %d in %v Failed:\nStdout: %s\nError: %v", rel.ID, repo, stdout, err) return fmt.Errorf("git tag -d: %w", err) diff --git a/services/release/release_test.go b/services/release/release_test.go index 3d0681f1e17..95a54832b9e 100644 --- a/services/release/release_test.go +++ b/services/release/release_test.go @@ -228,7 +228,7 @@ func TestRelease_Update(t *testing.T) { IsTag: false, } assert.NoError(t, CreateRelease(gitRepo, release, nil, "")) - assert.Greater(t, release.ID, int64(0)) + assert.Positive(t, release.ID) release.IsDraft = false tagName := release.TagName diff --git a/services/repository/adopt.go b/services/repository/adopt.go index 615f4d482c0..e37909e7ab2 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -92,7 +92,6 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR } if stdout, _, err := git.NewCommand(ctx, "update-server-info"). - SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) return fmt.Errorf("CreateRepository(git update-server-info): %w", err) diff --git a/services/repository/archiver/archiver_test.go b/services/repository/archiver/archiver_test.go index 2ab18edf491..1d0c6e513d8 100644 --- a/services/repository/archiver/archiver_test.go +++ b/services/repository/archiver/archiver_test.go @@ -4,7 +4,6 @@ package archiver import ( - "errors" "testing" "time" @@ -121,7 +120,7 @@ func TestArchive_Basic(t *testing.T) { // It's fine to go ahead and set it to nil now. assert.Equal(t, zipReq, zipReq2) - assert.False(t, zipReq == zipReq2) + assert.NotSame(t, zipReq, zipReq2) // Same commit, different compression formats should have different names. // Ideally, the extension would match what we originally requested. @@ -131,5 +130,5 @@ func TestArchive_Basic(t *testing.T) { func TestErrUnknownArchiveFormat(t *testing.T) { err := ErrUnknownArchiveFormat{RequestFormat: "master"} - assert.True(t, errors.Is(err, ErrUnknownArchiveFormat{})) + assert.ErrorIs(t, err, ErrUnknownArchiveFormat{}) } diff --git a/services/repository/branch.go b/services/repository/branch.go index 3a95aab2649..fc476298ca5 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -9,7 +9,6 @@ import ( "fmt" "strings" - "code.gitea.io/gitea/models" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" @@ -31,6 +30,7 @@ import ( "code.gitea.io/gitea/modules/util" webhook_module "code.gitea.io/gitea/modules/webhook" notify_service "code.gitea.io/gitea/services/notify" + release_service "code.gitea.io/gitea/services/release" files_service "code.gitea.io/gitea/services/repository/files" "xorm.io/builder" @@ -274,7 +274,7 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri BranchName: branchRefName, } case refName == git.TagPrefix+name: - return models.ErrTagAlreadyExists{ + return release_service.ErrTagAlreadyExists{ TagName: name, } } diff --git a/services/repository/check.go b/services/repository/check.go index 5cdcc146797..acca15daf2a 100644 --- a/services/repository/check.go +++ b/services/repository/check.go @@ -86,8 +86,7 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args git.TrustedCmdA // GitGcRepo calls 'git gc' to remove unnecessary files and optimize the local repository func GitGcRepo(ctx context.Context, repo *repo_model.Repository, timeout time.Duration, args git.TrustedCmdArgs) error { log.Trace("Running git gc on %-v", repo) - command := git.NewCommand(ctx, "gc").AddArguments(args...). - SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())) + command := git.NewCommand(ctx, "gc").AddArguments(args...) var stdout string var err error stdout, _, err = command.RunStdString(&git.RunOpts{Timeout: timeout, Dir: repo.RepoPath()}) diff --git a/services/repository/create.go b/services/repository/create.go index 14e625d962a..a3199f2a40a 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -68,7 +68,6 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, // Clone to temporary path and do the init commit. if stdout, _, err := git.NewCommand(ctx, "clone").AddDynamicArguments(repoPath, tmpDir). - SetDescription(fmt.Sprintf("prepareRepoCommit (git clone): %s to %s", repoPath, tmpDir)). RunStdString(&git.RunOpts{Dir: "", Env: env}); err != nil { log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err) return fmt.Errorf("git clone: %w", err) @@ -301,7 +300,6 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt } if stdout, _, err := git.NewCommand(ctx, "update-server-info"). - SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)). RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) rollbackRepo = repo @@ -314,9 +312,7 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt if len(opts.License) > 0 { licenses = append(licenses, ConvertLicenseName(opts.License)) - stdout, _, err := git.NewCommand(ctx, "rev-parse", "HEAD"). - SetDescription(fmt.Sprintf("CreateRepository(git rev-parse HEAD): %s", repoPath)). - RunStdString(&git.RunOpts{Dir: repoPath}) + stdout, _, err := git.NewCommand(ctx, "rev-parse", "HEAD").RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil { log.Error("CreateRepository(git rev-parse HEAD) in %v: Stdout: %s\nError: %v", repo, stdout, err) rollbackRepo = repo diff --git a/services/repository/delete.go b/services/repository/delete.go index f33bae77901..61e39fe1056 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -7,11 +7,9 @@ import ( "context" "fmt" - "code.gitea.io/gitea/models" actions_model "code.gitea.io/gitea/models/actions" activities_model "code.gitea.io/gitea/models/activities" admin_model "code.gitea.io/gitea/models/admin" - asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -76,16 +74,11 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID } // Delete Deploy Keys - deployKeys, err := db.Find[asymkey_model.DeployKey](ctx, asymkey_model.ListDeployKeysOptions{RepoID: repoID}) + deleted, err := asymkey_service.DeleteRepoDeployKeys(ctx, repoID) if err != nil { - return fmt.Errorf("listDeployKeys: %w", err) - } - needRewriteKeysFile := len(deployKeys) > 0 - for _, dKey := range deployKeys { - if err := models.DeleteDeployKey(ctx, doer, dKey.ID); err != nil { - return fmt.Errorf("deleteDeployKeys: %w", err) - } + return err } + needRewriteKeysFile := deleted > 0 if cnt, err := sess.ID(repoID).Delete(&repo_model.Repository{}); err != nil { return err diff --git a/services/repository/files/cherry_pick.go b/services/repository/files/cherry_pick.go index 451a1821557..10545e9e038 100644 --- a/services/repository/files/cherry_pick.go +++ b/services/repository/files/cherry_pick.go @@ -8,7 +8,6 @@ import ( "fmt" "strings" - "code.gitea.io/gitea/models" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" @@ -17,6 +16,22 @@ import ( "code.gitea.io/gitea/services/pull" ) +// ErrCommitIDDoesNotMatch represents a "CommitIDDoesNotMatch" kind of error. +type ErrCommitIDDoesNotMatch struct { + GivenCommitID string + CurrentCommitID string +} + +// IsErrCommitIDDoesNotMatch checks if an error is a ErrCommitIDDoesNotMatch. +func IsErrCommitIDDoesNotMatch(err error) bool { + _, ok := err.(ErrCommitIDDoesNotMatch) + return ok +} + +func (err ErrCommitIDDoesNotMatch) Error() string { + return fmt.Sprintf("file CommitID does not match [given: %s, expected: %s]", err.GivenCommitID, err.CurrentCommitID) +} + // CherryPick cherrypicks or reverts a commit to the given repository func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, revert bool, opts *ApplyDiffPatchOptions) (*structs.FileResponse, error) { if err := opts.Validate(ctx, repo, doer); err != nil { @@ -57,7 +72,7 @@ func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_mod } opts.LastCommitID = lastCommitID.String() if commit.ID.String() != opts.LastCommitID { - return nil, models.ErrCommitIDDoesNotMatch{ + return nil, ErrCommitIDDoesNotMatch{ GivenCommitID: opts.LastCommitID, CurrentCommitID: opts.LastCommitID, } diff --git a/services/repository/files/content.go b/services/repository/files/content.go index 95e7c7087c0..0ab7422ce2e 100644 --- a/services/repository/files/content.go +++ b/services/repository/files/content.go @@ -10,7 +10,6 @@ import ( "path" "strings" - "code.gitea.io/gitea/models" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" @@ -53,7 +52,7 @@ func GetContentsOrList(ctx context.Context, repo *repo_model.Repository, treePat // Check that the path given in opts.treePath is valid (not a git path) cleanTreePath := CleanUploadFileName(treePath) if cleanTreePath == "" && treePath != "" { - return nil, models.ErrFilenameInvalid{ + return nil, ErrFilenameInvalid{ Path: treePath, } } @@ -128,7 +127,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref // Check that the path given in opts.treePath is valid (not a git path) cleanTreePath := CleanUploadFileName(treePath) if cleanTreePath == "" && treePath != "" { - return nil, models.ErrFilenameInvalid{ + return nil, ErrFilenameInvalid{ Path: treePath, } } diff --git a/services/repository/files/file.go b/services/repository/files/file.go index 16783f5b5f8..d7ca8e79e5b 100644 --- a/services/repository/files/file.go +++ b/services/repository/files/file.go @@ -156,6 +156,25 @@ func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *user_m return authorUser, committerUser } +// ErrFilenameInvalid represents a "FilenameInvalid" kind of error. +type ErrFilenameInvalid struct { + Path string +} + +// IsErrFilenameInvalid checks if an error is an ErrFilenameInvalid. +func IsErrFilenameInvalid(err error) bool { + _, ok := err.(ErrFilenameInvalid) + return ok +} + +func (err ErrFilenameInvalid) Error() string { + return fmt.Sprintf("path contains a malformed path component [path: %s]", err.Path) +} + +func (err ErrFilenameInvalid) Unwrap() error { + return util.ErrInvalidArgument +} + // CleanUploadFileName Trims a filename and returns empty string if it is a .git directory func CleanUploadFileName(name string) string { // Rebase the filename diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index ab0e7ffd36f..38c17b4073d 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -8,7 +8,6 @@ import ( "fmt" "strings" - "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -16,9 +15,29 @@ import ( "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" asymkey_service "code.gitea.io/gitea/services/asymkey" ) +// ErrUserCannotCommit represents "UserCannotCommit" kind of error. +type ErrUserCannotCommit struct { + UserName string +} + +// IsErrUserCannotCommit checks if an error is an ErrUserCannotCommit. +func IsErrUserCannotCommit(err error) bool { + _, ok := err.(ErrUserCannotCommit) + return ok +} + +func (err ErrUserCannotCommit) Error() string { + return fmt.Sprintf("user cannot commit to repo [user: %s]", err.UserName) +} + +func (err ErrUserCannotCommit) Unwrap() error { + return util.ErrPermissionDenied +} + // ApplyDiffPatchOptions holds the repository diff patch update options type ApplyDiffPatchOptions struct { LastCommitID string @@ -74,7 +93,7 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode if protectedBranch != nil { protectedBranch.Repo = repo if !protectedBranch.CanUserPush(ctx, doer) { - return models.ErrUserCannotCommit{ + return ErrUserCannotCommit{ UserName: doer.LowerName, } } @@ -85,7 +104,7 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode if !asymkey_service.IsErrWontSign(err) { return err } - return models.ErrUserCannotCommit{ + return ErrUserCannotCommit{ UserName: doer.LowerName, } } @@ -137,7 +156,7 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user } opts.LastCommitID = lastCommitID.String() if commit.ID.String() != opts.LastCommitID { - return nil, models.ErrCommitIDDoesNotMatch{ + return nil, ErrCommitIDDoesNotMatch{ GivenCommitID: opts.LastCommitID, CurrentCommitID: opts.LastCommitID, } diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index 30ab22db1e4..138af991f9b 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -13,7 +13,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" @@ -187,7 +186,7 @@ func (t *TemporaryUploadRepository) AddObjectToIndex(mode, objectHash, objectPat if _, _, err := git.NewCommand(t.ctx, "update-index", "--add", "--replace", "--cacheinfo").AddDynamicArguments(mode, objectHash, objectPath).RunStdString(&git.RunOpts{Dir: t.basePath}); err != nil { stderr := err.Error() if matched, _ := regexp.MatchString(".*Invalid path '.*", stderr); matched { - return models.ErrFilePathInvalid{ + return ErrFilePathInvalid{ Message: objectPath, Path: objectPath, } diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index e3a7f3b8b0e..6775186afdd 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -8,18 +8,37 @@ import ( "fmt" "net/url" - "code.gitea.io/gitea/models" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" ) +// ErrSHANotFound represents a "SHADoesNotMatch" kind of error. +type ErrSHANotFound struct { + SHA string +} + +// IsErrSHANotFound checks if an error is a ErrSHANotFound. +func IsErrSHANotFound(err error) bool { + _, ok := err.(ErrSHANotFound) + return ok +} + +func (err ErrSHANotFound) Error() string { + return fmt.Sprintf("sha not found [%s]", err.SHA) +} + +func (err ErrSHANotFound) Unwrap() error { + return util.ErrNotExist +} + // GetTreeBySHA get the GitTreeResponse of a repository using a sha hash. func GetTreeBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, sha string, page, perPage int, recursive bool) (*api.GitTreeResponse, error) { gitTree, err := gitRepo.GetTree(sha) if err != nil || gitTree == nil { - return nil, models.ErrSHANotFound{ + return nil, ErrSHANotFound{ // TODO: this error has never been catch outside of this function SHA: sha, } } diff --git a/services/repository/files/update.go b/services/repository/files/update.go index b1b64bacd99..a2763105b0c 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -11,7 +11,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -21,7 +20,9 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" asymkey_service "code.gitea.io/gitea/services/asymkey" + pull_service "code.gitea.io/gitea/services/pull" ) // IdentityOptions for a person's identity like an author or committer @@ -64,6 +65,26 @@ type RepoFileOptions struct { executable bool } +// ErrRepoFileDoesNotExist represents a "RepoFileDoesNotExist" kind of error. +type ErrRepoFileDoesNotExist struct { + Path string + Name string +} + +// IsErrRepoFileDoesNotExist checks if an error is a ErrRepoDoesNotExist. +func IsErrRepoFileDoesNotExist(err error) bool { + _, ok := err.(ErrRepoFileDoesNotExist) + return ok +} + +func (err ErrRepoFileDoesNotExist) Error() string { + return fmt.Sprintf("repository file does not exist [path: %s]", err.Path) +} + +func (err ErrRepoFileDoesNotExist) Unwrap() error { + return util.ErrNotExist +} + // ChangeRepoFiles adds, updates or removes multiple files in the given repository func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, opts *ChangeRepoFilesOptions) (*structs.FilesResponse, error) { err := repo.MustNotBeArchived() @@ -100,14 +121,14 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use // Check that the path given in opts.treePath is valid (not a git path) treePath := CleanUploadFileName(file.TreePath) if treePath == "" { - return nil, models.ErrFilenameInvalid{ + return nil, ErrFilenameInvalid{ Path: file.TreePath, } } // If there is a fromTreePath (we are copying it), also clean it up fromTreePath := CleanUploadFileName(file.FromTreePath) if fromTreePath == "" && file.FromTreePath != "" { - return nil, models.ErrFilenameInvalid{ + return nil, ErrFilenameInvalid{ Path: file.FromTreePath, } } @@ -185,7 +206,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use } } if !inFilelist { - return nil, models.ErrRepoFileDoesNotExist{ + return nil, ErrRepoFileDoesNotExist{ Path: file.TreePath, } } @@ -276,6 +297,63 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use return filesResponse, nil } +// ErrRepoFileAlreadyExists represents a "RepoFileAlreadyExist" kind of error. +type ErrRepoFileAlreadyExists struct { + Path string +} + +// IsErrRepoFileAlreadyExists checks if an error is a ErrRepoFileAlreadyExists. +func IsErrRepoFileAlreadyExists(err error) bool { + _, ok := err.(ErrRepoFileAlreadyExists) + return ok +} + +func (err ErrRepoFileAlreadyExists) Error() string { + return fmt.Sprintf("repository file already exists [path: %s]", err.Path) +} + +func (err ErrRepoFileAlreadyExists) Unwrap() error { + return util.ErrAlreadyExist +} + +// ErrFilePathInvalid represents a "FilePathInvalid" kind of error. +type ErrFilePathInvalid struct { + Message string + Path string + Name string + Type git.EntryMode +} + +// IsErrFilePathInvalid checks if an error is an ErrFilePathInvalid. +func IsErrFilePathInvalid(err error) bool { + _, ok := err.(ErrFilePathInvalid) + return ok +} + +func (err ErrFilePathInvalid) Error() string { + if err.Message != "" { + return err.Message + } + return fmt.Sprintf("path is invalid [path: %s]", err.Path) +} + +func (err ErrFilePathInvalid) Unwrap() error { + return util.ErrInvalidArgument +} + +// ErrSHAOrCommitIDNotProvided represents a "SHAOrCommitIDNotProvided" kind of error. +type ErrSHAOrCommitIDNotProvided struct{} + +// IsErrSHAOrCommitIDNotProvided checks if an error is a ErrSHAOrCommitIDNotProvided. +func IsErrSHAOrCommitIDNotProvided(err error) bool { + _, ok := err.(ErrSHAOrCommitIDNotProvided) + return ok +} + +func (err ErrSHAOrCommitIDNotProvided) Error() string { + return "a SHA or commit ID must be proved when updating a file" +} + // handles the check for various issues for ChangeRepoFiles func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRepoFilesOptions) error { if file.Operation == "update" || file.Operation == "delete" { @@ -286,7 +364,7 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep if file.SHA != "" { // If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error if file.SHA != fromEntry.ID.String() { - return models.ErrSHADoesNotMatch{ + return pull_service.ErrSHADoesNotMatch{ Path: file.Options.treePath, GivenSHA: file.SHA, CurrentSHA: fromEntry.ID.String(), @@ -299,7 +377,7 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep if changed, err := commit.FileChangedSinceCommit(file.Options.treePath, opts.LastCommitID); err != nil { return err } else if changed { - return models.ErrCommitIDDoesNotMatch{ + return ErrCommitIDDoesNotMatch{ GivenCommitID: opts.LastCommitID, CurrentCommitID: opts.LastCommitID, } @@ -309,7 +387,7 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep } else { // When updating a file, a lastCommitID or SHA needs to be given to make sure other commits // haven't been made. We throw an error if one wasn't provided. - return models.ErrSHAOrCommitIDNotProvided{} + return ErrSHAOrCommitIDNotProvided{} } file.Options.executable = fromEntry.IsExecutable() } @@ -332,7 +410,7 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep } if index < len(treePathParts)-1 { if !entry.IsDir() { - return models.ErrFilePathInvalid{ + return ErrFilePathInvalid{ Message: fmt.Sprintf("a file exists where you’re trying to create a subdirectory [path: %s]", subTreePath), Path: subTreePath, Name: part, @@ -340,14 +418,14 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep } } } else if entry.IsLink() { - return models.ErrFilePathInvalid{ + return ErrFilePathInvalid{ Message: fmt.Sprintf("a symbolic link exists where you’re trying to create a subdirectory [path: %s]", subTreePath), Path: subTreePath, Name: part, Type: git.EntryModeSymlink, } } else if entry.IsDir() { - return models.ErrFilePathInvalid{ + return ErrFilePathInvalid{ Message: fmt.Sprintf("a directory exists where you’re trying to create a file [path: %s]", subTreePath), Path: subTreePath, Name: part, @@ -355,7 +433,7 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep } } else if file.Options.fromTreePath != file.Options.treePath || file.Operation == "create" { // The entry shouldn't exist if we are creating new file or moving to a new path - return models.ErrRepoFileAlreadyExists{ + return ErrRepoFileAlreadyExists{ Path: file.Options.treePath, } } @@ -376,7 +454,7 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file if file.Operation == "create" { for _, indexFile := range filesInIndex { if indexFile == file.TreePath { - return models.ErrRepoFileAlreadyExists{ + return ErrRepoFileAlreadyExists{ Path: file.TreePath, } } @@ -479,12 +557,12 @@ func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, do isUnprotectedFile = protectedBranch.IsUnprotectedFile(globUnprotected, treePath) } if !canUserPush && !isUnprotectedFile { - return models.ErrUserCannotCommit{ + return ErrUserCannotCommit{ UserName: doer.LowerName, } } if protectedBranch.IsProtectedFile(globProtected, treePath) { - return models.ErrFilePathProtected{ + return pull_service.ErrFilePathProtected{ Path: treePath, } } @@ -495,7 +573,7 @@ func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, do if !asymkey_service.IsErrWontSign(err) { return err } - return models.ErrUserCannotCommit{ + return ErrUserCannotCommit{ UserName: doer.LowerName, } } diff --git a/services/repository/fork.go b/services/repository/fork.go index bc4fdf85627..cff0b1a4036 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -158,7 +158,6 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork } repoPath := repo_model.RepoPath(owner.Name, repo.Name) if stdout, _, err := cloneCmd.AddDynamicArguments(oldRepoPath, repoPath). - SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", opts.BaseRepo.FullName(), repo.FullName())). RunStdBytes(&git.RunOpts{Timeout: 10 * time.Minute}); err != nil { log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err) return fmt.Errorf("git clone: %w", err) @@ -169,7 +168,6 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork } if stdout, _, err := git.NewCommand(txCtx, "update-server-info"). - SetDescription(fmt.Sprintf("ForkRepository(git update-server-info): %s", repo.FullName())). RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err) return fmt.Errorf("git update-server-info: %w", err) diff --git a/services/repository/generate.go b/services/repository/generate.go index f2280de8b21..24cf9d1b9bf 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -237,7 +237,6 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r repoPath := repo.RepoPath() if stdout, _, err := git.NewCommand(ctx, "remote", "add", "origin").AddDynamicArguments(repoPath). - SetDescription(fmt.Sprintf("generateRepoCommit (git remote add): %s to %s", templateRepoPath, tmpDir)). RunStdString(&git.RunOpts{Dir: tmpDir, Env: env}); err != nil { log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err) return fmt.Errorf("git remote add: %w", err) @@ -369,7 +368,6 @@ func generateRepository(ctx context.Context, doer, owner *user_model.User, templ } if stdout, _, err := git.NewCommand(ctx, "update-server-info"). - SetDescription(fmt.Sprintf("GenerateRepository(git update-server-info): %s", repoPath)). RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { log.Error("GenerateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", generateRepo, stdout, err) return generateRepo, fmt.Errorf("error in GenerateRepository(git update-server-info): %w", err) diff --git a/services/repository/init.go b/services/repository/init.go index 817fa4abd77..c719e117863 100644 --- a/services/repository/init.go +++ b/services/repository/init.go @@ -34,7 +34,6 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi committerEmail := sig.Email if stdout, _, err := git.NewCommand(ctx, "add", "--all"). - SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)). RunStdString(&git.RunOpts{Dir: tmpPath}); err != nil { log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err) return fmt.Errorf("git add --all: %w", err) @@ -62,9 +61,8 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi ) if stdout, _, err := cmd. - SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)). RunStdString(&git.RunOpts{Dir: tmpPath, Env: env}); err != nil { - log.Error("Failed to commit: %v: Stdout: %s\nError: %v", cmd.String(), stdout, err) + log.Error("Failed to commit: %v: Stdout: %s\nError: %v", cmd.LogString(), stdout, err) return fmt.Errorf("git commit: %w", err) } @@ -73,7 +71,6 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi } if stdout, _, err := git.NewCommand(ctx, "push", "origin").AddDynamicArguments("HEAD:" + defaultBranch). - SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)). RunStdString(&git.RunOpts{Dir: tmpPath, Env: repo_module.InternalPushingEnvironment(u, repo)}); err != nil { log.Error("Failed to push back to HEAD: Stdout: %s\nError: %v", stdout, err) return fmt.Errorf("git push: %w", err) diff --git a/services/repository/license_test.go b/services/repository/license_test.go index 39e9738145c..9d3e0f36e36 100644 --- a/services/repository/license_test.go +++ b/services/repository/license_test.go @@ -65,7 +65,7 @@ func Test_detectLicense(t *testing.T) { result, err := detectLicense(strings.NewReader(tests[2].arg + tests[3].arg + tests[4].arg)) assert.NoError(t, err) t.Run("multiple licenses test", func(t *testing.T) { - assert.Equal(t, 3, len(result)) + assert.Len(t, result, 3) assert.Contains(t, result, tests[2].want[0]) assert.Contains(t, result, tests[3].want[0]) assert.Contains(t, result, tests[4].want[0]) diff --git a/services/repository/migrate.go b/services/repository/migrate.go index c627b46fab5..7fe63eb5ca7 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -122,7 +122,6 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, } if stdout, _, err := git.NewCommand(ctx, "update-server-info"). - SetDescription(fmt.Sprintf("MigrateRepositoryGitData(git update-server-info): %s", repoPath)). RunStdString(&git.RunOpts{Dir: repoPath}); err != nil { log.Error("MigrateRepositoryGitData(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) return repo, fmt.Errorf("error in MigrateRepositoryGitData(git update-server-info): %w", err) diff --git a/services/repository/repo_team.go b/services/repository/repo_team.go index 29c67893b23..672ee49fea9 100644 --- a/services/repository/repo_team.go +++ b/services/repository/repo_team.go @@ -63,7 +63,7 @@ func addRepositoryToTeam(ctx context.Context, t *organization.Team, repo *repo_m // If the team already has some repositories they will be left unchanged. func AddAllRepositoriesToTeam(ctx context.Context, t *organization.Team) error { return db.WithTx(ctx, func(ctx context.Context) error { - orgRepos, err := organization.GetOrgRepositories(ctx, t.OrgID) + orgRepos, err := repo_model.GetOrgRepositories(ctx, t.OrgID) if err != nil { return fmt.Errorf("get org repos: %w", err) } @@ -103,8 +103,15 @@ func RemoveAllRepositoriesFromTeam(ctx context.Context, t *organization.Team) (e // Note: Shall not be called if team includes all repositories func removeAllRepositoriesFromTeam(ctx context.Context, t *organization.Team) (err error) { e := db.GetEngine(ctx) + repos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{ + TeamID: t.ID, + }) + if err != nil { + return fmt.Errorf("GetTeamRepositories: %w", err) + } + // Delete all accesses. - for _, repo := range t.Repos { + for _, repo := range repos { if err := access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil { return err } diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 9a643469d9d..9ef28ddeb9f 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -9,7 +9,6 @@ import ( "os" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" @@ -285,7 +284,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName wikiRenamed = true } - if err := models.DeleteRepositoryTransfer(ctx, repo.ID); err != nil { + if err := repo_model.DeleteRepositoryTransfer(ctx, repo.ID); err != nil { return fmt.Errorf("deleteRepositoryTransfer: %w", err) } repo.Status = repo_model.RepositoryReady @@ -388,7 +387,7 @@ func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo // StartRepositoryTransfer transfer a repo from one owner to a new one. // it make repository into pending transfer state, if doer can not create repo for new owner. func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository, teams []*organization.Team) error { - if err := models.TestRepositoryReadyForTransfer(repo.Status); err != nil { + if err := repo_model.TestRepositoryReadyForTransfer(repo.Status); err != nil { return err } @@ -425,7 +424,7 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use // Make repo as pending for transfer repo.Status = repo_model.RepositoryPendingTransfer - if err := models.CreatePendingRepositoryTransfer(ctx, doer, newOwner, repo.ID, teams); err != nil { + if err := repo_model.CreatePendingRepositoryTransfer(ctx, doer, newOwner, repo.ID, teams); err != nil { return err } @@ -449,7 +448,7 @@ func CancelRepositoryTransfer(ctx context.Context, repo *repo_model.Repository) return err } - if err := models.DeleteRepositoryTransfer(ctx, repo.ID); err != nil { + if err := repo_model.DeleteRepositoryTransfer(ctx, repo.ID); err != nil { return err } diff --git a/services/repository/transfer_test.go b/services/repository/transfer_test.go index 67799eddcc3..91722fb8ae1 100644 --- a/services/repository/transfer_test.go +++ b/services/repository/transfer_test.go @@ -7,7 +7,6 @@ import ( "sync" "testing" - "code.gitea.io/gitea/models" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" @@ -86,36 +85,36 @@ func TestRepositoryTransfer(t *testing.T) { doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - transfer, err := models.GetPendingRepositoryTransfer(db.DefaultContext, repo) + transfer, err := repo_model.GetPendingRepositoryTransfer(db.DefaultContext, repo) assert.NoError(t, err) assert.NotNil(t, transfer) // Cancel transfer assert.NoError(t, CancelRepositoryTransfer(db.DefaultContext, repo)) - transfer, err = models.GetPendingRepositoryTransfer(db.DefaultContext, repo) + transfer, err = repo_model.GetPendingRepositoryTransfer(db.DefaultContext, repo) assert.Error(t, err) assert.Nil(t, transfer) - assert.True(t, models.IsErrNoPendingTransfer(err)) + assert.True(t, repo_model.IsErrNoPendingTransfer(err)) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - assert.NoError(t, models.CreatePendingRepositoryTransfer(db.DefaultContext, doer, user2, repo.ID, nil)) + assert.NoError(t, repo_model.CreatePendingRepositoryTransfer(db.DefaultContext, doer, user2, repo.ID, nil)) - transfer, err = models.GetPendingRepositoryTransfer(db.DefaultContext, repo) - assert.Nil(t, err) + transfer, err = repo_model.GetPendingRepositoryTransfer(db.DefaultContext, repo) + assert.NoError(t, err) assert.NoError(t, transfer.LoadAttributes(db.DefaultContext)) assert.Equal(t, "user2", transfer.Recipient.Name) org6 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Only transfer can be started at any given time - err = models.CreatePendingRepositoryTransfer(db.DefaultContext, doer, org6, repo.ID, nil) + err = repo_model.CreatePendingRepositoryTransfer(db.DefaultContext, doer, org6, repo.ID, nil) assert.Error(t, err) - assert.True(t, models.IsErrRepoTransferInProgress(err)) + assert.True(t, repo_model.IsErrRepoTransferInProgress(err)) // Unknown user - err = models.CreatePendingRepositoryTransfer(db.DefaultContext, doer, &user_model.User{ID: 1000, LowerName: "user1000"}, repo.ID, nil) + err = repo_model.CreatePendingRepositoryTransfer(db.DefaultContext, doer, &user_model.User{ID: 1000, LowerName: "user1000"}, repo.ID, nil) assert.Error(t, err) // Cancel transfer diff --git a/services/user/block.go b/services/user/block.go index 0b3b618aae6..c24ce5273ca 100644 --- a/services/user/block.go +++ b/services/user/block.go @@ -6,7 +6,6 @@ package user import ( "context" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" org_model "code.gitea.io/gitea/models/organization" @@ -194,7 +193,7 @@ func unwatchRepos(ctx context.Context, watcher, repoOwner *user_model.User) erro } func cancelRepositoryTransfers(ctx context.Context, sender, recipient *user_model.User) error { - transfers, err := models.GetPendingRepositoryTransfers(ctx, &models.PendingRepositoryTransferOptions{ + transfers, err := repo_model.GetPendingRepositoryTransfers(ctx, &repo_model.PendingRepositoryTransferOptions{ SenderID: sender.ID, RecipientID: recipient.ID, }) diff --git a/services/user/update.go b/services/user/update.go index cbaf90053a2..4a39f4f7836 100644 --- a/services/user/update.go +++ b/services/user/update.go @@ -7,7 +7,6 @@ import ( "context" "fmt" - "code.gitea.io/gitea/models" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" password_module "code.gitea.io/gitea/modules/auth/password" @@ -113,7 +112,7 @@ func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) er } if opts.IsAdmin.Has() { if !opts.IsAdmin.Value() && user_model.IsLastAdminUser(ctx, u) { - return models.ErrDeleteLastAdminUser{UID: u.ID} + return user_model.ErrDeleteLastAdminUser{UID: u.ID} } u.IsAdmin = opts.IsAdmin.Value() diff --git a/services/user/user.go b/services/user/user.go index 7bde642412b..1aeebff142d 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" @@ -127,7 +126,7 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error { } if u.IsActive && user_model.IsLastAdminUser(ctx, u) { - return models.ErrDeleteLastAdminUser{UID: u.ID} + return user_model.ErrDeleteLastAdminUser{UID: u.ID} } if purge { @@ -225,7 +224,7 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error { if err != nil { return fmt.Errorf("GetRepositoryCount: %w", err) } else if count > 0 { - return models.ErrUserOwnRepos{UID: u.ID} + return repo_model.ErrUserOwnRepos{UID: u.ID} } // Check membership of organization. @@ -233,14 +232,14 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error { if err != nil { return fmt.Errorf("GetOrganizationCount: %w", err) } else if count > 0 { - return models.ErrUserHasOrgs{UID: u.ID} + return organization.ErrUserHasOrgs{UID: u.ID} } // Check ownership of packages. if ownsPackages, err := packages_model.HasOwnerPackages(ctx, u.ID); err != nil { return fmt.Errorf("HasOwnerPackages: %w", err) } else if ownsPackages { - return models.ErrUserOwnPackages{UID: u.ID} + return packages_model.ErrUserOwnPackages{UID: u.ID} } if err := deleteUser(ctx, u, purge); err != nil { @@ -288,7 +287,7 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error { for _, u := range inactiveUsers { if err = DeleteUser(ctx, u, false); err != nil { // Ignore inactive users that were ever active but then were set inactive by admin - if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) || models.IsErrUserOwnPackages(err) { + if repo_model.IsErrUserOwnRepos(err) || organization.IsErrUserHasOrgs(err) || packages_model.IsErrUserOwnPackages(err) { log.Warn("Inactive user %q has repositories, organizations or packages, skipping deletion: %v", u.Name, err) continue } diff --git a/services/user/user_test.go b/services/user/user_test.go index c668b005c57..162a735cd43 100644 --- a/services/user/user_test.go +++ b/services/user/user_test.go @@ -9,7 +9,6 @@ import ( "testing" "time" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" @@ -37,7 +36,7 @@ func TestDeleteUser(t *testing.T) { if len(ownedRepos) > 0 { err := DeleteUser(db.DefaultContext, user, false) assert.Error(t, err) - assert.True(t, models.IsErrUserOwnRepos(err)) + assert.True(t, repo_model.IsErrUserOwnRepos(err)) return } diff --git a/services/webhook/notifier.go b/services/webhook/notifier.go index cc263947e9b..a3d5cb34b19 100644 --- a/services/webhook/notifier.go +++ b/services/webhook/notifier.go @@ -410,6 +410,10 @@ func (m *webhookNotifier) CreateIssueComment(ctx context.Context, doer *user_mod var pullRequest *api.PullRequest if issue.IsPull { eventType = webhook_module.HookEventPullRequestComment + if err := issue.LoadPullRequest(ctx); err != nil { + log.Error("LoadPullRequest: %v", err) + return + } pullRequest = convert.ToAPIPullRequest(ctx, issue.PullRequest, doer) } else { eventType = webhook_module.HookEventIssueComment diff --git a/services/webhook/packagist_test.go b/services/webhook/packagist_test.go index e9b0695baae..f47807fa6e2 100644 --- a/services/webhook/packagist_test.go +++ b/services/webhook/packagist_test.go @@ -25,7 +25,7 @@ func TestPackagistPayload(t *testing.T) { pl, err := pc.Create(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) }) t.Run("Delete", func(t *testing.T) { @@ -33,7 +33,7 @@ func TestPackagistPayload(t *testing.T) { pl, err := pc.Delete(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) }) t.Run("Fork", func(t *testing.T) { @@ -41,7 +41,7 @@ func TestPackagistPayload(t *testing.T) { pl, err := pc.Fork(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) }) t.Run("Push", func(t *testing.T) { @@ -59,12 +59,12 @@ func TestPackagistPayload(t *testing.T) { p.Action = api.HookIssueOpened pl, err := pc.Issue(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) p.Action = api.HookIssueClosed pl, err = pc.Issue(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) }) t.Run("IssueComment", func(t *testing.T) { @@ -72,7 +72,7 @@ func TestPackagistPayload(t *testing.T) { pl, err := pc.IssueComment(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) }) t.Run("PullRequest", func(t *testing.T) { @@ -80,7 +80,7 @@ func TestPackagistPayload(t *testing.T) { pl, err := pc.PullRequest(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) }) t.Run("PullRequestComment", func(t *testing.T) { @@ -88,7 +88,7 @@ func TestPackagistPayload(t *testing.T) { pl, err := pc.IssueComment(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) }) t.Run("Review", func(t *testing.T) { @@ -97,7 +97,7 @@ func TestPackagistPayload(t *testing.T) { pl, err := pc.Review(p, webhook_module.HookEventPullRequestReviewApproved) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) }) t.Run("Repository", func(t *testing.T) { @@ -105,7 +105,7 @@ func TestPackagistPayload(t *testing.T) { pl, err := pc.Repository(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) }) t.Run("Package", func(t *testing.T) { @@ -113,7 +113,7 @@ func TestPackagistPayload(t *testing.T) { pl, err := pc.Package(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) }) t.Run("Wiki", func(t *testing.T) { @@ -122,17 +122,17 @@ func TestPackagistPayload(t *testing.T) { p.Action = api.HookWikiCreated pl, err := pc.Wiki(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) p.Action = api.HookWikiEdited pl, err = pc.Wiki(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) p.Action = api.HookWikiDeleted pl, err = pc.Wiki(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) }) t.Run("Release", func(t *testing.T) { @@ -140,7 +140,7 @@ func TestPackagistPayload(t *testing.T) { pl, err := pc.Release(p) require.NoError(t, err) - require.Equal(t, pl, PackagistPayload{}) + require.Equal(t, PackagistPayload{}, pl) }) } diff --git a/services/webhook/webhook_test.go b/services/webhook/webhook_test.go index 5f5c1462323..63cbce17712 100644 --- a/services/webhook/webhook_test.go +++ b/services/webhook/webhook_test.go @@ -21,11 +21,11 @@ func TestWebhook_GetSlackHook(t *testing.T) { Meta: `{"channel": "foo", "username": "username", "color": "blue"}`, } slackHook := GetSlackHook(w) - assert.Equal(t, *slackHook, SlackMeta{ + assert.Equal(t, SlackMeta{ Channel: "foo", Username: "username", Color: "blue", - }) + }, *slackHook) } func TestPrepareWebhooks(t *testing.T) { diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index 951ee590d1c..ed7a8d6f243 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -11,9 +11,9 @@ -