diff --git a/.eslintrc.cjs b/.eslintrc.cjs
new file mode 100644
index 0000000000..f52da3fa5d
--- /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': [2], // it is still needed by eslint & IDE to prompt undefined names in real time
+ '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 0dd9a6687d..0000000000
--- 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/Dockerfile b/Dockerfile
index 2a6b1dd6b7..b95ba83289 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Build stage
-FROM docker.io/library/golang:1.23-alpine3.20 AS build-env
+FROM docker.io/library/golang:1.23-alpine3.21 AS build-env
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct}
@@ -41,7 +41,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
-FROM docker.io/library/alpine:3.20
+FROM docker.io/library/alpine:3.21
LABEL maintainer="maintainers@gitea.io"
EXPOSE 22 3000
@@ -78,7 +78,7 @@ ENV GITEA_CUSTOM=/data/gitea
VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/entrypoint"]
-CMD ["/bin/s6-svscan", "/etc/s6"]
+CMD ["/usr/bin/s6-svscan", "/etc/s6"]
COPY --from=build-env /tmp/local /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
diff --git a/Dockerfile.rootless b/Dockerfile.rootless
index 26f02205a7..be6f125104 100644
--- a/Dockerfile.rootless
+++ b/Dockerfile.rootless
@@ -1,5 +1,5 @@
# Build stage
-FROM docker.io/library/golang:1.23-alpine3.20 AS build-env
+FROM docker.io/library/golang:1.23-alpine3.21 AS build-env
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct}
@@ -39,7 +39,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
/go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
-FROM docker.io/library/alpine:3.20
+FROM docker.io/library/alpine:3.21
LABEL maintainer="maintainers@gitea.io"
EXPOSE 2222 3000
diff --git a/Makefile b/Makefile
index 4889958c3b..32ea823e1e 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.62.2
-GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
-MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1
+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
diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 6377ebf9d2..d819b55d28 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -1339,6 +1339,9 @@ LEVEL = Info
;; Number of repos that are displayed on one page
;REPO_PAGING_NUM = 15
+;; Number of orgs that are displayed on profile page
+;ORG_PAGING_NUM = 15
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[ui.meta]
diff --git a/go.mod b/go.mod
index 671151d4b6..9b99face84 100644
--- a/go.mod
+++ b/go.mod
@@ -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 afa3abece8..93b2f9fa99 100644
--- a/go.sum
+++ b/go.sum
@@ -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.go b/models/actions/run.go
index f40bc1eb3d..a224a910ab 100644
--- a/models/actions/run.go
+++ b/models/actions/run.go
@@ -275,7 +275,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
return err
}
run.Index = index
- run.Title, _ = util.SplitStringAtByteN(run.Title, 255)
+ run.Title = util.EllipsisDisplayString(run.Title, 255)
if err := db.Insert(ctx, run); err != nil {
return err
@@ -308,7 +308,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
} else {
hasWaiting = true
}
- job.Name, _ = util.SplitStringAtByteN(job.Name, 255)
+ job.Name = util.EllipsisDisplayString(job.Name, 255)
runJobs = append(runJobs, &ActionRunJob{
RunID: run.ID,
RepoID: run.RepoID,
@@ -402,7 +402,7 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
if len(cols) > 0 {
sess.Cols(cols...)
}
- run.Title, _ = util.SplitStringAtByteN(run.Title, 255)
+ run.Title = util.EllipsisDisplayString(run.Title, 255)
affected, err := sess.Update(run)
if err != nil {
return err
diff --git a/models/actions/run_job_status_test.go b/models/actions/run_job_status_test.go
index 04fd9ceba7..523d38327e 100644
--- a/models/actions/run_job_status_test.go
+++ b/models/actions/run_job_status_test.go
@@ -69,7 +69,7 @@ func TestAggregateJobStatus(t *testing.T) {
{[]Status{StatusFailure, StatusBlocked}, StatusFailure},
// skipped with other status
- // TODO: need to clarify whether a PR with "skipped" job status is considered as "mergeable" or not.
+ // "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},
diff --git a/models/actions/runner.go b/models/actions/runner.go
index b35a76680c..0d5464a5be 100644
--- a/models/actions/runner.go
+++ b/models/actions/runner.go
@@ -252,7 +252,7 @@ func GetRunnerByID(ctx context.Context, id int64) (*ActionRunner, error) {
// UpdateRunner updates runner's information.
func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
e := db.GetEngine(ctx)
- r.Name, _ = util.SplitStringAtByteN(r.Name, 255)
+ r.Name = util.EllipsisDisplayString(r.Name, 255)
var err error
if len(cols) == 0 {
_, err = e.ID(r.ID).AllCols().Update(r)
@@ -279,7 +279,7 @@ func CreateRunner(ctx context.Context, t *ActionRunner) error {
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
t.OwnerID = 0
}
- t.Name, _ = util.SplitStringAtByteN(t.Name, 255)
+ t.Name = util.EllipsisDisplayString(t.Name, 255)
return db.Insert(ctx, t)
}
diff --git a/models/actions/runner_token.go b/models/actions/runner_token.go
index fd6ba7ecad..bbd2af73b6 100644
--- a/models/actions/runner_token.go
+++ b/models/actions/runner_token.go
@@ -51,7 +51,7 @@ func GetRunnerToken(ctx context.Context, token string) (*ActionRunnerToken, erro
if err != nil {
return nil, err
} else if !has {
- return nil, fmt.Errorf("runner token %q: %w", token, util.ErrNotExist)
+ return nil, fmt.Errorf(`runner token "%s...": %w`, util.TruncateRunes(token, 3), util.ErrNotExist)
}
return &runnerToken, nil
}
@@ -68,19 +68,15 @@ func UpdateRunnerToken(ctx context.Context, r *ActionRunnerToken, cols ...string
return err
}
-// NewRunnerToken creates a new active runner token and invalidate all old tokens
+// NewRunnerTokenWithValue creates a new active runner token and invalidate all old tokens
// ownerID will be ignored and treated as 0 if repoID is non-zero.
-func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
+func NewRunnerTokenWithValue(ctx context.Context, ownerID, repoID int64, token string) (*ActionRunnerToken, error) {
if ownerID != 0 && repoID != 0 {
// It's trying to create a runner token that belongs to a repository, but OwnerID has been set accidentally.
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
ownerID = 0
}
- token, err := util.CryptoRandomString(40)
- if err != nil {
- return nil, err
- }
runnerToken := &ActionRunnerToken{
OwnerID: ownerID,
RepoID: repoID,
@@ -95,11 +91,19 @@ func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerTo
return err
}
- _, err = db.GetEngine(ctx).Insert(runnerToken)
+ _, err := db.GetEngine(ctx).Insert(runnerToken)
return err
})
}
+func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
+ token, err := util.CryptoRandomString(40)
+ if err != nil {
+ return nil, err
+ }
+ return NewRunnerTokenWithValue(ctx, ownerID, repoID, token)
+}
+
// GetLatestRunnerToken returns the latest runner token
func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
if ownerID != 0 && repoID != 0 {
diff --git a/models/actions/schedule.go b/models/actions/schedule.go
index 961ffd0851..e2cc32eedc 100644
--- a/models/actions/schedule.go
+++ b/models/actions/schedule.go
@@ -68,7 +68,7 @@ func CreateScheduleTask(ctx context.Context, rows []*ActionSchedule) error {
// Loop through each schedule row
for _, row := range rows {
- row.Title, _ = util.SplitStringAtByteN(row.Title, 255)
+ row.Title = util.EllipsisDisplayString(row.Title, 255)
// Create new schedule row
if err = db.Insert(ctx, row); err != nil {
return err
diff --git a/models/actions/task.go b/models/actions/task.go
index af74faf937..9f13ff94c9 100644
--- a/models/actions/task.go
+++ b/models/actions/task.go
@@ -298,7 +298,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
if len(workflowJob.Steps) > 0 {
steps := make([]*ActionTaskStep, len(workflowJob.Steps))
for i, v := range workflowJob.Steps {
- name, _ := util.SplitStringAtByteN(v.String(), 255)
+ name := util.EllipsisDisplayString(v.String(), 255)
steps[i] = &ActionTaskStep{
Name: name,
TaskID: task.ID,
diff --git a/models/activities/action.go b/models/activities/action.go
index 65d95fbe66..8304210188 100644
--- a/models/activities/action.go
+++ b/models/activities/action.go
@@ -20,12 +20,12 @@ import (
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/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/util"
"xorm.io/builder"
"xorm.io/xorm/schemas"
@@ -226,7 +226,7 @@ func (a *Action) GetActUserName(ctx context.Context) string {
// ShortActUserName gets the action's user name trimmed to max 20
// chars.
func (a *Action) ShortActUserName(ctx context.Context) string {
- return base.EllipsisString(a.GetActUserName(ctx), 20)
+ return util.EllipsisDisplayString(a.GetActUserName(ctx), 20)
}
// GetActDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank.
@@ -260,7 +260,7 @@ func (a *Action) GetRepoUserName(ctx context.Context) string {
// ShortRepoUserName returns the name of the action repository owner
// trimmed to max 20 chars.
func (a *Action) ShortRepoUserName(ctx context.Context) string {
- return base.EllipsisString(a.GetRepoUserName(ctx), 20)
+ return util.EllipsisDisplayString(a.GetRepoUserName(ctx), 20)
}
// GetRepoName returns the name of the action repository.
@@ -275,7 +275,7 @@ func (a *Action) GetRepoName(ctx context.Context) string {
// ShortRepoName returns the name of the action repository
// trimmed to max 33 chars.
func (a *Action) ShortRepoName(ctx context.Context) string {
- return base.EllipsisString(a.GetRepoName(ctx), 33)
+ return util.EllipsisDisplayString(a.GetRepoName(ctx), 33)
}
// GetRepoPath returns the virtual path to the action repository.
@@ -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/repo_activity.go b/models/activities/repo_activity.go
index 3ffad035b7..3ccdbd47d3 100644
--- a/models/activities/repo_activity.go
+++ b/models/activities/repo_activity.go
@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
+ "xorm.io/builder"
"xorm.io/xorm"
)
@@ -337,8 +338,10 @@ func newlyCreatedIssues(ctx context.Context, repoID int64, fromTime time.Time) *
func activeIssues(ctx context.Context, repoID int64, fromTime time.Time) *xorm.Session {
sess := db.GetEngine(ctx).Where("issue.repo_id = ?", repoID).
And("issue.is_pull = ?", false).
- And("issue.created_unix >= ?", fromTime.Unix()).
- Or("issue.closed_unix >= ?", fromTime.Unix())
+ And(builder.Or(
+ builder.Gte{"issue.created_unix": fromTime.Unix()},
+ builder.Gte{"issue.closed_unix": fromTime.Unix()},
+ ))
return sess
}
diff --git a/models/asymkey/error.go b/models/asymkey/error.go
index 03bc82302f..2e65d76612 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/source_test.go b/models/auth/source_test.go
index 36e76d5e28..84aede0a6b 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 a7db9f5442..79ade87380 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 171e26b933..51627712b1 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 8c124471ab..80b0f7b04b 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 b17188945a..91015f7038 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 0000000000..63f2d4e093
--- /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 0000000000..b4c543c3dd
--- /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 0000000000..da85018957
--- /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/name.go b/models/db/name.go
index 51be33a8bc..55c9dffb6a 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 f49ad935de..9adc5113ac 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 75c53245de..0000000000
--- 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/label.yml b/models/fixtures/label.yml
index 2242b90dcd..acfac74968 100644
--- a/models/fixtures/label.yml
+++ b/models/fixtures/label.yml
@@ -96,3 +96,14 @@
num_issues: 0
num_closed_issues: 0
archived_unix: 0
+
+-
+ id: 10
+ repo_id: 3
+ org_id: 0
+ name: repo3label1
+ color: '#112233'
+ exclusive: false
+ num_issues: 0
+ num_closed_issues: 0
+ archived_unix: 0
diff --git a/models/issues/dependency_test.go b/models/issues/dependency_test.go
index 6eed483cc9..67418039de 100644
--- a/models/issues/dependency_test.go
+++ b/models/issues/dependency_test.go
@@ -49,9 +49,13 @@ func TestCreateIssueDependency(t *testing.T) {
assert.False(t, left)
// Close #2 and check again
- _, err = issues_model.ChangeIssueStatus(db.DefaultContext, issue2, user1, true)
+ _, err = issues_model.CloseIssue(db.DefaultContext, issue2, user1)
assert.NoError(t, err)
+ issue2Closed, err := issues_model.GetIssueByID(db.DefaultContext, 2)
+ assert.NoError(t, err)
+ assert.True(t, issue2Closed.IsClosed)
+
left, err = issues_model.IssueNoDependenciesLeft(db.DefaultContext, issue1)
assert.NoError(t, err)
assert.True(t, left)
@@ -59,4 +63,11 @@ func TestCreateIssueDependency(t *testing.T) {
// Test removing the dependency
err = issues_model.RemoveIssueDependency(db.DefaultContext, user1, issue1, issue2, issues_model.DependencyTypeBlockedBy)
assert.NoError(t, err)
+
+ _, err = issues_model.ReopenIssue(db.DefaultContext, issue2, user1)
+ assert.NoError(t, err)
+
+ issue2Reopened, err := issues_model.GetIssueByID(db.DefaultContext, 2)
+ assert.NoError(t, err)
+ assert.False(t, issue2Reopened.IsClosed)
}
diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go
index 5b929c9115..2a6f3603bc 100644
--- a/models/issues/issue_update.go
+++ b/models/issues/issue_update.go
@@ -119,8 +119,8 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
})
}
-// ChangeIssueStatus changes issue status to open or closed.
-func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed bool) (*Comment, error) {
+// CloseIssue changes issue status to closed.
+func CloseIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Comment, error) {
if err := issue.LoadRepo(ctx); err != nil {
return nil, err
}
@@ -128,7 +128,45 @@ func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User,
return nil, err
}
- return changeIssueStatus(ctx, issue, doer, isClosed, false)
+ ctx, committer, err := db.TxContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ defer committer.Close()
+
+ comment, err := changeIssueStatus(ctx, issue, doer, true, false)
+ if err != nil {
+ return nil, err
+ }
+ if err := committer.Commit(); err != nil {
+ return nil, err
+ }
+ return comment, nil
+}
+
+// ReopenIssue changes issue status to open.
+func ReopenIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Comment, error) {
+ if err := issue.LoadRepo(ctx); err != nil {
+ return nil, err
+ }
+ if err := issue.LoadPoster(ctx); err != nil {
+ return nil, err
+ }
+
+ ctx, committer, err := db.TxContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ defer committer.Close()
+
+ comment, err := changeIssueStatus(ctx, issue, doer, false, false)
+ if err != nil {
+ return nil, err
+ }
+ if err := committer.Commit(); err != nil {
+ return nil, err
+ }
+ return comment, nil
}
// ChangeIssueTitle changes the title of this issue, as the given user.
@@ -139,7 +177,7 @@ func ChangeIssueTitle(ctx context.Context, issue *Issue, doer *user_model.User,
}
defer committer.Close()
- issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255)
+ issue.Title = util.EllipsisDisplayString(issue.Title, 255)
if err = UpdateIssueCols(ctx, issue, "name"); err != nil {
return fmt.Errorf("updateIssueCols: %w", err)
}
@@ -402,7 +440,7 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, la
}
issue.Index = idx
- issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255)
+ issue.Title = util.EllipsisDisplayString(issue.Title, 255)
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
Repo: repo,
diff --git a/models/issues/issue_xref_test.go b/models/issues/issue_xref_test.go
index f1b1bb2a6b..7f257330b7 100644
--- a/models/issues/issue_xref_test.go
+++ b/models/issues/issue_xref_test.go
@@ -98,7 +98,7 @@ func TestXRef_ResolveCrossReferences(t *testing.T) {
i1 := testCreateIssue(t, 1, 2, "title1", "content1", false)
i2 := testCreateIssue(t, 1, 2, "title2", "content2", false)
i3 := testCreateIssue(t, 1, 2, "title3", "content3", false)
- _, err := issues_model.ChangeIssueStatus(db.DefaultContext, i3, d, true)
+ _, err := issues_model.CloseIssue(db.DefaultContext, i3, d)
assert.NoError(t, err)
pr := testCreatePR(t, 1, 2, "titlepr", fmt.Sprintf("closes #%d", i1.Index))
diff --git a/models/issues/label.go b/models/issues/label.go
index d80578193e..b9d24bbe99 100644
--- a/models/issues/label.go
+++ b/models/issues/label.go
@@ -349,6 +349,17 @@ func GetLabelIDsInRepoByNames(ctx context.Context, repoID int64, labelNames []st
Find(&labelIDs)
}
+// GetLabelIDsInOrgByNames returns a list of labelIDs by names in a given org.
+func GetLabelIDsInOrgByNames(ctx context.Context, orgID int64, labelNames []string) ([]int64, error) {
+ labelIDs := make([]int64, 0, len(labelNames))
+ return labelIDs, db.GetEngine(ctx).Table("label").
+ Where("org_id = ?", orgID).
+ In("name", labelNames).
+ Asc("name").
+ Cols("id").
+ Find(&labelIDs)
+}
+
// BuildLabelNamesIssueIDsCondition returns a builder where get issue ids match label names
func BuildLabelNamesIssueIDsCondition(labelNames []string) *builder.Builder {
return builder.Select("issue_label.issue_id").
diff --git a/models/issues/pull.go b/models/issues/pull.go
index 853e2a69e6..efb24e8984 100644
--- a/models/issues/pull.go
+++ b/models/issues/pull.go
@@ -572,7 +572,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Iss
}
issue.Index = idx
- issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255)
+ issue.Title = util.EllipsisDisplayString(issue.Title, 255)
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
Repo: repo,
diff --git a/models/migrations/base/tests.go b/models/migrations/base/tests.go
index c2134f702a..2eb85cd8a7 100644
--- a/models/migrations/base/tests.go
+++ b/models/migrations/base/tests.go
@@ -13,9 +13,9 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/testlogger"
"github.com/stretchr/testify/require"
@@ -92,10 +92,7 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu
func MainTest(m *testing.M) {
testlogger.Init()
- giteaRoot := base.SetupGiteaRoot()
- if giteaRoot == "" {
- testlogger.Fatalf("Environment variable $GITEA_ROOT not set\n")
- }
+ giteaRoot := test.SetupGiteaRoot()
giteaBinary := "gitea"
if runtime.GOOS == "windows" {
giteaBinary += ".exe"
diff --git a/models/organization/org.go b/models/organization/org.go
index 725a99356e..3e55a36758 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_repo.go b/models/organization/org_repo.go
deleted file mode 100644
index f7e59928f4..0000000000
--- 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 5e99e88689..2c5b4090df 100644
--- a/models/organization/org_test.go
+++ b/models/organization/org_test.go
@@ -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 1d3b2fab44..08d936d922 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 fb7f0c0493..96666da39a 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 4ceb405e31..6f2a922e95 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 c90dfdeda0..53edd203a8 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 8c34e7a612..deaabbfa2c 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 417d62d199..c12f345f0e 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/project/project.go b/models/project/project.go
index 9779908b9d..87e679e1b7 100644
--- a/models/project/project.go
+++ b/models/project/project.go
@@ -256,7 +256,7 @@ func NewProject(ctx context.Context, p *Project) error {
return util.NewInvalidArgumentErrorf("project type is not valid")
}
- p.Title, _ = util.SplitStringAtByteN(p.Title, 255)
+ p.Title = util.EllipsisDisplayString(p.Title, 255)
return db.WithTx(ctx, func(ctx context.Context) error {
if err := db.Insert(ctx, p); err != nil {
@@ -311,7 +311,7 @@ func UpdateProject(ctx context.Context, p *Project) error {
p.CardType = CardTypeTextOnly
}
- p.Title, _ = util.SplitStringAtByteN(p.Title, 255)
+ p.Title = util.EllipsisDisplayString(p.Title, 255)
_, err := db.GetEngine(ctx).ID(p.ID).Cols(
"title",
"description",
diff --git a/models/repo.go b/models/repo.go
index 0dc8ee5df3..3e9c52fdd9 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 0000000000..5f0af2d475
--- /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/release.go b/models/repo/release.go
index ba7a3b3159..1c2e4a48e3 100644
--- a/models/repo/release.go
+++ b/models/repo/release.go
@@ -156,7 +156,7 @@ func IsReleaseExist(ctx context.Context, repoID int64, tagName string) (bool, er
// UpdateRelease updates all columns of a release
func UpdateRelease(ctx context.Context, rel *Release) error {
- rel.Title, _ = util.SplitStringAtByteN(rel.Title, 255)
+ rel.Title = util.EllipsisDisplayString(rel.Title, 255)
_, err := db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel)
return err
}
diff --git a/models/repo/repo.go b/models/repo/repo.go
index 8d211caf40..2d9b9de88d 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_transfer.go b/models/repo/transfer.go
similarity index 73%
rename from models/repo_transfer.go
rename to models/repo/transfer.go
index 37f591f65d..43e15b33bc 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/unittest/testdb.go b/models/unittest/testdb.go
index 5a1c27dbea..12f3c25676 100644
--- a/models/unittest/testdb.go
+++ b/models/unittest/testdb.go
@@ -14,13 +14,13 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/auth/password/hash"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/setting/config"
"code.gitea.io/gitea/modules/storage"
+ "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
@@ -206,7 +206,7 @@ func CreateTestEngine(opts FixturesOptions) error {
x, err := xorm.NewEngine("sqlite3", "file::memory:?cache=shared&_txlock=immediate")
if err != nil {
if strings.Contains(err.Error(), "unknown driver") {
- return fmt.Errorf(`sqlite3 requires: import _ "github.com/mattn/go-sqlite3" or -tags sqlite,sqlite_unlock_notify%s%w`, "\n", err)
+ return fmt.Errorf(`sqlite3 requires: -tags sqlite,sqlite_unlock_notify%s%w`, "\n", err)
}
return err
}
@@ -235,5 +235,5 @@ func PrepareTestEnv(t testing.TB) {
assert.NoError(t, PrepareTestDatabase())
metaPath := filepath.Join(giteaRoot, "tests", "gitea-repositories-meta")
assert.NoError(t, SyncDirs(metaPath, setting.RepoRootPath))
- base.SetupGiteaRoot() // Makes sure GITEA_ROOT is set
+ test.SetupGiteaRoot() // Makes sure GITEA_ROOT is set
}
diff --git a/models/user/email_address.go b/models/user/email_address.go
index 5c04909ed7..74ba5f617a 100644
--- a/models/user/email_address.go
+++ b/models/user/email_address.go
@@ -357,8 +357,8 @@ func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddres
if user := GetVerifyUser(ctx, code); user != nil {
// time limit code
prefix := code[:base.TimeLimitCodeLength]
- data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands)
-
+ opts := &TimeLimitCodeOptions{Purpose: TimeLimitCodeActivateEmail, NewEmail: email}
+ data := makeTimeLimitCodeHashData(opts, user)
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
emailAddress := &EmailAddress{UID: user.ID, Email: email}
if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
@@ -486,10 +486,10 @@ func ActivateUserEmail(ctx context.Context, userID int64, email string, activate
// Activate/deactivate a user's primary email address and account
if addr.IsPrimary {
- user, exist, err := db.Get[User](ctx, builder.Eq{"id": userID, "email": email})
+ user, exist, err := db.Get[User](ctx, builder.Eq{"id": userID})
if err != nil {
return err
- } else if !exist {
+ } else if !exist || !strings.EqualFold(user.Email, email) {
return fmt.Errorf("no user with ID: %d and Email: %s", userID, email)
}
diff --git a/models/user/user.go b/models/user/user.go
index bd92693b6e..19879fbcc7 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -181,7 +181,8 @@ func (u *User) BeforeUpdate() {
u.MaxRepoCreation = -1
}
- // Organization does not need email
+ // FIXME: this email doesn't need to be in lowercase, because the emails are mainly managed by the email table with lower_email field
+ // This trick could be removed in new releases to display the user inputed email as-is.
u.Email = strings.ToLower(u.Email)
if !u.IsOrganization() {
if len(u.AvatarEmail) == 0 {
@@ -190,9 +191,9 @@ func (u *User) BeforeUpdate() {
}
u.LowerName = strings.ToLower(u.Name)
- u.Location = base.TruncateString(u.Location, 255)
- u.Website = base.TruncateString(u.Website, 255)
- u.Description = base.TruncateString(u.Description, 255)
+ u.Location = util.TruncateRunes(u.Location, 255)
+ u.Website = util.TruncateRunes(u.Website, 255)
+ u.Description = util.TruncateRunes(u.Description, 255)
}
// AfterLoad is invoked from XORM after filling all the fields of this object.
@@ -310,17 +311,6 @@ func (u *User) OrganisationLink() string {
return setting.AppSubURL + "/org/" + url.PathEscape(u.Name)
}
-// GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
-func (u *User) GenerateEmailActivateCode(email string) string {
- code := base.CreateTimeLimitCode(
- fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands),
- setting.Service.ActiveCodeLives, time.Now(), nil)
-
- // Add tail hex username
- code += hex.EncodeToString([]byte(u.LowerName))
- return code
-}
-
// GetUserFollowers returns range of user's followers.
func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
sess := db.GetEngine(ctx).
@@ -501,9 +491,9 @@ func (u *User) GitName() string {
// ShortName ellipses username to length
func (u *User) ShortName(length int) string {
if setting.UI.DefaultShowFullName && len(u.FullName) > 0 {
- return base.EllipsisString(u.FullName, length)
+ return util.EllipsisDisplayString(u.FullName, length)
}
- return base.EllipsisString(u.Name, length)
+ return util.EllipsisDisplayString(u.Name, length)
}
// IsMailable checks if a user is eligible
@@ -788,6 +778,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 {
@@ -848,12 +853,38 @@ func GetVerifyUser(ctx context.Context, code string) (user *User) {
return nil
}
-// VerifyUserActiveCode verifies active code when active account
-func VerifyUserActiveCode(ctx context.Context, code string) (user *User) {
+type TimeLimitCodePurpose string
+
+const (
+ TimeLimitCodeActivateAccount TimeLimitCodePurpose = "activate_account"
+ TimeLimitCodeActivateEmail TimeLimitCodePurpose = "activate_email"
+ TimeLimitCodeResetPassword TimeLimitCodePurpose = "reset_password"
+)
+
+type TimeLimitCodeOptions struct {
+ Purpose TimeLimitCodePurpose
+ NewEmail string
+}
+
+func makeTimeLimitCodeHashData(opts *TimeLimitCodeOptions, u *User) string {
+ return fmt.Sprintf("%s|%d|%s|%s|%s|%s", opts.Purpose, u.ID, strings.ToLower(util.IfZero(opts.NewEmail, u.Email)), u.LowerName, u.Passwd, u.Rands)
+}
+
+// GenerateUserTimeLimitCode generates a time-limit code based on user information and given e-mail.
+// TODO: need to use cache or db to store it to make sure a code can only be consumed once
+func GenerateUserTimeLimitCode(opts *TimeLimitCodeOptions, u *User) string {
+ data := makeTimeLimitCodeHashData(opts, u)
+ code := base.CreateTimeLimitCode(data, setting.Service.ActiveCodeLives, time.Now(), nil)
+ code += hex.EncodeToString([]byte(u.LowerName)) // Add tail hex username
+ return code
+}
+
+// VerifyUserTimeLimitCode verifies the time-limit code
+func VerifyUserTimeLimitCode(ctx context.Context, opts *TimeLimitCodeOptions, code string) (user *User) {
if user = GetVerifyUser(ctx, code); user != nil {
// time limit code
prefix := code[:base.TimeLimitCodeLength]
- data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands)
+ data := makeTimeLimitCodeHashData(opts, user)
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
return user
}
diff --git a/modules/base/base.go b/modules/base/base.go
deleted file mode 100644
index dddce202da..0000000000
--- a/modules/base/base.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2014 The Gogs Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package base
-
-type (
- // TplName template relative path type
- TplName string
-)
diff --git a/modules/base/tool.go b/modules/base/tool.go
index 928c80700b..1d16186bc5 100644
--- a/modules/base/tool.go
+++ b/modules/base/tool.go
@@ -13,17 +13,14 @@ import (
"errors"
"fmt"
"hash"
- "os"
- "path/filepath"
- "runtime"
"strconv"
"strings"
"time"
- "unicode/utf8"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
"github.com/dustin/go-humanize"
)
@@ -38,7 +35,7 @@ func EncodeSha256(str string) string {
// ShortSha is basically just truncating.
// It is DEPRECATED and will be removed in the future.
func ShortSha(sha1 string) string {
- return TruncateString(sha1, 10)
+ return util.TruncateRunes(sha1, 10)
}
// BasicAuthDecode decode basic auth string
@@ -119,27 +116,6 @@ func FileSize(s int64) string {
return humanize.IBytes(uint64(s))
}
-// EllipsisString returns a truncated short string,
-// it appends '...' in the end of the length of string is too large.
-func EllipsisString(str string, length int) string {
- if length <= 3 {
- return "..."
- }
- if utf8.RuneCountInString(str) <= length {
- return str
- }
- return string([]rune(str)[:length-3]) + "..."
-}
-
-// TruncateString returns a truncated string with given limit,
-// it returns input string if length is not reached limit.
-func TruncateString(str string, limit int) string {
- if utf8.RuneCountInString(str) < limit {
- return str
- }
- return string([]rune(str)[:limit])
-}
-
// StringsToInt64s converts a slice of string to a slice of int64.
func StringsToInt64s(strs []string) ([]int64, error) {
if strs == nil {
@@ -189,49 +165,3 @@ func EntryIcon(entry *git.TreeEntry) string {
return "file"
}
-
-// SetupGiteaRoot Sets GITEA_ROOT if it is not already set and returns the value
-func SetupGiteaRoot() string {
- giteaRoot := os.Getenv("GITEA_ROOT")
- if giteaRoot == "" {
- _, filename, _, _ := runtime.Caller(0)
- giteaRoot = strings.TrimSuffix(filename, "modules/base/tool.go")
- wd, err := os.Getwd()
- if err != nil {
- rel, err := filepath.Rel(giteaRoot, wd)
- if err != nil && strings.HasPrefix(filepath.ToSlash(rel), "../") {
- giteaRoot = wd
- }
- }
- if _, err := os.Stat(filepath.Join(giteaRoot, "gitea")); os.IsNotExist(err) {
- giteaRoot = ""
- } else if err := os.Setenv("GITEA_ROOT", giteaRoot); err != nil {
- giteaRoot = ""
- }
- }
- return giteaRoot
-}
-
-// FormatNumberSI format a number
-func FormatNumberSI(data any) string {
- var num int64
- if num1, ok := data.(int64); ok {
- num = num1
- } else if num1, ok := data.(int); ok {
- num = int64(num1)
- } else {
- return ""
- }
-
- if num < 1000 {
- return fmt.Sprintf("%d", num)
- } else if num < 1000000 {
- num2 := float32(num) / float32(1000.0)
- return fmt.Sprintf("%.1fk", num2)
- } else if num < 1000000000 {
- num2 := float32(num) / float32(1000000.0)
- return fmt.Sprintf("%.1fM", num2)
- }
- num2 := float32(num) / float32(1000000000.0)
- return fmt.Sprintf("%.1fG", num2)
-}
diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go
index f63679048e..c821a55c19 100644
--- a/modules/base/tool_test.go
+++ b/modules/base/tool_test.go
@@ -113,36 +113,6 @@ func TestFileSize(t *testing.T) {
assert.Equal(t, "2.0 EiB", FileSize(size))
}
-func TestEllipsisString(t *testing.T) {
- assert.Equal(t, "...", EllipsisString("foobar", 0))
- assert.Equal(t, "...", EllipsisString("foobar", 1))
- assert.Equal(t, "...", EllipsisString("foobar", 2))
- assert.Equal(t, "...", EllipsisString("foobar", 3))
- assert.Equal(t, "f...", EllipsisString("foobar", 4))
- assert.Equal(t, "fo...", EllipsisString("foobar", 5))
- assert.Equal(t, "foobar", EllipsisString("foobar", 6))
- assert.Equal(t, "foobar", EllipsisString("foobar", 10))
- assert.Equal(t, "测...", EllipsisString("测试文本一二三四", 4))
- assert.Equal(t, "测试...", EllipsisString("测试文本一二三四", 5))
- assert.Equal(t, "测试文...", EllipsisString("测试文本一二三四", 6))
- assert.Equal(t, "测试文本一二三四", EllipsisString("测试文本一二三四", 10))
-}
-
-func TestTruncateString(t *testing.T) {
- assert.Equal(t, "", TruncateString("foobar", 0))
- assert.Equal(t, "f", TruncateString("foobar", 1))
- assert.Equal(t, "fo", TruncateString("foobar", 2))
- assert.Equal(t, "foo", TruncateString("foobar", 3))
- assert.Equal(t, "foob", TruncateString("foobar", 4))
- assert.Equal(t, "fooba", TruncateString("foobar", 5))
- assert.Equal(t, "foobar", TruncateString("foobar", 6))
- assert.Equal(t, "foobar", TruncateString("foobar", 7))
- assert.Equal(t, "测试文本", TruncateString("测试文本一二三四", 4))
- assert.Equal(t, "测试文本一", TruncateString("测试文本一二三四", 5))
- assert.Equal(t, "测试文本一二", TruncateString("测试文本一二三四", 6))
- assert.Equal(t, "测试文本一二三", TruncateString("测试文本一二三四", 7))
-}
-
func TestStringsToInt64s(t *testing.T) {
testSuccess := func(input []string, expected []int64) {
result, err := StringsToInt64s(input)
@@ -169,18 +139,3 @@ func TestInt64sToStrings(t *testing.T) {
}
// TODO: Test EntryIcon
-
-func TestSetupGiteaRoot(t *testing.T) {
- t.Setenv("GITEA_ROOT", "test")
- assert.Equal(t, "test", SetupGiteaRoot())
- t.Setenv("GITEA_ROOT", "")
- assert.NotEqual(t, "test", SetupGiteaRoot())
-}
-
-func TestFormatNumberSI(t *testing.T) {
- assert.Equal(t, "125", FormatNumberSI(int(125)))
- assert.Equal(t, "1.3k", FormatNumberSI(int64(1317)))
- assert.Equal(t, "21.3M", FormatNumberSI(21317675))
- assert.Equal(t, "45.7G", FormatNumberSI(45721317675))
- assert.Equal(t, "", FormatNumberSI("test"))
-}
diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go
index 7dfda72155..532dbad989 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 a9b2706f21..cad720edf4 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 22cb275ab2..b231c3beea 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 9a6228c9ad..0823afd7f7 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/remote.go b/modules/git/remote.go
index 7b10e6b663..de8d74eded 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 fc6e6e7acc..0993a4ac37 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_commit.go b/modules/git/repo_commit.go
index 9ffadb833d..647894bb21 100644
--- a/modules/git/repo_commit.go
+++ b/modules/git/repo_commit.go
@@ -216,8 +216,6 @@ type CommitsByFileAndRangeOptions struct {
// CommitsByFileAndRange return the commits according revision file and the page
func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) ([]*Commit, error) {
- skip := (opts.Page - 1) * setting.Git.CommitsRangeSize
-
stdoutReader, stdoutWriter := io.Pipe()
defer func() {
_ = stdoutReader.Close()
@@ -226,8 +224,8 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
go func() {
stderr := strings.Builder{}
gitCmd := NewCommand(repo.Ctx, "rev-list").
- AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize*opts.Page).
- AddOptionFormat("--skip=%d", skip)
+ AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
+ AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize)
gitCmd.AddDynamicArguments(opts.Revision)
if opts.Not != "" {
diff --git a/modules/git/repo_commit_test.go b/modules/git/repo_commit_test.go
index 4c26fa2a48..e9f469accd 100644
--- a/modules/git/repo_commit_test.go
+++ b/modules/git/repo_commit_test.go
@@ -8,7 +8,11 @@ import (
"path/filepath"
"testing"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
+
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestRepository_GetCommitBranches(t *testing.T) {
@@ -126,3 +130,21 @@ func TestGetRefCommitID(t *testing.T) {
}
}
}
+
+func TestCommitsByFileAndRange(t *testing.T) {
+ defer test.MockVariableValue(&setting.Git.CommitsRangeSize, 2)()
+
+ bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
+ bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
+ require.NoError(t, err)
+ defer bareRepo1.Close()
+
+ // "foo" has 3 commits in "master" branch
+ commits, err := bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 1})
+ require.NoError(t, err)
+ assert.Len(t, commits, 2)
+
+ commits, err = bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 2})
+ require.NoError(t, err)
+ assert.Len(t, commits, 1)
+}
diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go
index 16fcdcf4c8..877a7ff3b8 100644
--- a/modules/git/repo_compare.go
+++ b/modules/git/repo_compare.go
@@ -233,72 +233,34 @@ func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int,
return numFiles, totalAdditions, totalDeletions, err
}
-// GetDiffOrPatch generates either diff or formatted patch data between given revisions
-func (repo *Repository) GetDiffOrPatch(base, head string, w io.Writer, patch, binary bool) error {
- if patch {
- return repo.GetPatch(base, head, w)
- }
- if binary {
- return repo.GetDiffBinary(base, head, w)
- }
- return repo.GetDiff(base, head, w)
-}
-
// GetDiff generates and returns patch data between given revisions, optimized for human readability
-func (repo *Repository) GetDiff(base, head string, w io.Writer) error {
+func (repo *Repository) GetDiff(compareArg string, w io.Writer) error {
stderr := new(bytes.Buffer)
- err := NewCommand(repo.Ctx, "diff", "-p").AddDynamicArguments(base + "..." + head).
+ return NewCommand(repo.Ctx, "diff", "-p").AddDynamicArguments(compareArg).
Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
})
- if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
- return NewCommand(repo.Ctx, "diff", "-p").AddDynamicArguments(base, head).
- Run(&RunOpts{
- Dir: repo.Path,
- Stdout: w,
- })
- }
- return err
}
// GetDiffBinary generates and returns patch data between given revisions, including binary diffs.
-func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error {
- stderr := new(bytes.Buffer)
- err := NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram").AddDynamicArguments(base + "..." + head).
- Run(&RunOpts{
- Dir: repo.Path,
- Stdout: w,
- Stderr: stderr,
- })
- if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
- return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram").AddDynamicArguments(base, head).
- Run(&RunOpts{
- Dir: repo.Path,
- Stdout: w,
- })
- }
- return err
+func (repo *Repository) GetDiffBinary(compareArg string, w io.Writer) error {
+ return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram").AddDynamicArguments(compareArg).Run(&RunOpts{
+ Dir: repo.Path,
+ Stdout: w,
+ })
}
// GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply`
-func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
+func (repo *Repository) GetPatch(compareArg string, w io.Writer) error {
stderr := new(bytes.Buffer)
- err := NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout").AddDynamicArguments(base + "..." + head).
+ return NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout").AddDynamicArguments(compareArg).
Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
})
- if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
- return NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout").AddDynamicArguments(base, head).
- Run(&RunOpts{
- Dir: repo.Path,
- Stdout: w,
- })
- }
- return err
}
// GetFilesChangedBetween returns a list of all files that have been changed between the given commits
@@ -329,21 +291,6 @@ func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, err
return split, err
}
-// GetDiffFromMergeBase generates and return patch data from merge base to head
-func (repo *Repository) GetDiffFromMergeBase(base, head string, w io.Writer) error {
- stderr := new(bytes.Buffer)
- err := NewCommand(repo.Ctx, "diff", "-p", "--binary").AddDynamicArguments(base + "..." + head).
- Run(&RunOpts{
- Dir: repo.Path,
- Stdout: w,
- Stderr: stderr,
- })
- if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
- return repo.GetDiffBinary(base, head, w)
- }
- return err
-}
-
// ReadPatchCommit will check if a diff patch exists and return stats
func (repo *Repository) ReadPatchCommit(prID int64) (commitSHA string, err error) {
// Migrated repositories download patches to "pulls" location
diff --git a/modules/git/repo_compare_test.go b/modules/git/repo_compare_test.go
index 454ed6b9f8..25ee4c5198 100644
--- a/modules/git/repo_compare_test.go
+++ b/modules/git/repo_compare_test.go
@@ -28,7 +28,7 @@ func TestGetFormatPatch(t *testing.T) {
defer repo.Close()
rd := &bytes.Buffer{}
- err = repo.GetPatch("8d92fc95^", "8d92fc95", rd)
+ err = repo.GetPatch("8d92fc95^...8d92fc95", rd)
if err != nil {
assert.NoError(t, err)
return
diff --git a/modules/gitrepo/gitrepo.go b/modules/gitrepo/gitrepo.go
index 14d809aedb..831b9d7bb7 100644
--- a/modules/gitrepo/gitrepo.go
+++ b/modules/gitrepo/gitrepo.go
@@ -10,6 +10,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
@@ -38,63 +39,32 @@ func OpenWikiRepository(ctx context.Context, repo Repository) (*git.Repository,
// contextKey is a value for use with context.WithValue.
type contextKey struct {
- name string
-}
-
-// RepositoryContextKey is a context key. It is used with context.Value() to get the current Repository for the context
-var RepositoryContextKey = &contextKey{"repository"}
-
-// RepositoryFromContext attempts to get the repository from the context
-func repositoryFromContext(ctx context.Context, repo Repository) *git.Repository {
- value := ctx.Value(RepositoryContextKey)
- if value == nil {
- return nil
- }
-
- if gitRepo, ok := value.(*git.Repository); ok && gitRepo != nil {
- if gitRepo.Path == repoPath(repo) {
- return gitRepo
- }
- }
-
- return nil
+ repoPath string
}
// RepositoryFromContextOrOpen attempts to get the repository from the context or just opens it
func RepositoryFromContextOrOpen(ctx context.Context, repo Repository) (*git.Repository, io.Closer, error) {
- gitRepo := repositoryFromContext(ctx, repo)
- if gitRepo != nil {
- return gitRepo, util.NopCloser{}, nil
+ ds := reqctx.GetRequestDataStore(ctx)
+ if ds != nil {
+ gitRepo, err := RepositoryFromRequestContextOrOpen(ctx, ds, repo)
+ return gitRepo, util.NopCloser{}, err
}
-
gitRepo, err := OpenRepository(ctx, repo)
return gitRepo, gitRepo, err
}
-// repositoryFromContextPath attempts to get the repository from the context
-func repositoryFromContextPath(ctx context.Context, path string) *git.Repository {
- value := ctx.Value(RepositoryContextKey)
- if value == nil {
- return nil
+// RepositoryFromRequestContextOrOpen opens the repository at the given relative path in the provided request context
+// The repo will be automatically closed when the request context is done
+func RepositoryFromRequestContextOrOpen(ctx context.Context, ds reqctx.RequestDataStore, repo Repository) (*git.Repository, error) {
+ ck := contextKey{repoPath: repoPath(repo)}
+ if gitRepo, ok := ctx.Value(ck).(*git.Repository); ok {
+ return gitRepo, nil
}
-
- if repo, ok := value.(*git.Repository); ok && repo != nil {
- if repo.Path == path {
- return repo
- }
+ gitRepo, err := git.OpenRepository(ctx, ck.repoPath)
+ if err != nil {
+ return nil, err
}
-
- return nil
-}
-
-// RepositoryFromContextOrOpenPath attempts to get the repository from the context or just opens it
-// Deprecated: Use RepositoryFromContextOrOpen instead
-func RepositoryFromContextOrOpenPath(ctx context.Context, path string) (*git.Repository, io.Closer, error) {
- gitRepo := repositoryFromContextPath(ctx, path)
- if gitRepo != nil {
- return gitRepo, util.NopCloser{}, nil
- }
-
- gitRepo, err := git.OpenRepository(ctx, path)
- return gitRepo, gitRepo, err
+ ds.AddCloser(gitRepo)
+ ds.SetContextValue(ck, gitRepo)
+ return gitRepo, nil
}
diff --git a/modules/gitrepo/walk_gogit.go b/modules/gitrepo/walk_gogit.go
index 6370faf08e..709897ba0c 100644
--- a/modules/gitrepo/walk_gogit.go
+++ b/modules/gitrepo/walk_gogit.go
@@ -14,15 +14,11 @@ import (
// WalkReferences walks all the references from the repository
// refname is empty, ObjectTag or ObjectBranch. All other values should be treated as equivalent to empty.
func WalkReferences(ctx context.Context, repo Repository, walkfn func(sha1, refname string) error) (int, error) {
- gitRepo := repositoryFromContext(ctx, repo)
- if gitRepo == nil {
- var err error
- gitRepo, err = OpenRepository(ctx, repo)
- if err != nil {
- return 0, err
- }
- defer gitRepo.Close()
+ gitRepo, closer, err := RepositoryFromContextOrOpen(ctx, repo)
+ if err != nil {
+ return 0, err
}
+ defer closer.Close()
i := 0
iter, err := gitRepo.GoGitRepo().References()
diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go
index 991b2f2b7a..433e8c4c27 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 {
diff --git a/modules/graceful/manager_common.go b/modules/graceful/manager_common.go
index f6dbcc748d..7cfbdfbeb0 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 0000000000..974b2c9757
--- /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/issue/template/unmarshal.go b/modules/issue/template/unmarshal.go
index 0fc13d7ddf..1d8e9dd02d 100644
--- a/modules/issue/template/unmarshal.go
+++ b/modules/issue/template/unmarshal.go
@@ -109,7 +109,7 @@ func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
it.Content = string(content)
it.Name = path.Base(it.FileName) // paths in Git are always '/' separated - do not use filepath!
- it.About, _ = util.SplitStringAtByteN(it.Content, 80)
+ it.About = util.EllipsisDisplayString(it.Content, 80)
} else {
it.Content = templateBody
if it.About == "" {
diff --git a/modules/log/event_format.go b/modules/log/event_format.go
index 0b8d1cec79..8fda0a4980 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
@@ -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 7c299a607d..6fd0f36d48 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 f025159d53..8064c91745 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 56d7af42da..0000000000
--- 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 34e99653d6..0000000000
--- a/modules/log/groutinelabel_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package log
-
-import (
- "context"
- "runtime/pprof"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func Test_getGoroutineLabels(t *testing.T) {
- pprof.Do(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 d38c6516ed..76dd5f43fb 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/markup/common/linkify.go b/modules/markup/common/linkify.go
index be6ab22b55..52888958fa 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 25628db39e..bb12febf27 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_commit.go b/modules/markup/html_commit.go
index 358e7b06ba..aa1b7d034a 100644
--- a/modules/markup/html_commit.go
+++ b/modules/markup/html_commit.go
@@ -8,6 +8,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/util"
"golang.org/x/net/html"
@@ -194,3 +195,21 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
node = node.NextSibling.NextSibling
}
}
+
+func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
+ next := node.NextSibling
+
+ for node != nil && node != next {
+ found, ref := references.FindRenderizableCommitCrossReference(node.Data)
+ if !found {
+ return
+ }
+
+ reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha)
+ linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, "commit", ref.CommitSha), LinkTypeApp)
+ link := createLink(ctx, linkHref, reftext, "commit")
+
+ replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link)
+ node = node.NextSibling.NextSibling
+ }
+}
diff --git a/modules/markup/html_issue.go b/modules/markup/html_issue.go
index e64ec76c3d..7a6f33011a 100644
--- a/modules/markup/html_issue.go
+++ b/modules/markup/html_issue.go
@@ -4,9 +4,9 @@
package markup
import (
+ "strconv"
"strings"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/references"
@@ -16,8 +16,16 @@ import (
"code.gitea.io/gitea/modules/util"
"golang.org/x/net/html"
+ "golang.org/x/net/html/atom"
)
+type RenderIssueIconTitleOptions struct {
+ OwnerName string
+ RepoName string
+ LinkHref string
+ IssueIndex int64
+}
+
func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.RenderOptions.Metas == nil {
return
@@ -66,6 +74,27 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
}
}
+func createIssueLinkContentWithSummary(ctx *RenderContext, linkHref string, ref *references.RenderizableReference) *html.Node {
+ if DefaultRenderHelperFuncs.RenderRepoIssueIconTitle == nil {
+ return nil
+ }
+ issueIndex, _ := strconv.ParseInt(ref.Issue, 10, 64)
+ h, err := DefaultRenderHelperFuncs.RenderRepoIssueIconTitle(ctx, RenderIssueIconTitleOptions{
+ OwnerName: ref.Owner,
+ RepoName: ref.Name,
+ LinkHref: linkHref,
+ IssueIndex: issueIndex,
+ })
+ if err != nil {
+ log.Error("RenderRepoIssueIconTitle failed: %v", err)
+ return nil
+ }
+ if h == "" {
+ return nil
+ }
+ return &html.Node{Type: html.RawNode, Data: string(ctx.RenderInternal.ProtectSafeAttrs(h))}
+}
+
func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.RenderOptions.Metas == nil {
return
@@ -76,32 +105,28 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
// old logic: crossLinkOnly := ctx.RenderOptions.Metas["mode"] == "document" && !ctx.IsWiki
crossLinkOnly := ctx.RenderOptions.Metas["markupAllowShortIssuePattern"] != "true"
- var (
- found bool
- ref *references.RenderizableReference
- )
+ var ref *references.RenderizableReference
next := node.NextSibling
-
for node != nil && node != next {
_, hasExtTrackFormat := ctx.RenderOptions.Metas["format"]
// Repos with external issue trackers might still need to reference local PRs
// We need to concern with the first one that shows up in the text, whichever it is
isNumericStyle := ctx.RenderOptions.Metas["style"] == "" || ctx.RenderOptions.Metas["style"] == IssueNameStyleNumeric
- foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle, crossLinkOnly)
+ refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle, crossLinkOnly)
switch ctx.RenderOptions.Metas["style"] {
case "", IssueNameStyleNumeric:
- found, ref = foundNumeric, refNumeric
+ ref = refNumeric
case IssueNameStyleAlphanumeric:
- found, ref = references.FindRenderizableReferenceAlphanumeric(node.Data)
+ ref = references.FindRenderizableReferenceAlphanumeric(node.Data)
case IssueNameStyleRegexp:
pattern, err := regexplru.GetCompiled(ctx.RenderOptions.Metas["regexp"])
if err != nil {
return
}
- found, ref = references.FindRenderizableReferenceRegexp(node.Data, pattern)
+ ref = references.FindRenderizableReferenceRegexp(node.Data, pattern)
}
// Repos with external issue trackers might still need to reference local PRs
@@ -109,17 +134,17 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
if hasExtTrackFormat && !isNumericStyle && refNumeric != nil {
// If numeric (PR) was found, and it was BEFORE the non-numeric pattern, use that
// Allow a free-pass when non-numeric pattern wasn't found.
- if found && (ref == nil || refNumeric.RefLocation.Start < ref.RefLocation.Start) {
- found = foundNumeric
+ if ref == nil || refNumeric.RefLocation.Start < ref.RefLocation.Start {
ref = refNumeric
}
}
- if !found {
+
+ if ref == nil {
return
}
var link *html.Node
- reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
+ refText := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
if hasExtTrackFormat && !ref.IsPull {
ctx.RenderOptions.Metas["index"] = ref.Issue
@@ -129,18 +154,23 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
log.Error("unable to expand template vars for ref %s, err: %v", ref.Issue, err)
}
- link = createLink(ctx, res, reftext, "ref-issue ref-external-issue")
+ link = createLink(ctx, res, refText, "ref-issue ref-external-issue")
} else {
// Path determines the type of link that will be rendered. It's unknown at this point whether
// the linked item is actually a PR or an issue. Luckily it's of no real consequence because
// Gitea will redirect on click as appropriate.
+ issueOwner := util.Iif(ref.Owner == "", ctx.RenderOptions.Metas["user"], ref.Owner)
+ issueRepo := util.Iif(ref.Owner == "", ctx.RenderOptions.Metas["repo"], ref.Name)
issuePath := util.Iif(ref.IsPull, "pulls", "issues")
- if ref.Owner == "" {
- linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], issuePath, ref.Issue), LinkTypeApp)
- link = createLink(ctx, linkHref, reftext, "ref-issue")
- } else {
- linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, issuePath, ref.Issue), LinkTypeApp)
- link = createLink(ctx, linkHref, reftext, "ref-issue")
+ linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(issueOwner, issueRepo, issuePath, ref.Issue), LinkTypeApp)
+
+ // at the moment, only render the issue index in a full line (or simple line) as icon+title
+ // otherwise it would be too noisy for "take #1 as an example" in a sentence
+ if node.Parent.DataAtom == atom.Li && ref.RefLocation.Start < 20 && ref.RefLocation.End == len(node.Data) {
+ link = createIssueLinkContentWithSummary(ctx, linkHref, ref)
+ }
+ if link == nil {
+ link = createLink(ctx, linkHref, refText, "ref-issue")
}
}
@@ -168,21 +198,3 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
node = node.NextSibling.NextSibling.NextSibling.NextSibling
}
}
-
-func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
- next := node.NextSibling
-
- for node != nil && node != next {
- found, ref := references.FindRenderizableCommitCrossReference(node.Data)
- if !found {
- return
- }
-
- reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha)
- linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, "commit", ref.CommitSha), LinkTypeApp)
- link := createLink(ctx, linkHref, reftext, "commit")
-
- replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link)
- node = node.NextSibling.NextSibling
- }
-}
diff --git a/modules/markup/html_issue_test.go b/modules/markup/html_issue_test.go
new file mode 100644
index 0000000000..8d189fbdf6
--- /dev/null
+++ b/modules/markup/html_issue_test.go
@@ -0,0 +1,72 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package markup_test
+
+import (
+ "context"
+ "html/template"
+ "strings"
+ "testing"
+
+ "code.gitea.io/gitea/modules/htmlutil"
+ "code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/markup/markdown"
+ testModule "code.gitea.io/gitea/modules/test"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestRender_IssueList(t *testing.T) {
+ defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
+ markup.Init(&markup.RenderHelperFuncs{
+ RenderRepoIssueIconTitle: func(ctx context.Context, opts markup.RenderIssueIconTitleOptions) (template.HTML, error) {
+ return htmlutil.HTMLFormat("
issue #%d
", opts.IssueIndex), nil
+ },
+ })
+
+ test := func(input, expected string) {
+ rctx := markup.NewTestRenderContext(markup.TestAppURL, map[string]string{
+ "user": "test-user", "repo": "test-repo",
+ "markupAllowShortIssuePattern": "true",
+ })
+ out, err := markdown.RenderString(rctx, input)
+ require.NoError(t, err)
+ assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out)))
+ }
+
+ t.Run("NormalIssueRef", func(t *testing.T) {
+ test(
+ "#12345",
+ `#12345
`,
+ )
+ })
+
+ t.Run("ListIssueRef", func(t *testing.T) {
+ test(
+ "* #12345",
+ ``,
+ )
+ })
+
+ t.Run("ListIssueRefNormal", func(t *testing.T) {
+ test(
+ "* foo #12345 bar",
+ ``,
+ )
+ })
+
+ t.Run("ListTodoIssueRef", func(t *testing.T) {
+ test(
+ "* [ ] #12345",
+ ``,
+ )
+ })
+}
diff --git a/modules/markup/html_link.go b/modules/markup/html_link.go
index 5fd38b63cd..0e7a988d36 100644
--- a/modules/markup/html_link.go
+++ b/modules/markup/html_link.go
@@ -9,6 +9,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/markup/common"
+ "code.gitea.io/gitea/modules/util"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
@@ -171,6 +172,10 @@ func linkProcessor(ctx *RenderContext, node *html.Node) {
}
uri := node.Data[m[0]:m[1]]
+ remaining := node.Data[m[1]:]
+ if util.IsLikelyEllipsisLeftPart(remaining) {
+ return
+ }
replaceContent(node, m[0], m[1], createLink(ctx, uri, uri, "" /*link*/))
node = node.NextSibling.NextSibling
}
diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go
index 54bd91f3b3..6d8f24184b 100644
--- a/modules/markup/html_test.go
+++ b/modules/markup/html_test.go
@@ -206,6 +206,16 @@ func TestRender_links(t *testing.T) {
test(
"ftps://gitea.com",
`ftps://gitea.com
`)
+
+ t.Run("LinkEllipsis", func(t *testing.T) {
+ input := util.EllipsisDisplayString("http://10.1.2.3", 12)
+ assert.Equal(t, "http://10…", input)
+ test(input, "http://10…
")
+
+ input = util.EllipsisDisplayString("http://10.1.2.3", 13)
+ assert.Equal(t, "http://10.…", input)
+ test(input, "http://10.…
")
+ })
}
func TestRender_email(t *testing.T) {
diff --git a/modules/markup/internal/renderinternal.go b/modules/markup/internal/renderinternal.go
index 4775fecfc7..4d58f160a9 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 620a39ebfd..69c2a96ff1 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 a14c0cad59..b5fffccdb9 100644
--- a/modules/markup/markdown/markdown.go
+++ b/modules/markup/markdown/markdown.go
@@ -129,7 +129,8 @@ func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender {
Enabled: setting.Markdown.EnableMath,
ParseDollarInline: true,
ParseDollarBlock: true,
- ParseSquareBlock: true, // TODO: this is a bad syntax, it should be deprecated in the future (by some config options)
+ 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,
),
diff --git a/modules/markup/render_helper.go b/modules/markup/render_helper.go
index 82796ef274..8ff0e7d6fb 100644
--- a/modules/markup/render_helper.go
+++ b/modules/markup/render_helper.go
@@ -38,6 +38,7 @@ type RenderHelper interface {
type RenderHelperFuncs struct {
IsUsernameMentionable func(ctx context.Context, username string) bool
RenderRepoFileCodePreview func(ctx context.Context, options RenderCodePreviewOptions) (template.HTML, error)
+ RenderRepoIssueIconTitle func(ctx context.Context, options RenderIssueIconTitleOptions) (template.HTML, error)
}
var DefaultRenderHelperFuncs *RenderHelperFuncs
diff --git a/modules/packages/arch/metadata.go b/modules/packages/arch/metadata.go
index e1e79c60e0..3d7bdf3997 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 f611ef5e84..5bdf63a3ee 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/maven/metadata.go b/modules/packages/maven/metadata.go
index 42aa250718..a61a62c086 100644
--- a/modules/packages/maven/metadata.go
+++ b/modules/packages/maven/metadata.go
@@ -7,6 +7,7 @@ import (
"encoding/xml"
"io"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
"golang.org/x/net/html/charset"
@@ -31,18 +32,27 @@ type Dependency struct {
}
type pomStruct struct {
- XMLName xml.Name `xml:"project"`
- GroupID string `xml:"groupId"`
- ArtifactID string `xml:"artifactId"`
- Version string `xml:"version"`
- Name string `xml:"name"`
- Description string `xml:"description"`
- URL string `xml:"url"`
- Licenses []struct {
+ XMLName xml.Name `xml:"project"`
+
+ Parent struct {
+ GroupID string `xml:"groupId"`
+ ArtifactID string `xml:"artifactId"`
+ Version string `xml:"version"`
+ } `xml:"parent"`
+
+ GroupID string `xml:"groupId"`
+ ArtifactID string `xml:"artifactId"`
+ Version string `xml:"version"`
+ Name string `xml:"name"`
+ Description string `xml:"description"`
+ URL string `xml:"url"`
+
+ Licenses []struct {
Name string `xml:"name"`
URL string `xml:"url"`
Distribution string `xml:"distribution"`
} `xml:"licenses>license"`
+
Dependencies []struct {
GroupID string `xml:"groupId"`
ArtifactID string `xml:"artifactId"`
@@ -81,8 +91,16 @@ func ParsePackageMetaData(r io.Reader) (*Metadata, error) {
})
}
+ pomGroupID := pom.GroupID
+ if pomGroupID == "" {
+ // the current module could inherit parent: https://maven.apache.org/pom.html#Inheritance
+ pomGroupID = pom.Parent.GroupID
+ }
+ if pomGroupID == "" {
+ return nil, util.ErrInvalidArgument
+ }
return &Metadata{
- GroupID: pom.GroupID,
+ GroupID: pomGroupID,
ArtifactID: pom.ArtifactID,
Name: pom.Name,
Description: pom.Description,
diff --git a/modules/packages/maven/metadata_test.go b/modules/packages/maven/metadata_test.go
index e675467730..2cff290808 100644
--- a/modules/packages/maven/metadata_test.go
+++ b/modules/packages/maven/metadata_test.go
@@ -7,7 +7,10 @@ import (
"strings"
"testing"
+ "code.gitea.io/gitea/modules/util"
+
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"golang.org/x/text/encoding/charmap"
)
@@ -86,4 +89,35 @@ func TestParsePackageMetaData(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, m)
})
+
+ t.Run("ParentInherit", func(t *testing.T) {
+ pom := `
+
+ 4.0.0
+
+ com.mycompany.app
+ my-app
+ 1.0-SNAPSHOT
+
+ submodule1
+
+`
+ m, err := ParsePackageMetaData(strings.NewReader(pom))
+ require.NoError(t, err)
+ require.NotNil(t, m)
+
+ assert.Equal(t, "com.mycompany.app", m.GroupID)
+ assert.Equal(t, "submodule1", m.ArtifactID)
+ })
+
+ t.Run("ParentInherit", func(t *testing.T) {
+ pom := `
+
+ 4.0.0
+
+
+`
+ _, err := ParsePackageMetaData(strings.NewReader(pom))
+ require.ErrorIs(t, err, util.ErrInvalidArgument)
+ })
}
diff --git a/modules/process/context.go b/modules/process/context.go
index 26a80ebd62..1854988bce 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 bdc4931810..661511ce8d 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 e260893113..d83060f6ee 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/workerqueue.go b/modules/queue/workerqueue.go
index 672e9a4114..0f5b105551 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/references/references.go b/modules/references/references.go
index 6e549cb875..a5b102b7f2 100644
--- a/modules/references/references.go
+++ b/modules/references/references.go
@@ -32,7 +32,7 @@ var (
// issueNumericPattern matches string that references to a numeric issue, e.g. #1287
issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\")([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$)`)
// issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234
- issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\"|\')([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$)|\"|\')`)
+ issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\"|\')([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$)|\"|\'|,)`)
// crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository
// e.g. org/repo#12345
crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
@@ -330,22 +330,22 @@ func FindAllIssueReferences(content string) []IssueReference {
}
// FindRenderizableReferenceNumeric returns the first unvalidated reference found in a string.
-func FindRenderizableReferenceNumeric(content string, prOnly, crossLinkOnly bool) (bool, *RenderizableReference) {
+func FindRenderizableReferenceNumeric(content string, prOnly, crossLinkOnly bool) *RenderizableReference {
var match []int
if !crossLinkOnly {
match = issueNumericPattern.FindStringSubmatchIndex(content)
}
if match == nil {
if match = crossReferenceIssueNumericPattern.FindStringSubmatchIndex(content); match == nil {
- return false, nil
+ return nil
}
}
r := getCrossReference(util.UnsafeStringToBytes(content), match[2], match[3], false, prOnly)
if r == nil {
- return false, nil
+ return nil
}
- return true, &RenderizableReference{
+ return &RenderizableReference{
Issue: r.issue,
Owner: r.owner,
Name: r.name,
@@ -372,15 +372,14 @@ func FindRenderizableCommitCrossReference(content string) (bool, *RenderizableRe
}
// FindRenderizableReferenceRegexp returns the first regexp unvalidated references found in a string.
-func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) (bool, *RenderizableReference) {
+func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) *RenderizableReference {
match := pattern.FindStringSubmatchIndex(content)
if len(match) < 4 {
- return false, nil
+ return nil
}
action, location := findActionKeywords([]byte(content), match[2])
-
- return true, &RenderizableReference{
+ return &RenderizableReference{
Issue: content[match[2]:match[3]],
RefLocation: &RefSpan{Start: match[0], End: match[1]},
Action: action,
@@ -390,15 +389,14 @@ func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) (bo
}
// FindRenderizableReferenceAlphanumeric returns the first alphanumeric unvalidated references found in a string.
-func FindRenderizableReferenceAlphanumeric(content string) (bool, *RenderizableReference) {
+func FindRenderizableReferenceAlphanumeric(content string) *RenderizableReference {
match := issueAlphanumericPattern.FindStringSubmatchIndex(content)
if match == nil {
- return false, nil
+ return nil
}
action, location := findActionKeywords([]byte(content), match[2])
-
- return true, &RenderizableReference{
+ return &RenderizableReference{
Issue: content[match[2]:match[3]],
RefLocation: &RefSpan{Start: match[2], End: match[3]},
Action: action,
diff --git a/modules/references/references_test.go b/modules/references/references_test.go
index e224c919e9..1b6a968d6a 100644
--- a/modules/references/references_test.go
+++ b/modules/references/references_test.go
@@ -249,11 +249,10 @@ func TestFindAllIssueReferences(t *testing.T) {
}
for _, fixture := range alnumFixtures {
- found, ref := FindRenderizableReferenceAlphanumeric(fixture.input)
+ ref := FindRenderizableReferenceAlphanumeric(fixture.input)
if fixture.issue == "" {
- assert.False(t, found, "Failed to parse: {%s}", fixture.input)
+ assert.Nil(t, ref, "Failed to parse: {%s}", fixture.input)
} else {
- assert.True(t, found, "Failed to parse: {%s}", fixture.input)
assert.Equal(t, fixture.issue, ref.Issue, "Failed to parse: {%s}", fixture.input)
assert.Equal(t, fixture.refLocation, ref.RefLocation, "Failed to parse: {%s}", fixture.input)
assert.Equal(t, fixture.action, ref.Action, "Failed to parse: {%s}", fixture.input)
@@ -463,6 +462,7 @@ func TestRegExp_issueAlphanumericPattern(t *testing.T) {
"ABC-123:",
"\"ABC-123\"",
"'ABC-123'",
+ "ABC-123, unknown PR",
}
falseTestCases := []string{
"RC-08",
diff --git a/modules/repository/fork.go b/modules/repository/fork.go
index d530634071..84ed4b23d6 100644
--- a/modules/repository/fork.go
+++ b/modules/repository/fork.go
@@ -12,6 +12,9 @@ import (
"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
diff --git a/modules/repository/license_test.go b/modules/repository/license_test.go
index 3b0cfa1eed..d00156a496 100644
--- a/modules/repository/license_test.go
+++ b/modules/repository/license_test.go
@@ -31,12 +31,7 @@ func Test_getLicense(t *testing.T) {
Copyright (c) 2023 Gitea
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-`,
+Permission is hereby granted`,
wantErr: assert.NoError,
},
{
@@ -53,7 +48,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
if !tt.wantErr(t, err, fmt.Sprintf("GetLicense(%v, %v)", tt.args.name, tt.args.values)) {
return
}
- assert.Equalf(t, tt.want, string(got), "GetLicense(%v, %v)", tt.args.name, tt.args.values)
+ assert.Contains(t, string(got), tt.want, "GetLicense(%v, %v)", tt.args.name, tt.args.values)
})
}
}
diff --git a/modules/reqctx/datastore.go b/modules/reqctx/datastore.go
new file mode 100644
index 0000000000..66361a4587
--- /dev/null
+++ b/modules/reqctx/datastore.go
@@ -0,0 +1,123 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package reqctx
+
+import (
+ "context"
+ "io"
+ "sync"
+
+ "code.gitea.io/gitea/modules/process"
+)
+
+type ContextDataProvider interface {
+ GetData() ContextData
+}
+
+type ContextData map[string]any
+
+func (ds ContextData) GetData() ContextData {
+ return ds
+}
+
+func (ds ContextData) MergeFrom(other ContextData) ContextData {
+ for k, v := range other {
+ ds[k] = v
+ }
+ return ds
+}
+
+// RequestDataStore is a short-lived context-related object that is used to store request-specific data.
+type RequestDataStore interface {
+ GetData() ContextData
+ SetContextValue(k, v any)
+ GetContextValue(key any) any
+ AddCleanUp(f func())
+ AddCloser(c io.Closer)
+}
+
+type requestDataStoreKeyType struct{}
+
+var RequestDataStoreKey requestDataStoreKeyType
+
+type requestDataStore struct {
+ data ContextData
+
+ mu sync.RWMutex
+ values map[any]any
+ cleanUpFuncs []func()
+}
+
+func (r *requestDataStore) GetContextValue(key any) any {
+ if key == RequestDataStoreKey {
+ return r
+ }
+ r.mu.RLock()
+ defer r.mu.RUnlock()
+ return r.values[key]
+}
+
+func (r *requestDataStore) SetContextValue(k, v any) {
+ r.mu.Lock()
+ r.values[k] = v
+ r.mu.Unlock()
+}
+
+// GetData and the underlying ContextData are not thread-safe, callers should ensure thread-safety.
+func (r *requestDataStore) GetData() ContextData {
+ if r.data == nil {
+ r.data = make(ContextData)
+ }
+ return r.data
+}
+
+func (r *requestDataStore) AddCleanUp(f func()) {
+ r.mu.Lock()
+ r.cleanUpFuncs = append(r.cleanUpFuncs, f)
+ r.mu.Unlock()
+}
+
+func (r *requestDataStore) AddCloser(c io.Closer) {
+ r.AddCleanUp(func() { _ = c.Close() })
+}
+
+func (r *requestDataStore) cleanUp() {
+ for _, f := range r.cleanUpFuncs {
+ f()
+ }
+}
+
+func GetRequestDataStore(ctx context.Context) RequestDataStore {
+ if req, ok := ctx.Value(RequestDataStoreKey).(*requestDataStore); ok {
+ return req
+ }
+ return nil
+}
+
+type requestContext struct {
+ context.Context
+ dataStore *requestDataStore
+}
+
+func (c *requestContext) Value(key any) any {
+ if v := c.dataStore.GetContextValue(key); v != nil {
+ return v
+ }
+ return c.Context.Value(key)
+}
+
+func NewRequestContext(parentCtx context.Context, profDesc string) (_ context.Context, finished func()) {
+ ctx, _, processFinished := process.GetManager().AddTypedContext(parentCtx, profDesc, process.RequestProcessType, true)
+ reqCtx := &requestContext{Context: ctx, dataStore: &requestDataStore{values: make(map[any]any)}}
+ return reqCtx, func() {
+ reqCtx.dataStore.cleanUp()
+ processFinished()
+ }
+}
+
+// NewRequestContextForTest creates a new RequestContext for testing purposes
+// It doesn't add the context to the process manager, nor do cleanup
+func NewRequestContextForTest(parentCtx context.Context) context.Context {
+ return &requestContext{Context: parentCtx, dataStore: &requestDataStore{values: make(map[any]any)}}
+}
diff --git a/modules/setting/i18n.go b/modules/setting/i18n.go
index c3076c0ab7..6e0767320f 100644
--- a/modules/setting/i18n.go
+++ b/modules/setting/i18n.go
@@ -11,6 +11,7 @@ var defaultI18nLangNames = []string{
"zh-TW", "繁體中文(台灣)",
"de-DE", "Deutsch",
"fr-FR", "Français",
+ "ga-IE", "Gaeilge",
"nl-NL", "Nederlands",
"lv-LV", "Latviešu",
"ru-RU", "Русский",
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index c93d199b1b..20da796b58 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -235,3 +235,9 @@ func checkOverlappedPath(name, path string) {
}
configuredPaths[path] = name
}
+
+func PanicInDevOrTesting(msg string, a ...any) {
+ if !IsProd || IsInTesting {
+ panic(fmt.Sprintf(msg, a...))
+ }
+}
diff --git a/modules/setting/ui.go b/modules/setting/ui.go
index db0fe9ef79..20fc612b43 100644
--- a/modules/setting/ui.go
+++ b/modules/setting/ui.go
@@ -63,6 +63,7 @@ var UI = struct {
} `ini:"ui.admin"`
User struct {
RepoPagingNum int
+ OrgPagingNum int
} `ini:"ui.user"`
Meta struct {
Author string
@@ -127,8 +128,10 @@ var UI = struct {
},
User: struct {
RepoPagingNum int
+ OrgPagingNum int
}{
RepoPagingNum: 15,
+ OrgPagingNum: 15,
},
Meta: struct {
Author string
diff --git a/modules/storage/azureblob.go b/modules/storage/azureblob.go
index 96c2525b29..837afd0ba6 100644
--- a/modules/storage/azureblob.go
+++ b/modules/storage/azureblob.go
@@ -70,7 +70,7 @@ func (a *azureBlobObject) Seek(offset int64, whence int) (int64, error) {
case io.SeekCurrent:
offset += a.offset
case io.SeekEnd:
- offset = a.Size - offset
+ offset = a.Size + offset
default:
return 0, errors.New("Seek: invalid whence")
}
diff --git a/modules/storage/azureblob_test.go b/modules/storage/azureblob_test.go
index 604870cb98..6905db5008 100644
--- a/modules/storage/azureblob_test.go
+++ b/modules/storage/azureblob_test.go
@@ -4,6 +4,8 @@
package storage
import (
+ "bytes"
+ "io"
"os"
"testing"
@@ -54,3 +56,46 @@ func TestAzureBlobStoragePath(t *testing.T) {
assert.Equal(t, "base/a", m.buildAzureBlobPath("/a"))
assert.Equal(t, "base/a/b", m.buildAzureBlobPath("/a/b/"))
}
+
+func Test_azureBlobObject(t *testing.T) {
+ if os.Getenv("CI") == "" {
+ t.Skip("azureBlobStorage not present outside of CI")
+ return
+ }
+
+ s, err := NewStorage(setting.AzureBlobStorageType, &setting.Storage{
+ AzureBlobConfig: setting.AzureBlobStorageConfig{
+ // https://learn.microsoft.com/azure/storage/common/storage-use-azurite?tabs=visual-studio-code#ip-style-url
+ Endpoint: "http://devstoreaccount1.azurite.local:10000",
+ // https://learn.microsoft.com/azure/storage/common/storage-use-azurite?tabs=visual-studio-code#well-known-storage-account-and-key
+ AccountName: "devstoreaccount1",
+ AccountKey: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==",
+ Container: "test",
+ },
+ })
+ assert.NoError(t, err)
+
+ data := "Q2xTckt6Y1hDOWh0"
+ _, err = s.Save("test.txt", bytes.NewBufferString(data), int64(len(data)))
+ assert.NoError(t, err)
+ obj, err := s.Open("test.txt")
+ assert.NoError(t, err)
+ offset, err := obj.Seek(2, io.SeekStart)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 2, offset)
+ buf1 := make([]byte, 3)
+ read, err := obj.Read(buf1)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 3, read)
+ assert.Equal(t, data[2:5], string(buf1))
+ offset, err = obj.Seek(-5, io.SeekEnd)
+ assert.NoError(t, err)
+ assert.EqualValues(t, len(data)-5, offset)
+ buf2 := make([]byte, 4)
+ read, err = obj.Read(buf2)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 4, read)
+ assert.Equal(t, data[11:15], string(buf2))
+ assert.NoError(t, obj.Close())
+ assert.NoError(t, s.Delete("test.txt"))
+}
diff --git a/modules/svg/processor.go b/modules/svg/processor.go
index 82248fb0c1..4fcb11a57d 100644
--- a/modules/svg/processor.go
+++ b/modules/svg/processor.go
@@ -10,7 +10,7 @@ import (
"sync"
)
-type normalizeVarsStruct struct {
+type globalVarsStruct struct {
reXMLDoc,
reComment,
reAttrXMLNs,
@@ -18,26 +18,23 @@ type normalizeVarsStruct struct {
reAttrClassPrefix *regexp.Regexp
}
-var (
- normalizeVars *normalizeVarsStruct
- normalizeVarsOnce sync.Once
-)
+var globalVars = sync.OnceValue(func() *globalVarsStruct {
+ return &globalVarsStruct{
+ reXMLDoc: regexp.MustCompile(`(?s)<\?xml.*?>`),
+ reComment: regexp.MustCompile(`(?s)`),
+
+ reAttrXMLNs: regexp.MustCompile(`(?s)\s+xmlns\s*=\s*"[^"]*"`),
+ reAttrSize: regexp.MustCompile(`(?s)\s+(width|height)\s*=\s*"[^"]+"`),
+ reAttrClassPrefix: regexp.MustCompile(`(?s)\s+class\s*=\s*"`),
+ }
+})
// Normalize normalizes the SVG content: set default width/height, remove unnecessary tags/attributes
// It's designed to work with valid SVG content. For invalid SVG content, the returned content is not guaranteed.
func Normalize(data []byte, size int) []byte {
- normalizeVarsOnce.Do(func() {
- normalizeVars = &normalizeVarsStruct{
- reXMLDoc: regexp.MustCompile(`(?s)<\?xml.*?>`),
- reComment: regexp.MustCompile(`(?s)`),
-
- reAttrXMLNs: regexp.MustCompile(`(?s)\s+xmlns\s*=\s*"[^"]*"`),
- reAttrSize: regexp.MustCompile(`(?s)\s+(width|height)\s*=\s*"[^"]+"`),
- reAttrClassPrefix: regexp.MustCompile(`(?s)\s+class\s*=\s*"`),
- }
- })
- data = normalizeVars.reXMLDoc.ReplaceAll(data, nil)
- data = normalizeVars.reComment.ReplaceAll(data, nil)
+ vars := globalVars()
+ data = vars.reXMLDoc.ReplaceAll(data, nil)
+ data = vars.reComment.ReplaceAll(data, nil)
data = bytes.TrimSpace(data)
svgTag, svgRemaining, ok := bytes.Cut(data, []byte(">"))
@@ -45,9 +42,9 @@ func Normalize(data []byte, size int) []byte {
return data
}
normalized := bytes.Clone(svgTag)
- normalized = normalizeVars.reAttrXMLNs.ReplaceAll(normalized, nil)
- normalized = normalizeVars.reAttrSize.ReplaceAll(normalized, nil)
- normalized = normalizeVars.reAttrClassPrefix.ReplaceAll(normalized, []byte(` class="`))
+ normalized = vars.reAttrXMLNs.ReplaceAll(normalized, nil)
+ normalized = vars.reAttrSize.ReplaceAll(normalized, nil)
+ normalized = vars.reAttrClassPrefix.ReplaceAll(normalized, []byte(` class="`))
normalized = bytes.TrimSpace(normalized)
normalized = fmt.Appendf(normalized, ` width="%d" height="%d"`, size, size)
if !bytes.Contains(normalized, []byte(` class="`)) {
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index ff9673ccef..b673450f5a 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -9,7 +9,6 @@ import (
"html"
"html/template"
"net/url"
- "reflect"
"strings"
"time"
@@ -69,7 +68,7 @@ func NewFuncMap() template.FuncMap {
// -----------------------------------------------------------------
// time / number / format
"FileSize": base.FileSize,
- "CountFmt": base.FormatNumberSI,
+ "CountFmt": countFmt,
"Sec2Time": util.SecToTime,
"TimeEstimateString": timeEstimateString,
@@ -239,29 +238,8 @@ func iif(condition any, vals ...any) any {
}
func isTemplateTruthy(v any) bool {
- if v == nil {
- return false
- }
-
- rv := reflect.ValueOf(v)
- switch rv.Kind() {
- case reflect.Bool:
- return rv.Bool()
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return rv.Int() != 0
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- return rv.Uint() != 0
- case reflect.Float32, reflect.Float64:
- return rv.Float() != 0
- case reflect.Complex64, reflect.Complex128:
- return rv.Complex() != 0
- case reflect.String, reflect.Slice, reflect.Array, reflect.Map:
- return rv.Len() > 0
- case reflect.Struct:
- return true
- default:
- return !rv.IsNil()
- }
+ truth, _ := template.IsTrue(v)
+ return truth
}
// evalTokens evaluates the expression by tokens and returns the result, see the comment of eval.Expr for details.
@@ -286,14 +264,6 @@ func userThemeName(user *user_model.User) string {
return setting.UI.DefaultTheme
}
-func timeEstimateString(timeSec any) string {
- v, _ := util.ToInt64(timeSec)
- if v == 0 {
- return ""
- }
- return util.TimeEstimateString(v)
-}
-
// QueryBuild builds a query string from a list of key-value pairs.
// It omits the nil and empty strings, but it doesn't omit other zero values,
// because the zero value of number types may have a meaning.
@@ -361,7 +331,5 @@ func QueryBuild(a ...any) template.URL {
}
func panicIfDevOrTesting() {
- if !setting.IsProd || setting.IsInTesting {
- panic("legacy template functions are for backward compatibility only, do not use them in new code")
- }
+ setting.PanicInDevOrTesting("legacy template functions are for backward compatibility only, do not use them in new code")
}
diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go
index 3e17e86c66..a530d484bc 100644
--- a/modules/templates/helper_test.go
+++ b/modules/templates/helper_test.go
@@ -8,6 +8,7 @@ import (
"strings"
"testing"
+ "code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
@@ -65,31 +66,12 @@ func TestSanitizeHTML(t *testing.T) {
assert.Equal(t, template.HTML(`link xss inline
`), SanitizeHTML(`link xss inline
`))
}
-func TestTemplateTruthy(t *testing.T) {
+func TestTemplateIif(t *testing.T) {
tmpl := template.New("test")
tmpl.Funcs(template.FuncMap{"Iif": iif})
template.Must(tmpl.Parse(`{{if .Value}}true{{else}}false{{end}}:{{Iif .Value "true" "false"}}`))
- cases := []any{
- nil, false, true, "", "string", 0, 1,
- byte(0), byte(1), int64(0), int64(1), float64(0), float64(1),
- complex(0, 0), complex(1, 0),
- (chan int)(nil), make(chan int),
- (func())(nil), func() {},
- util.ToPointer(0), util.ToPointer(util.ToPointer(0)),
- util.ToPointer(1), util.ToPointer(util.ToPointer(1)),
- [0]int{},
- [1]int{0},
- []int(nil),
- []int{},
- []int{0},
- map[any]any(nil),
- map[any]any{},
- map[any]any{"k": "v"},
- (*struct{})(nil),
- struct{}{},
- util.ToPointer(struct{}{}),
- }
+ cases := []any{nil, false, true, "", "string", 0, 1}
w := &strings.Builder{}
truthyCount := 0
for i, v := range cases {
@@ -102,3 +84,37 @@ func TestTemplateTruthy(t *testing.T) {
}
assert.True(t, truthyCount != 0 && truthyCount != len(cases))
}
+
+func TestTemplateEscape(t *testing.T) {
+ execTmpl := func(code string) string {
+ tmpl := template.New("test")
+ tmpl.Funcs(template.FuncMap{"QueryBuild": QueryBuild, "HTMLFormat": htmlutil.HTMLFormat})
+ template.Must(tmpl.Parse(code))
+ w := &strings.Builder{}
+ assert.NoError(t, tmpl.Execute(w, nil))
+ return w.String()
+ }
+
+ t.Run("Golang URL Escape", func(t *testing.T) {
+ // Golang template considers "href", "*src*", "*uri*", "*url*" (and more) ... attributes as contentTypeURL and does auto-escaping
+ actual := execTmpl(``)
+ assert.Equal(t, ``, actual)
+ actual = execTmpl(``)
+ assert.Equal(t, ``, actual)
+ })
+ t.Run("Golang URL No-escape", func(t *testing.T) {
+ // non-URL content isn't auto-escaped
+ actual := execTmpl(``)
+ assert.Equal(t, ``, actual)
+ })
+ t.Run("QueryBuild", func(t *testing.T) {
+ actual := execTmpl(``)
+ assert.Equal(t, ``, actual)
+ actual = execTmpl(``)
+ assert.Equal(t, ``, actual)
+ })
+ t.Run("HTMLFormat", func(t *testing.T) {
+ actual := execTmpl("{{HTMLFormat `%s` `\"` `<>`}}")
+ assert.Equal(t, `<>`, actual)
+ })
+}
diff --git a/modules/templates/htmlrenderer.go b/modules/templates/htmlrenderer.go
index e7e805ed30..529284f7e8 100644
--- a/modules/templates/htmlrenderer.go
+++ b/modules/templates/htmlrenderer.go
@@ -29,6 +29,8 @@ import (
type TemplateExecutor scopedtmpl.TemplateExecutor
+type TplName string
+
type HTMLRender struct {
templates atomic.Pointer[scopedtmpl.ScopedTemplate]
}
@@ -40,7 +42,8 @@ var (
var ErrTemplateNotInitialized = errors.New("template system is not initialized, check your log for errors")
-func (h *HTMLRender) HTML(w io.Writer, status int, name string, data any, ctx context.Context) error { //nolint:revive
+func (h *HTMLRender) HTML(w io.Writer, status int, tplName TplName, data any, ctx context.Context) error { //nolint:revive
+ name := string(tplName)
if respWriter, ok := w.(http.ResponseWriter); ok {
if respWriter.Header().Get("Content-Type") == "" {
respWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
diff --git a/modules/templates/mailer.go b/modules/templates/mailer.go
index ace81bf4a5..310d645328 100644
--- a/modules/templates/mailer.go
+++ b/modules/templates/mailer.go
@@ -11,9 +11,9 @@ import (
"strings"
texttmpl "text/template"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
)
var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}\s*$`)
@@ -24,7 +24,7 @@ func mailSubjectTextFuncMap() texttmpl.FuncMap {
"dict": dict,
"Eval": evalTokens,
- "EllipsisString": base.EllipsisString,
+ "EllipsisString": util.EllipsisDisplayString,
"AppName": func() string {
return setting.AppName
},
diff --git a/modules/templates/util_format.go b/modules/templates/util_format.go
new file mode 100644
index 0000000000..bee6fb7b75
--- /dev/null
+++ b/modules/templates/util_format.go
@@ -0,0 +1,37 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package templates
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/modules/util"
+)
+
+func timeEstimateString(timeSec any) string {
+ v, _ := util.ToInt64(timeSec)
+ if v == 0 {
+ return ""
+ }
+ return util.TimeEstimateString(v)
+}
+
+func countFmt(data any) string {
+ // legacy code, not ideal, still used in some places
+ num, err := util.ToInt64(data)
+ if err != nil {
+ return ""
+ }
+ if num < 1000 {
+ return fmt.Sprintf("%d", num)
+ } else if num < 1_000_000 {
+ num2 := float32(num) / 1000.0
+ return fmt.Sprintf("%.1fk", num2)
+ } else if num < 1_000_000_000 {
+ num2 := float32(num) / 1_000_000.0
+ return fmt.Sprintf("%.1fM", num2)
+ }
+ num2 := float32(num) / 1_000_000_000.0
+ return fmt.Sprintf("%.1fG", num2)
+}
diff --git a/modules/templates/util_format_test.go b/modules/templates/util_format_test.go
new file mode 100644
index 0000000000..8d466faff0
--- /dev/null
+++ b/modules/templates/util_format_test.go
@@ -0,0 +1,18 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package templates
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestCountFmt(t *testing.T) {
+ assert.Equal(t, "125", countFmt(125))
+ assert.Equal(t, "1.3k", countFmt(int64(1317)))
+ assert.Equal(t, "21.3M", countFmt(21317675))
+ assert.Equal(t, "45.7G", countFmt(45721317675))
+ assert.Equal(t, "", countFmt("test"))
+}
diff --git a/modules/templates/util_string.go b/modules/templates/util_string.go
index 382e2de13f..683c77a870 100644
--- a/modules/templates/util_string.go
+++ b/modules/templates/util_string.go
@@ -8,7 +8,7 @@ import (
"html/template"
"strings"
- "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/util"
)
type StringUtils struct{}
@@ -54,7 +54,7 @@ func (su *StringUtils) Cut(s, sep string) []any {
}
func (su *StringUtils) EllipsisString(s string, maxLength int) string {
- return base.EllipsisString(s, maxLength)
+ return util.EllipsisDisplayString(s, maxLength)
}
func (su *StringUtils) ToUpper(s string) string {
diff --git a/modules/test/utils.go b/modules/test/utils.go
index 8dee92fbce..ec4c976388 100644
--- a/modules/test/utils.go
+++ b/modules/test/utils.go
@@ -4,11 +4,16 @@
package test
import (
+ "fmt"
"net/http"
"net/http/httptest"
+ "os"
+ "path/filepath"
+ "runtime"
"strings"
"code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/util"
)
// RedirectURL returns the redirect URL of a http response.
@@ -41,3 +46,19 @@ func MockVariableValue[T any](p *T, v ...T) (reset func()) {
}
return func() { *p = old }
}
+
+// SetupGiteaRoot Sets GITEA_ROOT if it is not already set and returns the value
+func SetupGiteaRoot() string {
+ giteaRoot := os.Getenv("GITEA_ROOT")
+ if giteaRoot != "" {
+ return giteaRoot
+ }
+ _, filename, _, _ := runtime.Caller(0)
+ giteaRoot = filepath.Dir(filepath.Dir(filepath.Dir(filename)))
+ fixturesDir := filepath.Join(giteaRoot, "models", "fixtures")
+ if exist, _ := util.IsDir(fixturesDir); !exist {
+ panic(fmt.Sprintf("fixtures directory not found: %s", fixturesDir))
+ }
+ _ = os.Setenv("GITEA_ROOT", giteaRoot)
+ return giteaRoot
+}
diff --git a/modules/test/utils_test.go b/modules/test/utils_test.go
new file mode 100644
index 0000000000..0469ce97f2
--- /dev/null
+++ b/modules/test/utils_test.go
@@ -0,0 +1,17 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package test
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSetupGiteaRoot(t *testing.T) {
+ t.Setenv("GITEA_ROOT", "test")
+ assert.Equal(t, "test", SetupGiteaRoot())
+ t.Setenv("GITEA_ROOT", "")
+ assert.NotEqual(t, "test", SetupGiteaRoot())
+}
diff --git a/modules/util/runtime.go b/modules/util/runtime.go
new file mode 100644
index 0000000000..91ec3c869c
--- /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 0000000000..20f9063b0b
--- /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/string.go b/modules/util/string.go
index cf50f591c6..19cf75b8b3 100644
--- a/modules/util/string.go
+++ b/modules/util/string.go
@@ -3,7 +3,10 @@
package util
-import "unsafe"
+import (
+ "strings"
+ "unsafe"
+)
func isSnakeCaseUpper(c byte) bool {
return 'A' <= c && c <= 'Z'
@@ -95,3 +98,15 @@ func UnsafeBytesToString(b []byte) string {
func UnsafeStringToBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}
+
+// SplitTrimSpace splits the string at given separator and trims leading and trailing space
+func SplitTrimSpace(input, sep string) []string {
+ input = strings.TrimSpace(input)
+ var stringList []string
+ for _, s := range strings.Split(input, sep) {
+ if s = strings.TrimSpace(s); s != "" {
+ stringList = append(stringList, s)
+ }
+ }
+ return stringList
+}
diff --git a/modules/util/string_test.go b/modules/util/string_test.go
index 0a4a8bbcfb..ff67b5c7d4 100644
--- a/modules/util/string_test.go
+++ b/modules/util/string_test.go
@@ -45,3 +45,8 @@ func TestToSnakeCase(t *testing.T) {
assert.Equal(t, expected, ToSnakeCase(input))
}
}
+
+func TestSplitTrimSpace(t *testing.T) {
+ assert.Equal(t, []string{"a", "b", "c"}, SplitTrimSpace("a\nb\nc", "\n"))
+ assert.Equal(t, []string{"a", "b"}, SplitTrimSpace("\r\na\n\r\nb\n\n", "\n"))
+}
diff --git a/modules/util/time_str.go b/modules/util/time_str.go
index b56f4740af..0fccfe82cc 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/truncate.go b/modules/util/truncate.go
index f2edbdc673..2bce248281 100644
--- a/modules/util/truncate.go
+++ b/modules/util/truncate.go
@@ -5,6 +5,7 @@ package util
import (
"strings"
+ "unicode"
"unicode/utf8"
)
@@ -14,43 +15,110 @@ const (
asciiEllipsis = "..."
)
-// SplitStringAtByteN splits a string at byte n accounting for rune boundaries. (Combining characters are not accounted for.)
-func SplitStringAtByteN(input string, n int) (left, right string) {
- if len(input) <= n {
- return input, ""
+func IsLikelyEllipsisLeftPart(s string) bool {
+ return strings.HasSuffix(s, utf8Ellipsis) || strings.HasSuffix(s, asciiEllipsis)
+}
+
+func ellipsisGuessDisplayWidth(r rune) int {
+ // To make the truncated string as long as possible,
+ // CJK/emoji chars are considered as 2-ASCII width but not 3-4 bytes width.
+ // Here we only make the best guess (better than counting them in bytes),
+ // it's impossible to 100% correctly determine the width of a rune without a real font and render.
+ //
+ // ATTENTION: the guessed width can't be zero, more details in ellipsisDisplayString's comment
+ if r <= 255 {
+ return 1
}
- if !utf8.ValidString(input) {
- if n-3 < 0 {
- return input, ""
+ switch {
+ case r == '\u3000': /* ideographic (CJK) characters, still use 2 */
+ return 2
+ case unicode.Is(unicode.M, r), /* (Mark) */
+ unicode.Is(unicode.Cf, r), /* (Other, format) */
+ unicode.Is(unicode.Cs, r), /* (Other, surrogate) */
+ unicode.Is(unicode.Z /* (Space) */, r):
+ return 1
+ default:
+ return 2
+ }
+}
+
+// EllipsisDisplayString returns a truncated short string for display purpose.
+// The length is the approximate number of ASCII-width in the string (CJK/emoji are 2-ASCII width)
+// It appends "…" or "..." at the end of truncated string.
+// It guarantees the length of the returned runes doesn't exceed the limit.
+func EllipsisDisplayString(str string, limit int) string {
+ s, _, _, _ := ellipsisDisplayString(str, limit)
+ return s
+}
+
+// EllipsisDisplayStringX works like EllipsisDisplayString while it also returns the right part
+func EllipsisDisplayStringX(str string, limit int) (left, right string) {
+ left, offset, truncated, encounterInvalid := ellipsisDisplayString(str, limit)
+ if truncated {
+ right = str[offset:]
+ r, _ := utf8.DecodeRune(UnsafeStringToBytes(right))
+ encounterInvalid = encounterInvalid || r == utf8.RuneError
+ ellipsis := utf8Ellipsis
+ if encounterInvalid {
+ ellipsis = asciiEllipsis
}
- return input[:n-3] + asciiEllipsis, asciiEllipsis + input[n-3:]
+ right = ellipsis + right
+ }
+ return left, right
+}
+
+func ellipsisDisplayString(str string, limit int) (res string, offset int, truncated, encounterInvalid bool) {
+ if len(str) <= limit {
+ return str, len(str), false, false
}
- end := 0
- for end <= n-3 {
- _, size := utf8.DecodeRuneInString(input[end:])
- if end+size > n-3 {
+ // To future maintainers: this logic must guarantee that the length of the returned runes doesn't exceed the limit,
+ // because the returned string will also be used as database value. UTF-8 VARCHAR(10) could store 10 rune characters,
+ // So each rune must be countered as at least 1 width.
+ // Even if there are some special Unicode characters (zero-width, combining, etc.), they should NEVER be counted as zero.
+ pos, used := 0, 0
+ for i, r := range str {
+ encounterInvalid = encounterInvalid || r == utf8.RuneError
+ pos = i
+ runeWidth := ellipsisGuessDisplayWidth(r)
+ if used+runeWidth+3 > limit {
break
}
- end += size
+ used += runeWidth
+ offset += utf8.RuneLen(r)
}
- return input[:end] + utf8Ellipsis, utf8Ellipsis + input[end:]
-}
-
-// SplitTrimSpace splits the string at given separator and trims leading and trailing space
-func SplitTrimSpace(input, sep string) []string {
- // Trim initial leading & trailing space
- input = strings.TrimSpace(input)
- // replace CRLF with LF
- input = strings.ReplaceAll(input, "\r\n", "\n")
-
- var stringList []string
- for _, s := range strings.Split(input, sep) {
- // trim leading and trailing space
- stringList = append(stringList, strings.TrimSpace(s))
+ // if the remaining are fewer than 3 runes, then maybe we could add them, no need to ellipse
+ if len(str)-pos <= 12 {
+ var nextCnt, nextWidth int
+ for _, r := range str[pos:] {
+ if nextCnt >= 4 {
+ break
+ }
+ nextWidth += ellipsisGuessDisplayWidth(r)
+ nextCnt++
+ }
+ if nextCnt <= 3 && used+nextWidth <= limit {
+ return str, len(str), false, false
+ }
}
-
- return stringList
+ if limit < 3 {
+ // if the limit is so small, do not add ellipsis
+ return str[:offset], offset, true, false
+ }
+ ellipsis := utf8Ellipsis
+ if encounterInvalid {
+ ellipsis = asciiEllipsis
+ }
+ return str[:offset] + ellipsis, offset, true, encounterInvalid
+}
+
+// TruncateRunes returns a truncated string with given rune limit,
+// it returns input string if its rune length doesn't exceed the limit.
+func TruncateRunes(str string, limit int) string {
+ if utf8.RuneCountInString(str) < limit {
+ return str
+ }
+ return string([]rune(str)[:limit])
}
diff --git a/modules/util/truncate_test.go b/modules/util/truncate_test.go
index dfe1230fd4..8789c824f5 100644
--- a/modules/util/truncate_test.go
+++ b/modules/util/truncate_test.go
@@ -4,43 +4,127 @@
package util
import (
+ "fmt"
+ "strings"
"testing"
"github.com/stretchr/testify/assert"
)
-func TestSplitString(t *testing.T) {
- type testCase struct {
- input string
- n int
- leftSub string
- ellipsis string
+func TestEllipsisGuessDisplayWidth(t *testing.T) {
+ cases := []struct {
+ r string
+ want int
+ }{
+ {r: "a", want: 1},
+ {r: "é", want: 1},
+ {r: "测", want: 2},
+ {r: "⚽", want: 2},
+ {r: "☁️", want: 3}, // 2 runes, it has a mark
+ {r: "\u200B", want: 1}, // ZWSP
+ {r: "\u3000", want: 2}, // ideographic space
}
-
- test := func(tc []*testCase, f func(input string, n int) (left, right string)) {
- for _, c := range tc {
- l, r := f(c.input, c.n)
- if c.ellipsis != "" {
- assert.Equal(t, c.leftSub+c.ellipsis, l, "test split %q at %d, expected leftSub: %q", c.input, c.n, c.leftSub)
- assert.Equal(t, c.ellipsis+c.input[len(c.leftSub):], r, "test split %s at %d, expected rightSub: %q", c.input, c.n, c.input[len(c.leftSub):])
- } else {
- assert.Equal(t, c.leftSub, l, "test split %q at %d, expected leftSub: %q", c.input, c.n, c.leftSub)
- assert.Empty(t, r, "test split %q at %d, expected rightSub: %q", c.input, c.n, "")
+ for _, c := range cases {
+ t.Run(c.r, func(t *testing.T) {
+ w := 0
+ for _, r := range c.r {
+ w += ellipsisGuessDisplayWidth(r)
}
- }
+ assert.Equal(t, c.want, w, "hex=% x", []byte(c.r))
+ })
+ }
+}
+
+func TestEllipsisString(t *testing.T) {
+ cases := []struct {
+ limit int
+
+ input, left, right string
+ }{
+ {limit: 0, input: "abcde", left: "", right: "…abcde"},
+ {limit: 1, input: "abcde", left: "", right: "…abcde"},
+ {limit: 2, input: "abcde", left: "", right: "…abcde"},
+ {limit: 3, input: "abcde", left: "…", right: "…abcde"},
+ {limit: 4, input: "abcde", left: "a…", right: "…bcde"},
+ {limit: 5, input: "abcde", left: "abcde", right: ""},
+ {limit: 6, input: "abcde", left: "abcde", right: ""},
+ {limit: 7, input: "abcde", left: "abcde", right: ""},
+
+ // a CJK char or emoji is considered as 2-ASCII width, the ellipsis is 3-ASCII width
+ {limit: 0, input: "测试文本", left: "", right: "…测试文本"},
+ {limit: 1, input: "测试文本", left: "", right: "…测试文本"},
+ {limit: 2, input: "测试文本", left: "", right: "…测试文本"},
+ {limit: 3, input: "测试文本", left: "…", right: "…测试文本"},
+ {limit: 4, input: "测试文本", left: "…", right: "…测试文本"},
+ {limit: 5, input: "测试文本", left: "测…", right: "…试文本"},
+ {limit: 6, input: "测试文本", left: "测…", right: "…试文本"},
+ {limit: 7, input: "测试文本", left: "测试…", right: "…文本"},
+ {limit: 8, input: "测试文本", left: "测试文本", right: ""},
+ {limit: 9, input: "测试文本", left: "测试文本", right: ""},
+
+ {limit: 6, input: "测试abc", left: "测…", right: "…试abc"},
+ {limit: 7, input: "测试abc", left: "测试abc", right: ""}, // exactly 7-width
+ {limit: 8, input: "测试abc", left: "测试abc", right: ""},
+
+ {limit: 7, input: "测abc试啊", left: "测ab…", right: "…c试啊"},
+ {limit: 8, input: "测abc试啊", left: "测abc…", right: "…试啊"},
+ {limit: 9, input: "测abc试啊", left: "测abc试啊", right: ""}, // exactly 9-width
+ {limit: 10, input: "测abc试啊", left: "测abc试啊", right: ""},
+ }
+ for _, c := range cases {
+ t.Run(fmt.Sprintf("%s(%d)", c.input, c.limit), func(t *testing.T) {
+ left, right := EllipsisDisplayStringX(c.input, c.limit)
+ assert.Equal(t, c.left, left, "left")
+ assert.Equal(t, c.right, right, "right")
+ })
}
- tc := []*testCase{
- {"abc123xyz", 0, "", utf8Ellipsis},
- {"abc123xyz", 1, "", utf8Ellipsis},
- {"abc123xyz", 4, "a", utf8Ellipsis},
- {"啊bc123xyz", 4, "", utf8Ellipsis},
- {"啊bc123xyz", 6, "啊", utf8Ellipsis},
- {"啊bc", 5, "啊bc", ""},
- {"啊bc", 6, "啊bc", ""},
- {"abc\xef\x03\xfe", 3, "", asciiEllipsis},
- {"abc\xef\x03\xfe", 4, "a", asciiEllipsis},
- {"\xef\x03", 1, "\xef\x03", ""},
- }
- test(tc, SplitStringAtByteN)
+ t.Run("LongInput", func(t *testing.T) {
+ left, right := EllipsisDisplayStringX(strings.Repeat("abc", 240), 90)
+ assert.Equal(t, strings.Repeat("abc", 29)+"…", left)
+ assert.Equal(t, "…"+strings.Repeat("abc", 211), right)
+ })
+
+ t.Run("InvalidUtf8", func(t *testing.T) {
+ invalidCases := []struct {
+ limit int
+ left, right string
+ }{
+ {limit: 0, left: "", right: "...\xef\x03\xfe\xef\x03\xfe"},
+ {limit: 1, left: "", right: "...\xef\x03\xfe\xef\x03\xfe"},
+ {limit: 2, left: "", right: "...\xef\x03\xfe\xef\x03\xfe"},
+ {limit: 3, left: "...", right: "...\xef\x03\xfe\xef\x03\xfe"},
+ {limit: 4, left: "...", right: "...\xef\x03\xfe\xef\x03\xfe"},
+ {limit: 5, left: "\xef\x03\xfe...", right: "...\xef\x03\xfe"},
+ {limit: 6, left: "\xef\x03\xfe\xef\x03\xfe", right: ""},
+ {limit: 7, left: "\xef\x03\xfe\xef\x03\xfe", right: ""},
+ }
+ for _, c := range invalidCases {
+ t.Run(fmt.Sprintf("%d", c.limit), func(t *testing.T) {
+ left, right := EllipsisDisplayStringX("\xef\x03\xfe\xef\x03\xfe", c.limit)
+ assert.Equal(t, c.left, left, "left")
+ assert.Equal(t, c.right, right, "right")
+ })
+ }
+ })
+
+ t.Run("IsLikelyEllipsisLeftPart", func(t *testing.T) {
+ assert.True(t, IsLikelyEllipsisLeftPart("abcde…"))
+ assert.True(t, IsLikelyEllipsisLeftPart("abcde..."))
+ })
+}
+
+func TestTruncateRunes(t *testing.T) {
+ assert.Equal(t, "", TruncateRunes("", 0))
+ assert.Equal(t, "", TruncateRunes("", 1))
+
+ assert.Equal(t, "", TruncateRunes("ab", 0))
+ assert.Equal(t, "a", TruncateRunes("ab", 1))
+ assert.Equal(t, "ab", TruncateRunes("ab", 2))
+ assert.Equal(t, "ab", TruncateRunes("ab", 3))
+
+ assert.Equal(t, "", TruncateRunes("测试", 0))
+ assert.Equal(t, "测", TruncateRunes("测试", 1))
+ assert.Equal(t, "测试", TruncateRunes("测试", 2))
+ assert.Equal(t, "测试", TruncateRunes("测试", 3))
}
diff --git a/modules/util/util_test.go b/modules/util/util_test.go
index 5abce08b41..52b05acc5b 100644
--- a/modules/util/util_test.go
+++ b/modules/util/util_test.go
@@ -242,10 +242,10 @@ func TestReserveLineBreakForTextarea(t *testing.T) {
}
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/modules/web/handler.go b/modules/web/handler.go
index 1812c664b3..9a3e4a7f17 100644
--- a/modules/web/handler.go
+++ b/modules/web/handler.go
@@ -4,7 +4,6 @@
package web
import (
- goctx "context"
"fmt"
"net/http"
"reflect"
@@ -51,7 +50,6 @@ func (r *responseWriter) WriteHeader(statusCode int) {
var (
httpReqType = reflect.TypeOf((*http.Request)(nil))
respWriterType = reflect.TypeOf((*http.ResponseWriter)(nil)).Elem()
- cancelFuncType = reflect.TypeOf((*goctx.CancelFunc)(nil)).Elem()
)
// preCheckHandler checks whether the handler is valid, developers could get first-time feedback, all mistakes could be found at startup
@@ -65,11 +63,8 @@ func preCheckHandler(fn reflect.Value, argsIn []reflect.Value) {
if !hasStatusProvider {
panic(fmt.Sprintf("handler should have at least one ResponseStatusProvider argument, but got %s", fn.Type()))
}
- if fn.Type().NumOut() != 0 && fn.Type().NumIn() != 1 {
- panic(fmt.Sprintf("handler should have no return value or only one argument, but got %s", fn.Type()))
- }
- if fn.Type().NumOut() == 1 && fn.Type().Out(0) != cancelFuncType {
- panic(fmt.Sprintf("handler should return a cancel function, but got %s", fn.Type()))
+ if fn.Type().NumOut() != 0 {
+ panic(fmt.Sprintf("handler should have no return value other than registered ones, but got %s", fn.Type()))
}
}
@@ -105,16 +100,10 @@ func prepareHandleArgsIn(resp http.ResponseWriter, req *http.Request, fn reflect
return argsIn
}
-func handleResponse(fn reflect.Value, ret []reflect.Value) goctx.CancelFunc {
- if len(ret) == 1 {
- if cancelFunc, ok := ret[0].Interface().(goctx.CancelFunc); ok {
- return cancelFunc
- }
- panic(fmt.Sprintf("unsupported return type: %s", ret[0].Type()))
- } else if len(ret) > 1 {
+func handleResponse(fn reflect.Value, ret []reflect.Value) {
+ if len(ret) != 0 {
panic(fmt.Sprintf("unsupported return values: %s", fn.Type()))
}
- return nil
}
func hasResponseBeenWritten(argsIn []reflect.Value) bool {
@@ -171,11 +160,8 @@ func toHandlerProvider(handler any) func(next http.Handler) http.Handler {
routing.UpdateFuncInfo(req.Context(), funcInfo)
ret := fn.Call(argsIn)
- // handle the return value, and defer the cancel function if there is one
- cancelFunc := handleResponse(fn, ret)
- if cancelFunc != nil {
- defer cancelFunc()
- }
+ // handle the return value (no-op at the moment)
+ handleResponse(fn, ret)
// if the response has not been written, call the next handler
if next != nil && !hasResponseBeenWritten(argsIn) {
diff --git a/modules/web/middleware/data.go b/modules/web/middleware/data.go
index 08d83f94be..a47da0f836 100644
--- a/modules/web/middleware/data.go
+++ b/modules/web/middleware/data.go
@@ -7,46 +7,21 @@ import (
"context"
"time"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
)
-// ContextDataStore represents a data store
-type ContextDataStore interface {
- GetData() ContextData
-}
-
-type ContextData map[string]any
-
-func (ds ContextData) GetData() ContextData {
- return ds
-}
-
-func (ds ContextData) MergeFrom(other ContextData) ContextData {
- for k, v := range other {
- ds[k] = v
- }
- return ds
-}
-
const ContextDataKeySignedUser = "SignedUser"
-type contextDataKeyType struct{}
-
-var contextDataKey contextDataKeyType
-
-func WithContextData(c context.Context) context.Context {
- return context.WithValue(c, contextDataKey, make(ContextData, 10))
-}
-
-func GetContextData(c context.Context) ContextData {
- if ds, ok := c.Value(contextDataKey).(ContextData); ok {
- return ds
+func GetContextData(c context.Context) reqctx.ContextData {
+ if rc := reqctx.GetRequestDataStore(c); rc != nil {
+ return rc.GetData()
}
return nil
}
-func CommonTemplateContextData() ContextData {
- return ContextData{
+func CommonTemplateContextData() reqctx.ContextData {
+ return reqctx.ContextData{
"IsLandingPageOrganizations": setting.LandingPageURL == setting.LandingPageOrganizations,
"ShowRegistrationButton": setting.Service.ShowRegistrationButton,
diff --git a/modules/web/middleware/flash.go b/modules/web/middleware/flash.go
index 88da2049a4..0caaa8c036 100644
--- a/modules/web/middleware/flash.go
+++ b/modules/web/middleware/flash.go
@@ -7,11 +7,13 @@ import (
"fmt"
"html/template"
"net/url"
+
+ "code.gitea.io/gitea/modules/reqctx"
)
// Flash represents a one time data transfer between two requests.
type Flash struct {
- DataStore ContextDataStore
+ DataStore reqctx.RequestDataStore
url.Values
ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string
}
diff --git a/modules/web/route_test.go b/modules/web/route_test.go
deleted file mode 100644
index 6e4c309293..0000000000
--- a/modules/web/route_test.go
+++ /dev/null
@@ -1,222 +0,0 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package web
-
-import (
- "bytes"
- "net/http"
- "net/http/httptest"
- "strconv"
- "testing"
-
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/test"
-
- chi "github.com/go-chi/chi/v5"
- "github.com/stretchr/testify/assert"
-)
-
-func TestRoute1(t *testing.T) {
- buff := bytes.NewBufferString("")
- recorder := httptest.NewRecorder()
- recorder.Body = buff
-
- r := NewRouter()
- r.Get("/{username}/{reponame}/{type:issues|pulls}", func(resp http.ResponseWriter, req *http.Request) {
- username := chi.URLParam(req, "username")
- assert.EqualValues(t, "gitea", username)
- reponame := chi.URLParam(req, "reponame")
- assert.EqualValues(t, "gitea", reponame)
- tp := chi.URLParam(req, "type")
- assert.EqualValues(t, "issues", tp)
- })
-
- req, err := http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues", nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.EqualValues(t, http.StatusOK, recorder.Code)
-}
-
-func TestRoute2(t *testing.T) {
- buff := bytes.NewBufferString("")
- recorder := httptest.NewRecorder()
- recorder.Body = buff
-
- hit := -1
-
- r := NewRouter()
- r.Group("/{username}/{reponame}", func() {
- r.Group("", func() {
- r.Get("/{type:issues|pulls}", func(resp http.ResponseWriter, req *http.Request) {
- username := chi.URLParam(req, "username")
- assert.EqualValues(t, "gitea", username)
- reponame := chi.URLParam(req, "reponame")
- assert.EqualValues(t, "gitea", reponame)
- tp := chi.URLParam(req, "type")
- assert.EqualValues(t, "issues", tp)
- hit = 0
- })
-
- r.Get("/{type:issues|pulls}/{index}", func(resp http.ResponseWriter, req *http.Request) {
- username := chi.URLParam(req, "username")
- assert.EqualValues(t, "gitea", username)
- reponame := chi.URLParam(req, "reponame")
- assert.EqualValues(t, "gitea", reponame)
- tp := chi.URLParam(req, "type")
- assert.EqualValues(t, "issues", tp)
- index := chi.URLParam(req, "index")
- assert.EqualValues(t, "1", index)
- hit = 1
- })
- }, func(resp http.ResponseWriter, req *http.Request) {
- if stop, err := strconv.Atoi(req.FormValue("stop")); err == nil {
- hit = stop
- resp.WriteHeader(http.StatusOK)
- }
- })
-
- r.Group("/issues/{index}", func() {
- r.Get("/view", func(resp http.ResponseWriter, req *http.Request) {
- username := chi.URLParam(req, "username")
- assert.EqualValues(t, "gitea", username)
- reponame := chi.URLParam(req, "reponame")
- assert.EqualValues(t, "gitea", reponame)
- index := chi.URLParam(req, "index")
- assert.EqualValues(t, "1", index)
- hit = 2
- })
- })
- })
-
- req, err := http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues", nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.EqualValues(t, http.StatusOK, recorder.Code)
- assert.EqualValues(t, 0, hit)
-
- req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1", nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.EqualValues(t, http.StatusOK, recorder.Code)
- assert.EqualValues(t, 1, hit)
-
- req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1?stop=100", nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.EqualValues(t, http.StatusOK, recorder.Code)
- assert.EqualValues(t, 100, hit)
-
- req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1/view", nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.EqualValues(t, http.StatusOK, recorder.Code)
- assert.EqualValues(t, 2, hit)
-}
-
-func TestRoute3(t *testing.T) {
- buff := bytes.NewBufferString("")
- recorder := httptest.NewRecorder()
- recorder.Body = buff
-
- hit := -1
-
- m := NewRouter()
- r := NewRouter()
- r.Mount("/api/v1", m)
-
- m.Group("/repos", func() {
- m.Group("/{username}/{reponame}", func() {
- m.Group("/branch_protections", func() {
- m.Get("", func(resp http.ResponseWriter, req *http.Request) {
- hit = 0
- })
- m.Post("", func(resp http.ResponseWriter, req *http.Request) {
- hit = 1
- })
- m.Group("/{name}", func() {
- m.Get("", func(resp http.ResponseWriter, req *http.Request) {
- hit = 2
- })
- m.Patch("", func(resp http.ResponseWriter, req *http.Request) {
- hit = 3
- })
- m.Delete("", func(resp http.ResponseWriter, req *http.Request) {
- hit = 4
- })
- })
- })
- })
- })
-
- req, err := http.NewRequest("GET", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections", nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.EqualValues(t, http.StatusOK, recorder.Code)
- assert.EqualValues(t, 0, hit)
-
- req, err = http.NewRequest("POST", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections", nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.EqualValues(t, http.StatusOK, recorder.Code, http.StatusOK)
- assert.EqualValues(t, 1, hit)
-
- req, err = http.NewRequest("GET", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.EqualValues(t, http.StatusOK, recorder.Code)
- assert.EqualValues(t, 2, hit)
-
- req, err = http.NewRequest("PATCH", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.EqualValues(t, http.StatusOK, recorder.Code)
- assert.EqualValues(t, 3, hit)
-
- req, err = http.NewRequest("DELETE", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.EqualValues(t, http.StatusOK, recorder.Code)
- assert.EqualValues(t, 4, hit)
-}
-
-func TestRouteNormalizePath(t *testing.T) {
- type paths struct {
- EscapedPath, RawPath, Path string
- }
- testPath := func(reqPath string, expectedPaths paths) {
- recorder := httptest.NewRecorder()
- recorder.Body = bytes.NewBuffer(nil)
-
- actualPaths := paths{EscapedPath: "(none)", RawPath: "(none)", Path: "(none)"}
- r := NewRouter()
- r.Get("/*", func(resp http.ResponseWriter, req *http.Request) {
- actualPaths.EscapedPath = req.URL.EscapedPath()
- actualPaths.RawPath = req.URL.RawPath
- actualPaths.Path = req.URL.Path
- })
-
- req, err := http.NewRequest("GET", reqPath, nil)
- assert.NoError(t, err)
- r.ServeHTTP(recorder, req)
- assert.Equal(t, expectedPaths, actualPaths, "req path = %q", reqPath)
- }
-
- // RawPath could be empty if the EscapedPath is the same as escape(Path) and it is already normalized
- testPath("/", paths{EscapedPath: "/", RawPath: "", Path: "/"})
- testPath("//", paths{EscapedPath: "/", RawPath: "/", Path: "/"})
- testPath("/%2f", paths{EscapedPath: "/%2f", RawPath: "/%2f", Path: "//"})
- testPath("///a//b/", paths{EscapedPath: "/a/b", RawPath: "/a/b", Path: "/a/b"})
-
- defer test.MockVariableValue(&setting.UseSubURLPath, true)()
- defer test.MockVariableValue(&setting.AppSubURL, "/sub-path")()
- testPath("/", paths{EscapedPath: "(none)", RawPath: "(none)", Path: "(none)"}) // 404
- testPath("/sub-path", paths{EscapedPath: "/", RawPath: "/", Path: "/"})
- testPath("/sub-path/", paths{EscapedPath: "/", RawPath: "/", Path: "/"})
- testPath("/sub-path//a/b///", paths{EscapedPath: "/a/b", RawPath: "/a/b", Path: "/a/b"})
- testPath("/sub-path/%2f/", paths{EscapedPath: "/%2f", RawPath: "/%2f", Path: "//"})
- // "/v2" is special for OCI container registry, it should always be in the root of the site
- testPath("/v2", paths{EscapedPath: "/v2", RawPath: "/v2", Path: "/v2"})
- testPath("/v2/", paths{EscapedPath: "/v2", RawPath: "/v2", Path: "/v2"})
- testPath("/v2/%2f", paths{EscapedPath: "/v2/%2f", RawPath: "/v2/%2f", Path: "/v2//"})
-}
diff --git a/modules/web/route.go b/modules/web/router.go
similarity index 85%
rename from modules/web/route.go
rename to modules/web/router.go
index 787521dfb0..da06b955b1 100644
--- a/modules/web/route.go
+++ b/modules/web/router.go
@@ -10,6 +10,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/htmlutil"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
@@ -29,18 +30,18 @@ func Bind[T any](_ T) http.HandlerFunc {
}
// SetForm set the form object
-func SetForm(dataStore middleware.ContextDataStore, obj any) {
+func SetForm(dataStore reqctx.ContextDataProvider, obj any) {
dataStore.GetData()["__form"] = obj
}
// GetForm returns the validate form information
-func GetForm(dataStore middleware.ContextDataStore) any {
+func GetForm(dataStore reqctx.RequestDataStore) any {
return dataStore.GetData()["__form"]
}
// Router defines a route based on chi's router
type Router struct {
- chiRouter chi.Router
+ chiRouter *chi.Mux
curGroupPrefix string
curMiddlewares []any
}
@@ -92,16 +93,21 @@ func isNilOrFuncNil(v any) bool {
return r.Kind() == reflect.Func && r.IsNil()
}
-func (r *Router) wrapMiddlewareAndHandler(h []any) ([]func(http.Handler) http.Handler, http.HandlerFunc) {
- handlerProviders := make([]func(http.Handler) http.Handler, 0, len(r.curMiddlewares)+len(h)+1)
- for _, m := range r.curMiddlewares {
+func wrapMiddlewareAndHandler(curMiddlewares, h []any) ([]func(http.Handler) http.Handler, http.HandlerFunc) {
+ handlerProviders := make([]func(http.Handler) http.Handler, 0, len(curMiddlewares)+len(h)+1)
+ for _, m := range curMiddlewares {
if !isNilOrFuncNil(m) {
handlerProviders = append(handlerProviders, toHandlerProvider(m))
}
}
- for _, m := range h {
+ if len(h) == 0 {
+ panic("no endpoint handler provided")
+ }
+ for i, m := range h {
if !isNilOrFuncNil(m) {
handlerProviders = append(handlerProviders, toHandlerProvider(m))
+ } else if i == len(h)-1 {
+ panic("endpoint handler can't be nil")
}
}
middlewares := handlerProviders[:len(handlerProviders)-1]
@@ -116,7 +122,7 @@ func (r *Router) wrapMiddlewareAndHandler(h []any) ([]func(http.Handler) http.Ha
// Methods adds the same handlers for multiple http "methods" (separated by ",").
// If any method is invalid, the lower level router will panic.
func (r *Router) Methods(methods, pattern string, h ...any) {
- middlewares, handlerFunc := r.wrapMiddlewareAndHandler(h)
+ middlewares, handlerFunc := wrapMiddlewareAndHandler(r.curMiddlewares, h)
fullPattern := r.getPattern(pattern)
if strings.Contains(methods, ",") {
methods := strings.Split(methods, ",")
@@ -136,7 +142,7 @@ func (r *Router) Mount(pattern string, subRouter *Router) {
// Any delegate requests for all methods
func (r *Router) Any(pattern string, h ...any) {
- middlewares, handlerFunc := r.wrapMiddlewareAndHandler(h)
+ middlewares, handlerFunc := wrapMiddlewareAndHandler(r.curMiddlewares, h)
r.chiRouter.With(middlewares...).HandleFunc(r.getPattern(pattern), handlerFunc)
}
@@ -242,39 +248,11 @@ func (r *Router) Combo(pattern string, h ...any) *Combo {
return &Combo{r, pattern, h}
}
-// Combo represents a tiny group routes with same pattern
-type Combo struct {
- r *Router
- pattern string
- h []any
-}
-
-// Get delegates Get method
-func (c *Combo) Get(h ...any) *Combo {
- c.r.Get(c.pattern, append(c.h, h...)...)
- return c
-}
-
-// Post delegates Post method
-func (c *Combo) Post(h ...any) *Combo {
- c.r.Post(c.pattern, append(c.h, h...)...)
- return c
-}
-
-// Delete delegates Delete method
-func (c *Combo) Delete(h ...any) *Combo {
- c.r.Delete(c.pattern, append(c.h, h...)...)
- return c
-}
-
-// Put delegates Put method
-func (c *Combo) Put(h ...any) *Combo {
- c.r.Put(c.pattern, append(c.h, h...)...)
- return c
-}
-
-// Patch delegates Patch method
-func (c *Combo) Patch(h ...any) *Combo {
- c.r.Patch(c.pattern, append(c.h, h...)...)
- return c
+// PathGroup creates a group of paths which could be matched by regexp.
+// It is only designed to resolve some special cases which chi router can't handle.
+// For most cases, it shouldn't be used because it needs to iterate all rules to find the matched one (inefficient).
+func (r *Router) PathGroup(pattern string, fn func(g *RouterPathGroup), h ...any) {
+ g := &RouterPathGroup{r: r, pathParam: "*"}
+ fn(g)
+ r.Any(pattern, append(h, g.ServeHTTP)...)
}
diff --git a/modules/web/router_combo.go b/modules/web/router_combo.go
new file mode 100644
index 0000000000..4478689027
--- /dev/null
+++ b/modules/web/router_combo.go
@@ -0,0 +1,41 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package web
+
+// Combo represents a tiny group routes with same pattern
+type Combo struct {
+ r *Router
+ pattern string
+ h []any
+}
+
+// Get delegates Get method
+func (c *Combo) Get(h ...any) *Combo {
+ c.r.Get(c.pattern, append(c.h, h...)...)
+ return c
+}
+
+// Post delegates Post method
+func (c *Combo) Post(h ...any) *Combo {
+ c.r.Post(c.pattern, append(c.h, h...)...)
+ return c
+}
+
+// Delete delegates Delete method
+func (c *Combo) Delete(h ...any) *Combo {
+ c.r.Delete(c.pattern, append(c.h, h...)...)
+ return c
+}
+
+// Put delegates Put method
+func (c *Combo) Put(h ...any) *Combo {
+ c.r.Put(c.pattern, append(c.h, h...)...)
+ return c
+}
+
+// Patch delegates Patch method
+func (c *Combo) Patch(h ...any) *Combo {
+ c.r.Patch(c.pattern, append(c.h, h...)...)
+ return c
+}
diff --git a/modules/web/router_path.go b/modules/web/router_path.go
new file mode 100644
index 0000000000..39082c0724
--- /dev/null
+++ b/modules/web/router_path.go
@@ -0,0 +1,135 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package web
+
+import (
+ "fmt"
+ "net/http"
+ "regexp"
+ "strings"
+
+ "code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/modules/util"
+
+ "github.com/go-chi/chi/v5"
+)
+
+type RouterPathGroup struct {
+ r *Router
+ pathParam string
+ matchers []*routerPathMatcher
+}
+
+func (g *RouterPathGroup) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+ chiCtx := chi.RouteContext(req.Context())
+ path := chiCtx.URLParam(g.pathParam)
+ for _, m := range g.matchers {
+ if m.matchPath(chiCtx, path) {
+ handler := m.handlerFunc
+ for i := len(m.middlewares) - 1; i >= 0; i-- {
+ handler = m.middlewares[i](handler).ServeHTTP
+ }
+ handler(resp, req)
+ return
+ }
+ }
+ g.r.chiRouter.NotFoundHandler().ServeHTTP(resp, req)
+}
+
+// MatchPath matches the request method, and uses regexp to match the path.
+// The pattern uses "<...>" to define path parameters, for example: "/" (different from chi router)
+// It is only designed to resolve some special cases which chi router can't handle.
+// For most cases, it shouldn't be used because it needs to iterate all rules to find the matched one (inefficient).
+func (g *RouterPathGroup) MatchPath(methods, pattern string, h ...any) {
+ g.matchers = append(g.matchers, newRouterPathMatcher(methods, pattern, h...))
+}
+
+type routerPathParam struct {
+ name string
+ captureGroup int
+}
+
+type routerPathMatcher struct {
+ methods container.Set[string]
+ re *regexp.Regexp
+ params []routerPathParam
+ middlewares []func(http.Handler) http.Handler
+ handlerFunc http.HandlerFunc
+}
+
+func (p *routerPathMatcher) matchPath(chiCtx *chi.Context, path string) bool {
+ if !p.methods.Contains(chiCtx.RouteMethod) {
+ return false
+ }
+ if !strings.HasPrefix(path, "/") {
+ path = "/" + path
+ }
+ pathMatches := p.re.FindStringSubmatchIndex(path) // Golang regexp match pairs [start, end, start, end, ...]
+ if pathMatches == nil {
+ return false
+ }
+ var paramMatches [][]int
+ for i := 2; i < len(pathMatches); {
+ paramMatches = append(paramMatches, []int{pathMatches[i], pathMatches[i+1]})
+ pmIdx := len(paramMatches) - 1
+ end := pathMatches[i+1]
+ i += 2
+ for ; i < len(pathMatches); i += 2 {
+ if pathMatches[i] >= end {
+ break
+ }
+ paramMatches[pmIdx] = append(paramMatches[pmIdx], pathMatches[i], pathMatches[i+1])
+ }
+ }
+ for i, pm := range paramMatches {
+ groupIdx := p.params[i].captureGroup * 2
+ chiCtx.URLParams.Add(p.params[i].name, path[pm[groupIdx]:pm[groupIdx+1]])
+ }
+ return true
+}
+
+func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher {
+ middlewares, handlerFunc := wrapMiddlewareAndHandler(nil, h)
+ p := &routerPathMatcher{methods: make(container.Set[string]), middlewares: middlewares, handlerFunc: handlerFunc}
+ for _, method := range strings.Split(methods, ",") {
+ p.methods.Add(strings.TrimSpace(method))
+ }
+ re := []byte{'^'}
+ lastEnd := 0
+ for lastEnd < len(pattern) {
+ start := strings.IndexByte(pattern[lastEnd:], '<')
+ if start == -1 {
+ re = append(re, pattern[lastEnd:]...)
+ break
+ }
+ end := strings.IndexByte(pattern[lastEnd+start:], '>')
+ if end == -1 {
+ panic(fmt.Sprintf("invalid pattern: %s", pattern))
+ }
+ re = append(re, pattern[lastEnd:lastEnd+start]...)
+ partName, partExp, _ := strings.Cut(pattern[lastEnd+start+1:lastEnd+start+end], ":")
+ lastEnd += start + end + 1
+
+ // TODO: it could support to specify a "capture group" for the name, for example: "/"
+ // it is not used so no need to implement it now
+ param := routerPathParam{}
+ if partExp == "*" {
+ re = append(re, "(.*?)/?"...)
+ if lastEnd < len(pattern) && pattern[lastEnd] == '/' {
+ lastEnd++ // the "*" pattern is able to handle the last slash, so skip it
+ }
+ } else {
+ partExp = util.IfZero(partExp, "[^/]+")
+ re = append(re, '(')
+ re = append(re, partExp...)
+ re = append(re, ')')
+ }
+ param.name = partName
+ p.params = append(p.params, param)
+ }
+ re = append(re, '$')
+ reStr := string(re)
+ p.re = regexp.MustCompile(reStr)
+ return p
+}
diff --git a/modules/web/router_test.go b/modules/web/router_test.go
new file mode 100644
index 0000000000..bdcf623b95
--- /dev/null
+++ b/modules/web/router_test.go
@@ -0,0 +1,245 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package web
+
+import (
+ "bytes"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
+ "code.gitea.io/gitea/modules/util"
+
+ "github.com/go-chi/chi/v5"
+ "github.com/stretchr/testify/assert"
+)
+
+func chiURLParamsToMap(chiCtx *chi.Context) map[string]string {
+ pathParams := chiCtx.URLParams
+ m := make(map[string]string, len(pathParams.Keys))
+ for i, key := range pathParams.Keys {
+ if key == "*" && pathParams.Values[i] == "" {
+ continue // chi router will add an empty "*" key if there is a "Mount"
+ }
+ m[key] = pathParams.Values[i]
+ }
+ return util.Iif(len(m) == 0, nil, m)
+}
+
+func TestPathProcessor(t *testing.T) {
+ testProcess := func(pattern, uri string, expectedPathParams map[string]string) {
+ chiCtx := chi.NewRouteContext()
+ chiCtx.RouteMethod = "GET"
+ p := newRouterPathMatcher("GET", pattern, http.NotFound)
+ assert.True(t, p.matchPath(chiCtx, uri), "use pattern %s to process uri %s", pattern, uri)
+ assert.Equal(t, expectedPathParams, chiURLParamsToMap(chiCtx), "use pattern %s to process uri %s", pattern, uri)
+ }
+
+ // the "<...>" is intentionally designed to distinguish from chi's path parameters, because:
+ // 1. their behaviors are totally different, we do not want to mislead developers
+ // 2. we can write regexp in "" easily and parse it easily
+ testProcess("//", "/a/b", map[string]string{"p1": "a", "p2": "b"})
+ testProcess("/", "", map[string]string{"p1": ""}) // this is a special case, because chi router could use empty path
+ testProcess("/", "/", map[string]string{"p1": ""})
+ testProcess("//", "/a", map[string]string{"p1": "", "p2": "a"})
+ testProcess("//", "/a/b", map[string]string{"p1": "a", "p2": "b"})
+ testProcess("//", "/a/b/c", map[string]string{"p1": "a/b", "p2": "c"})
+}
+
+func TestRouter(t *testing.T) {
+ buff := bytes.NewBufferString("")
+ recorder := httptest.NewRecorder()
+ recorder.Body = buff
+
+ type resultStruct struct {
+ method string
+ pathParams map[string]string
+ handlerMark string
+ }
+ var res resultStruct
+
+ h := func(optMark ...string) func(resp http.ResponseWriter, req *http.Request) {
+ mark := util.OptionalArg(optMark, "")
+ return func(resp http.ResponseWriter, req *http.Request) {
+ res.method = req.Method
+ res.pathParams = chiURLParamsToMap(chi.RouteContext(req.Context()))
+ res.handlerMark = mark
+ }
+ }
+
+ stopMark := func(optMark ...string) func(resp http.ResponseWriter, req *http.Request) {
+ mark := util.OptionalArg(optMark, "")
+ return func(resp http.ResponseWriter, req *http.Request) {
+ if stop := req.FormValue("stop"); stop != "" && (mark == "" || mark == stop) {
+ h(stop)(resp, req)
+ resp.WriteHeader(http.StatusOK)
+ }
+ }
+ }
+
+ r := NewRouter()
+ r.NotFound(h("not-found:/"))
+ r.Get("/{username}/{reponame}/{type:issues|pulls}", h("list-issues-a")) // this one will never be called
+ r.Group("/{username}/{reponame}", func() {
+ r.Get("/{type:issues|pulls}", h("list-issues-b"))
+ r.Group("", func() {
+ r.Get("/{type:issues|pulls}/{index}", h("view-issue"))
+ }, stopMark())
+ r.Group("/issues/{index}", func() {
+ r.Post("/update", h("update-issue"))
+ })
+ })
+
+ m := NewRouter()
+ m.NotFound(h("not-found:/api/v1"))
+ r.Mount("/api/v1", m)
+ m.Group("/repos", func() {
+ m.Group("/{username}/{reponame}", func() {
+ m.Group("/branches", func() {
+ m.Get("", h())
+ m.Post("", h())
+ m.Group("/{name}", func() {
+ m.Get("", h())
+ m.Patch("", h())
+ m.Delete("", h())
+ })
+ m.PathGroup("/*", func(g *RouterPathGroup) {
+ g.MatchPath("GET", `//`, stopMark("s2"), h("match-path"))
+ }, stopMark("s1"))
+ })
+ })
+ })
+
+ testRoute := func(t *testing.T, methodPath string, expected resultStruct) {
+ t.Run(methodPath, func(t *testing.T) {
+ res = resultStruct{}
+ methodPathFields := strings.Fields(methodPath)
+ req, err := http.NewRequest(methodPathFields[0], methodPathFields[1], nil)
+ assert.NoError(t, err)
+ r.ServeHTTP(recorder, req)
+ assert.EqualValues(t, expected, res)
+ })
+ }
+
+ t.Run("RootRouter", func(t *testing.T) {
+ testRoute(t, "GET /the-user/the-repo/other", resultStruct{method: "GET", handlerMark: "not-found:/"})
+ testRoute(t, "GET /the-user/the-repo/pulls", resultStruct{
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "pulls"},
+ handlerMark: "list-issues-b",
+ })
+ testRoute(t, "GET /the-user/the-repo/issues/123", resultStruct{
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
+ handlerMark: "view-issue",
+ })
+ testRoute(t, "GET /the-user/the-repo/issues/123?stop=hijack", resultStruct{
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "type": "issues", "index": "123"},
+ handlerMark: "hijack",
+ })
+ testRoute(t, "POST /the-user/the-repo/issues/123/update", resultStruct{
+ method: "POST",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "index": "123"},
+ handlerMark: "update-issue",
+ })
+ })
+
+ t.Run("Sub Router", func(t *testing.T) {
+ testRoute(t, "GET /api/v1/other", resultStruct{method: "GET", handlerMark: "not-found:/api/v1"})
+ testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches", resultStruct{
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo"},
+ })
+
+ testRoute(t, "POST /api/v1/repos/the-user/the-repo/branches", resultStruct{
+ method: "POST",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo"},
+ })
+
+ testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/master", resultStruct{
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "name": "master"},
+ })
+
+ testRoute(t, "PATCH /api/v1/repos/the-user/the-repo/branches/master", resultStruct{
+ method: "PATCH",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "name": "master"},
+ })
+
+ testRoute(t, "DELETE /api/v1/repos/the-user/the-repo/branches/master", resultStruct{
+ method: "DELETE",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "name": "master"},
+ })
+ })
+
+ t.Run("MatchPath", func(t *testing.T) {
+ testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn", resultStruct{
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
+ handlerMark: "match-path",
+ })
+ testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/000", resultStruct{
+ method: "GET",
+ pathParams: map[string]string{"reponame": "the-repo", "username": "the-user", "*": "d1/d2/000"},
+ handlerMark: "not-found:/api/v1",
+ })
+
+ testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s1", resultStruct{
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn"},
+ handlerMark: "s1",
+ })
+
+ testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/d1/d2/fn?stop=s2", resultStruct{
+ method: "GET",
+ pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "*": "d1/d2/fn", "dir": "d1/d2", "file": "fn"},
+ handlerMark: "s2",
+ })
+ })
+}
+
+func TestRouteNormalizePath(t *testing.T) {
+ type paths struct {
+ EscapedPath, RawPath, Path string
+ }
+ testPath := func(reqPath string, expectedPaths paths) {
+ recorder := httptest.NewRecorder()
+ recorder.Body = bytes.NewBuffer(nil)
+
+ actualPaths := paths{EscapedPath: "(none)", RawPath: "(none)", Path: "(none)"}
+ r := NewRouter()
+ r.Get("/*", func(resp http.ResponseWriter, req *http.Request) {
+ actualPaths.EscapedPath = req.URL.EscapedPath()
+ actualPaths.RawPath = req.URL.RawPath
+ actualPaths.Path = req.URL.Path
+ })
+
+ req, err := http.NewRequest("GET", reqPath, nil)
+ assert.NoError(t, err)
+ r.ServeHTTP(recorder, req)
+ assert.Equal(t, expectedPaths, actualPaths, "req path = %q", reqPath)
+ }
+
+ // RawPath could be empty if the EscapedPath is the same as escape(Path) and it is already normalized
+ testPath("/", paths{EscapedPath: "/", RawPath: "", Path: "/"})
+ testPath("//", paths{EscapedPath: "/", RawPath: "/", Path: "/"})
+ testPath("/%2f", paths{EscapedPath: "/%2f", RawPath: "/%2f", Path: "//"})
+ testPath("///a//b/", paths{EscapedPath: "/a/b", RawPath: "/a/b", Path: "/a/b"})
+
+ defer test.MockVariableValue(&setting.UseSubURLPath, true)()
+ defer test.MockVariableValue(&setting.AppSubURL, "/sub-path")()
+ testPath("/", paths{EscapedPath: "(none)", RawPath: "(none)", Path: "(none)"}) // 404
+ testPath("/sub-path", paths{EscapedPath: "/", RawPath: "/", Path: "/"})
+ testPath("/sub-path/", paths{EscapedPath: "/", RawPath: "/", Path: "/"})
+ testPath("/sub-path//a/b///", paths{EscapedPath: "/a/b", RawPath: "/a/b", Path: "/a/b"})
+ testPath("/sub-path/%2f/", paths{EscapedPath: "/%2f", RawPath: "/%2f", Path: "//"})
+ // "/v2" is special for OCI container registry, it should always be in the root of the site
+ testPath("/v2", paths{EscapedPath: "/v2", RawPath: "/v2", Path: "/v2"})
+ testPath("/v2/", paths{EscapedPath: "/v2", RawPath: "/v2", Path: "/v2"})
+ testPath("/v2/%2f", paths{EscapedPath: "/v2/%2f", RawPath: "/v2/%2f", Path: "/v2//"})
+}
diff --git a/options/gitignore/AutomationStudio b/options/gitignore/AutomationStudio
new file mode 100644
index 0000000000..b5552b17a0
--- /dev/null
+++ b/options/gitignore/AutomationStudio
@@ -0,0 +1,31 @@
+# gitignore template for B&R Automation Studio (AS) 4
+# website: https://www.br-automation.com/en-us/products/software/automation-software/automation-studio/
+
+# AS temporary directories
+Binaries/
+Diagnosis/
+Temp/
+TempObjects/
+
+# AS transfer files
+*artransfer.br
+*arTrsfmode.nv
+
+# 'ignored' directory
+ignored/
+
+# ARNC0ext
+*arnc0ext.br
+
+# AS File types
+*.bak
+*.isopen
+*.orig
+*.log
+*.asar
+*.csvlog*
+*.set
+!**/Physical/**/*.set
+
+# RevInfo variables
+*RevInfo.var
diff --git a/options/gitignore/Firebase b/options/gitignore/Firebase
new file mode 100644
index 0000000000..55b8b0ea7f
--- /dev/null
+++ b/options/gitignore/Firebase
@@ -0,0 +1,28 @@
+# Firebase build and deployment files
+/firebase-debug.log
+/firebase-debug.*.log
+.firebaserc
+
+# Firebase Hosting
+/firebase.json
+*.cache
+hosting/.cache
+
+# Firebase Functions
+/functions/node_modules/
+/functions/.env
+/functions/package-lock.json
+
+# Firebase Emulators
+/firebase-*.zip
+/.firebase/
+/emulator-ui/
+
+# Logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Environment files (local configs)
+/.env.*
diff --git a/options/gitignore/Modelica b/options/gitignore/Modelica
new file mode 100644
index 0000000000..aa2cc996da
--- /dev/null
+++ b/options/gitignore/Modelica
@@ -0,0 +1,42 @@
+# Modelica - an object-oriented language for modeling of cyber-physical systems
+# https://modelica.org/
+# Ignore temporary files, build results, simulation files
+
+## Modelica-specific files
+*~
+*.bak
+*.bak-mo
+*.mof
+\#*\#
+*.moe
+*.mol
+
+## Build artefacts
+*.exe
+*.exp
+*.o
+*.pyc
+
+## Simulation files
+*.mat
+
+## Package files
+*.gz
+*.rar
+*.tar
+*.zip
+
+## Dymola-specific files
+buildlog.txt
+dsfinal.txt
+dsin.txt
+dslog.txt
+dsmodel*
+dsres.txt
+dymosim*
+request
+stat
+status
+stop
+success
+*.
diff --git a/options/gitignore/Python b/options/gitignore/Python
index c2fb773388..15201acc11 100644
--- a/options/gitignore/Python
+++ b/options/gitignore/Python
@@ -166,3 +166,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
+
+# PyPI configuration file
+.pypirc
diff --git a/options/gitignore/TeX b/options/gitignore/TeX
index a1f5212090..45e1706c28 100644
--- a/options/gitignore/TeX
+++ b/options/gitignore/TeX
@@ -26,6 +26,7 @@
## Bibliography auxiliary files (bibtex/biblatex/biber):
*.bbl
+*.bbl-SAVE-ERROR
*.bcf
*.blg
*-blx.aux
diff --git a/options/license/Elastic-2.0 b/options/license/Elastic-2.0
index 809108b857..9496955678 100644
--- a/options/license/Elastic-2.0
+++ b/options/license/Elastic-2.0
@@ -2,18 +2,18 @@ Elastic License 2.0
URL: https://www.elastic.co/licensing/elastic-license
-## Acceptance
+Acceptance
By using the software, you agree to all of the terms and conditions below.
-## Copyright License
+Copyright License
The licensor grants you a non-exclusive, royalty-free, worldwide,
non-sublicensable, non-transferable license to use, copy, distribute, make
available, and prepare derivative works of the software, in each case subject to
the limitations and conditions below.
-## Limitations
+Limitations
You may not provide the software to third parties as a hosted or managed
service, where the service provides users with access to any substantial set of
@@ -27,7 +27,7 @@ You may not alter, remove, or obscure any licensing, copyright, or other notices
of the licensor in the software. Any use of the licensor’s trademarks is subject
to applicable law.
-## Patents
+Patents
The licensor grants you a license, under any patent claims the licensor can
license, or becomes able to license, to make, have made, use, sell, offer for
@@ -40,7 +40,7 @@ the software granted under these terms ends immediately. If your company makes
such a claim, your patent license ends immediately for work on behalf of your
company.
-## Notices
+Notices
You must ensure that anyone who gets a copy of any part of the software from you
also gets a copy of these terms.
@@ -53,7 +53,7 @@ software prominent notices stating that you have modified the software.
These terms do not imply any licenses other than those expressly granted in
these terms.
-## Termination
+Termination
If you use the software in violation of these terms, such use is not licensed,
and your licenses will automatically terminate. If the licensor provides you
@@ -63,31 +63,31 @@ reinstated retroactively. However, if you violate these terms after such
reinstatement, any additional violation of these terms will cause your licenses
to terminate automatically and permanently.
-## No Liability
+No Liability
-*As far as the law allows, the software comes as is, without any warranty or
+As far as the law allows, the software comes as is, without any warranty or
condition, and the licensor will not be liable to you for any damages arising
out of these terms or the use or nature of the software, under any kind of
-legal claim.*
+legal claim.
-## Definitions
+Definitions
-The **licensor** is the entity offering these terms, and the **software** is the
+The licensor is the entity offering these terms, and the software is the
software the licensor makes available under these terms, including any portion
of it.
-**you** refers to the individual or entity agreeing to these terms.
+you refers to the individual or entity agreeing to these terms.
-**your company** is any legal entity, sole proprietorship, or other kind of
+your company is any legal entity, sole proprietorship, or other kind of
organization that you work for, plus all organizations that have control over,
are under the control of, or are under common control with that
-organization. **control** means ownership of substantially all the assets of an
+organization. control means ownership of substantially all the assets of an
entity, or the power to direct its management and policies by vote, contract, or
otherwise. Control can be direct or indirect.
-**your licenses** are all the licenses granted to you for the software under
+your licenses are all the licenses granted to you for the software under
these terms.
-**use** means anything you do with the software requiring one of your licenses.
+use means anything you do with the software requiring one of your licenses.
-**trademark** means trademarks, service marks, and similar rights.
+trademark means trademarks, service marks, and similar rights.
diff --git a/options/license/MIT b/options/license/MIT
index 2071b23b0e..fc2cf8e6b6 100644
--- a/options/license/MIT
+++ b/options/license/MIT
@@ -2,8 +2,17 @@ MIT License
Copyright (c)
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+associated documentation files (the "Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
+following conditions:
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all copies or substantial
+portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
+EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini
index 2abe3672cd..57b209aaf9 100644
--- a/options/locale/locale_cs-CZ.ini
+++ b/options/locale/locale_cs-CZ.ini
@@ -647,6 +647,7 @@ joined_on=Přidal/a se %s
repositories=Repozitáře
activity=Veřejná aktivita
followers=Sledující
+show_more=Zobrazit více
starred=Oblíbené repozitáře
watched=Sledované repozitáře
code=Kód
@@ -3742,6 +3743,7 @@ variables.creation.success=Proměnná „%s“ byla přidána.
variables.update.failed=Úprava proměnné se nezdařila.
variables.update.success=Proměnná byla upravena.
+
[projects]
deleted.display_name=Odstraněný projekt
type-1.display_name=Samostatný projekt
diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini
index c8dd3f71a2..8139970bd7 100644
--- a/options/locale/locale_de-DE.ini
+++ b/options/locale/locale_de-DE.ini
@@ -618,6 +618,7 @@ joined_on=Beigetreten am %s
repositories=Repositories
activity=Öffentliche Aktivität
followers=Follower
+show_more=Mehr anzeigen
starred=Favoriten
watched=Beobachtete Repositories
code=Quelltext
@@ -3556,6 +3557,7 @@ variables.creation.success=Die Variable „%s“ wurde hinzugefügt.
variables.update.failed=Fehler beim Bearbeiten der Variable.
variables.update.success=Die Variable wurde bearbeitet.
+
[projects]
type-1.display_name=Individuelles Projekt
type-2.display_name=Repository-Projekt
diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini
index 193441828a..fa9c41d5de 100644
--- a/options/locale/locale_el-GR.ini
+++ b/options/locale/locale_el-GR.ini
@@ -580,6 +580,7 @@ joined_on=Εγγράφηκε την %s
repositories=Αποθετήρια
activity=Δημόσια Δραστηριότητα
followers=Ακόλουθοι
+show_more=Εμφάνιση Περισσότερων
starred=Αγαπημένα Αποθετήρια
watched=Ακολουθούμενα Αποθετήρια
code=Κώδικας
@@ -3439,6 +3440,7 @@ variables.creation.success=Η μεταβλητή "%s" έχει προστεθε
variables.update.failed=Αποτυχία επεξεργασίας μεταβλητής.
variables.update.success=Η μεταβλητή έχει τροποποιηθεί.
+
[projects]
type-1.display_name=Ατομικό Έργο
type-2.display_name=Έργο Αποθετηρίου
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 92ce4f2db9..16d894aa26 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -649,6 +649,7 @@ joined_on = Joined on %s
repositories = Repositories
activity = Public Activity
followers = Followers
+show_more = Show More
starred = Starred Repositories
watched = Watched Repositories
code = Code
@@ -1946,8 +1947,8 @@ pulls.delete.title = Delete this pull request?
pulls.delete.text = Do you really want to delete this pull request? (This will permanently remove all content. Consider closing it instead, if you intend to keep it archived)
pulls.recently_pushed_new_branches = You pushed on branch %[1]s %[2]s
-pulls.upstream_diverging_prompt_behind_1 = This branch is %d commit behind %s
-pulls.upstream_diverging_prompt_behind_n = This branch is %d commits behind %s
+pulls.upstream_diverging_prompt_behind_1 = This branch is %[1]d commit behind %[2]s
+pulls.upstream_diverging_prompt_behind_n = This branch is %[1]d commits behind %[2]s
pulls.upstream_diverging_prompt_base_newer = The base branch %s has new changes
pulls.upstream_diverging_merge = Sync fork
@@ -3722,6 +3723,7 @@ runners.status.active = Active
runners.status.offline = Offline
runners.version = Version
runners.reset_registration_token = Reset registration token
+runners.reset_registration_token_confirm = Would you like to invalidate the current token and generate a new one?
runners.reset_registration_token_success = Runner registration token reset successfully
runs.all_workflows = All Workflows
@@ -3773,6 +3775,9 @@ variables.creation.success = The variable "%s" has been added.
variables.update.failed = Failed to edit variable.
variables.update.success = The variable has been edited.
+logs.always_auto_scroll = Always auto scroll logs
+logs.always_expand_running = Always expand running logs
+
[projects]
deleted.display_name = Deleted Project
type-1.display_name = Individual Project
diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini
index e95513766b..d7d1cadd08 100644
--- a/options/locale/locale_es-ES.ini
+++ b/options/locale/locale_es-ES.ini
@@ -577,6 +577,7 @@ joined_on=Se unió el %s
repositories=Repositorios
activity=Actividad pública
followers=Seguidores
+show_more=Ver más
starred=Repositorios Favoritos
watched=Repositorios seguidos
code=Código
@@ -3415,6 +3416,7 @@ variables.creation.success=La variable "%s" ha sido añadida.
variables.update.failed=Error al editar la variable.
variables.update.success=La variable ha sido editada.
+
[projects]
type-1.display_name=Proyecto individual
type-2.display_name=Proyecto repositorio
diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini
index 640592f2bf..6dcd35560a 100644
--- a/options/locale/locale_fa-IR.ini
+++ b/options/locale/locale_fa-IR.ini
@@ -463,6 +463,7 @@ change_avatar=تغییر آواتار…
repositories=مخازن
activity=فعالیت های عمومی
followers=دنبال کنندگان
+show_more=نمایش بیشتر
starred=مخان ستاره دار
watched=مخازنی که دنبال میشوند
projects=پروژهها
@@ -2529,6 +2530,7 @@ runs.commit=کامیت
+
[projects]
[git.filemode]
diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini
index 375c7b11bf..b4f3869db4 100644
--- a/options/locale/locale_fi-FI.ini
+++ b/options/locale/locale_fi-FI.ini
@@ -1707,6 +1707,7 @@ runs.commit=Commit
+
[projects]
[git.filemode]
diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini
index 4d4940cff5..b024be4d88 100644
--- a/options/locale/locale_fr-FR.ini
+++ b/options/locale/locale_fr-FR.ini
@@ -649,6 +649,7 @@ joined_on=Inscrit le %s
repositories=Dépôts
activity=Activité publique
followers=abonnés
+show_more=Voir plus
starred=Dépôts favoris
watched=Dépôts surveillés
code=Code
@@ -1109,6 +1110,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 +1529,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 +1680,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 +2632,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
@@ -3769,6 +3773,7 @@ variables.creation.success=La variable « %s » a été ajoutée.
variables.update.failed=Impossible d’éditer la variable.
variables.update.success=La variable a bien été modifiée.
+
[projects]
deleted.display_name=Projet supprimé
type-1.display_name=Projet personnel
diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini
index f40e0037d2..7bf6941e35 100644
--- a/options/locale/locale_ga-IE.ini
+++ b/options/locale/locale_ga-IE.ini
@@ -649,6 +649,7 @@ joined_on=Cláraigh ar %s
repositories=Stórais
activity=Gníomhaíocht Phoiblí
followers=Leantóirí
+show_more=Taispeáin Tuilleadh
starred=Stórais Réaltaithe
watched=Stórais Breathnaithe
code=Cód
@@ -1109,6 +1110,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 +1529,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 +1680,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
@@ -1943,8 +1946,8 @@ pulls.delete.title=Scrios an t-iarratas tarraingthe seo?
pulls.delete.text=An bhfuil tú cinnte gur mhaith leat an t-iarratas tarraingthe seo a scriosadh? (Bainfidh sé seo an t-inneachar go léir go buan. Smaoinigh ar é a dhúnadh ina ionad sin, má tá sé i gceist agat é a choinneáil i gcartlann)
pulls.recently_pushed_new_branches=Bhrúigh tú ar bhrainse %[1]s %[2]s
-pulls.upstream_diverging_prompt_behind_1=Tá an brainse seo %d tiomantas taobh thiar de %s
-pulls.upstream_diverging_prompt_behind_n=Tá an brainse seo %d geallta taobh thiar de %s
+pulls.upstream_diverging_prompt_behind_1=Tá an brainse seo %[1]d tiomantas taobh thiar de %[2]s
+pulls.upstream_diverging_prompt_behind_n=Tá an brainse seo %[1]d geallta taobh thiar de %[2]s
pulls.upstream_diverging_prompt_base_newer=Tá athruithe nua ar an mbunbhrainse %s
pulls.upstream_diverging_merge=Forc sionc
@@ -2629,6 +2632,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
@@ -3718,6 +3722,7 @@ runners.status.active=Gníomhach
runners.status.offline=As líne
runners.version=Leagan
runners.reset_registration_token=Athshocraigh comhartha clár
+runners.reset_registration_token_confirm=Ar mhaith leat an comhartha reatha a neamhbhailiú agus ceann nua a ghiniúint?
runners.reset_registration_token_success=D'éirigh le hathshocrú comhartha clárúcháin an dara háit
runs.all_workflows=Gach Sreafaí Oibre
@@ -3769,6 +3774,9 @@ variables.creation.success=Tá an athróg "%s" curtha leis.
variables.update.failed=Theip ar athróg a chur in eagar.
variables.update.success=Tá an t-athróg curtha in eagar.
+logs.always_auto_scroll=Logchomhaid scrollaithe uathoibríoch i gcónaí
+logs.always_expand_running=Leathnaigh logs reatha i gcónaí
+
[projects]
deleted.display_name=Tionscadal scriosta
type-1.display_name=Tionscadal Aonair
diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini
index 88ccc9fac2..8e11f07d33 100644
--- a/options/locale/locale_hu-HU.ini
+++ b/options/locale/locale_hu-HU.ini
@@ -1615,6 +1615,7 @@ runs.commit=Commit
+
[projects]
[git.filemode]
diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini
index 237323a0fc..7c8271b98b 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
@@ -1396,6 +1444,7 @@ variables.creation.success=Variabel "%s" telah ditambahkan.
variables.update.failed=Gagal mengedit variabel.
variables.update.success=Variabel telah diedit.
+
[projects]
type-1.display_name=Proyek Individu
type-2.display_name=Proyek Repositori
diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini
index 0564d49b1c..d3824f83a2 100644
--- a/options/locale/locale_is-IS.ini
+++ b/options/locale/locale_is-IS.ini
@@ -1342,6 +1342,7 @@ runs.commit=Framlag
+
[projects]
[git.filemode]
diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini
index 567e6acdce..1268f15673 100644
--- a/options/locale/locale_it-IT.ini
+++ b/options/locale/locale_it-IT.ini
@@ -488,6 +488,7 @@ change_avatar=Modifica il tuo avatar…
repositories=Repository
activity=Attività pubblica
followers=Seguaci
+show_more=Mostra Altro
starred=Repositories votate
watched=Repository Osservate
projects=Progetti
@@ -2807,6 +2808,7 @@ runs.commit=Commit
+
[projects]
[git.filemode]
diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini
index 1d8b33bef6..efcf34806a 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=フィルターをクリア
@@ -648,6 +649,7 @@ joined_on=%sに登録
repositories=リポジトリ
activity=公開アクティビティ
followers=フォロワー
+show_more=さらに表示
starred=スター付きリポジトリ
watched=ウォッチ中リポジトリ
code=コード
@@ -1043,6 +1045,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 +1670,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 +1708,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 +1941,8 @@ pulls.delete.title=このプルリクエストを削除しますか?
pulls.delete.text=本当にこのプルリクエストを削除しますか? (これはすべてのコンテンツを完全に削除します。 保存しておきたい場合は、代わりにクローズすることを検討してください)
pulls.recently_pushed_new_branches=%[2]s 、あなたはブランチ %[1]s にプッシュしました
+pulls.upstream_diverging_prompt_base_newer=ベースブランチ %s に新しい変更があります
+pulls.upstream_diverging_merge=フォークを同期
pull.deleted_branch=(削除済み):%s
pull.agit_documentation=AGitに関するドキュメントを確認する
@@ -3744,6 +3763,7 @@ variables.creation.success=変数 "%s" を追加しました。
variables.update.failed=変数を更新できませんでした。
variables.update.success=変数を更新しました。
+
[projects]
deleted.display_name=削除されたプロジェクト
type-1.display_name=個人プロジェクト
diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini
index 48220d5c99..361ab23e4c 100644
--- a/options/locale/locale_ko-KR.ini
+++ b/options/locale/locale_ko-KR.ini
@@ -1563,6 +1563,7 @@ runs.commit=커밋
+
[projects]
[git.filemode]
diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini
index fd412b95b4..4aa069b663 100644
--- a/options/locale/locale_lv-LV.ini
+++ b/options/locale/locale_lv-LV.ini
@@ -583,6 +583,7 @@ joined_on=Pievienojās %s
repositories=Repozitoriji
activity=Publiskā aktivitāte
followers=Sekotāji
+show_more=Rādīt vairāk
starred=Atzīmēti repozitoriji
watched=Vērotie repozitoriji
code=Kods
@@ -3443,6 +3444,7 @@ variables.creation.success=Mainīgais "%s" tika pievienots.
variables.update.failed=Neizdevās labot mainīgo.
variables.update.success=Mainīgais tika labots.
+
[projects]
type-1.display_name=Individuālais projekts
type-2.display_name=Repozitorija projekts
diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini
index a4da8177bc..fe41c4529a 100644
--- a/options/locale/locale_nl-NL.ini
+++ b/options/locale/locale_nl-NL.ini
@@ -487,6 +487,7 @@ change_avatar=Wijzig je profielfoto…
repositories=repositories
activity=Openbare activiteit
followers=Volgers
+show_more=Meer weergeven
starred=Repositories met ster
watched=Gevolgde repositories
projects=Projecten
@@ -2537,6 +2538,7 @@ runs.commit=Commit
+
[projects]
[git.filemode]
diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini
index 22f701219d..13c05eebe0 100644
--- a/options/locale/locale_pl-PL.ini
+++ b/options/locale/locale_pl-PL.ini
@@ -2430,6 +2430,7 @@ runs.commit=Commit
+
[projects]
[git.filemode]
diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini
index 8fb869898b..9a8b6aeb62 100644
--- a/options/locale/locale_pt-BR.ini
+++ b/options/locale/locale_pt-BR.ini
@@ -582,6 +582,7 @@ joined_on=Inscreveu-se em %s
repositories=Repositórios
activity=Atividade pública
followers=Seguidores
+show_more=Mostrar mais
starred=Repositórios favoritos
watched=Repositórios observados
code=Código
@@ -3353,6 +3354,7 @@ runs.empty_commit_message=(mensagem de commit vazia)
need_approval_desc=Precisa de aprovação para executar workflows para pull request do fork.
+
[projects]
type-1.display_name=Projeto individual
type-2.display_name=Projeto do repositório
diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini
index 776d2bdc2b..b59a3cd9b6 100644
--- a/options/locale/locale_pt-PT.ini
+++ b/options/locale/locale_pt-PT.ini
@@ -649,6 +649,7 @@ joined_on=Inscreveu-se em %s
repositories=Repositórios
activity=Trabalho público
followers=Seguidores
+show_more=Mostrar mais
starred=Repositórios favoritos
watched=Repositórios sob vigilância
code=Código
@@ -1679,7 +1680,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
@@ -1922,8 +1922,8 @@ pulls.close=Encerrar pedido de integração
pulls.closed_at=`fechou este pedido de integração %[2]s`
pulls.reopened_at=`reabriu este pedido de integração %[2]s`
pulls.cmd_instruction_hint=`Ver instruções para a linha de comandos.`
-pulls.cmd_instruction_checkout_title=Conferir
-pulls.cmd_instruction_checkout_desc=No seu repositório, irá criar um novo ramo para que possa testar as modificações.
+pulls.cmd_instruction_checkout_title=Checkout
+pulls.cmd_instruction_checkout_desc=A partir do seu repositório, crie um novo ramo e teste nele as modificações.
pulls.cmd_instruction_merge_title=Integrar
pulls.cmd_instruction_merge_desc=Integrar as modificações e enviar para o Gitea.
pulls.cmd_instruction_merge_warning=Aviso: Esta operação não pode executar pedidos de integração porque "auto-identificar integração manual" não estava habilitado
@@ -1946,8 +1946,8 @@ pulls.delete.title=Eliminar este pedido de integração?
pulls.delete.text=Tem a certeza que quer eliminar este pedido de integração? Isso irá remover todo o conteúdo permanentemente. Como alternativa considere fechá-lo, se pretender mantê-lo em arquivo.
pulls.recently_pushed_new_branches=Enviou para o ramo %[1]s %[2]s
-pulls.upstream_diverging_prompt_behind_1=Este ramo está %d cometimento atrás de %s
-pulls.upstream_diverging_prompt_behind_n=Este ramo está %d cometimentos atrás de %s
+pulls.upstream_diverging_prompt_behind_1=Este ramo está %[1]d cometimento atrás de %[2]s
+pulls.upstream_diverging_prompt_behind_n=Este ramo está %[1]d cometimentos atrás de %[2]s
pulls.upstream_diverging_prompt_base_newer=O ramo base %s tem novas modificações
pulls.upstream_diverging_merge=Sincronizar derivação
@@ -2632,6 +2632,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
@@ -3721,6 +3722,7 @@ runners.status.active=Em funcionamento
runners.status.offline=Desconectado
runners.version=Versão
runners.reset_registration_token=Repor código de registo
+runners.reset_registration_token_confirm=Gostaria de invalidar o código vigente e gerar um novo?
runners.reset_registration_token_success=O código de incrição do executor foi reposto com sucesso
runs.all_workflows=Todas as sequências de trabalho
@@ -3772,6 +3774,9 @@ variables.creation.success=A variável "%s" foi adicionada.
variables.update.failed=Falha ao editar a variável.
variables.update.success=A variável foi editada.
+logs.always_auto_scroll=Rolar registos de forma automática e permanente
+logs.always_expand_running=Expandir sempre os registos que vão rolando
+
[projects]
deleted.display_name=Planeamento eliminado
type-1.display_name=Planeamento individual
diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini
index 735077bd41..dc72dd7621 100644
--- a/options/locale/locale_ru-RU.ini
+++ b/options/locale/locale_ru-RU.ini
@@ -578,6 +578,7 @@ joined_on=Присоединил(ся/ась) %s
repositories=Репозитории
activity=Активность
followers=Подписчики
+show_more=Показать больше
starred=Избранные репозитории
watched=Отслеживаемые репозитории
code=Код
@@ -3373,6 +3374,7 @@ variables.creation.success=Переменная «%s» добавлена.
variables.update.failed=Не удалось изменить переменную.
variables.update.success=Переменная изменена.
+
[projects]
type-1.display_name=Индивидуальный проект
type-2.display_name=Проект репозитория
diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini
index 506fa5b492..f722d160eb 100644
--- a/options/locale/locale_si-LK.ini
+++ b/options/locale/locale_si-LK.ini
@@ -452,6 +452,7 @@ change_avatar=ඔබගේ අවතාරය වෙනස් කරන්න…
repositories=කෝෂ්ඨ
activity=ප්රසිද්ධ ක්රියාකාරකම
followers=අනුගාමිකයන්
+show_more=තව පෙන්වන්න
starred=තරු ගබඩාව
watched=නරඹන ලද ගබඩාවලදී
projects=ව්යාපෘති
@@ -2470,6 +2471,7 @@ runs.commit=කැප
+
[projects]
[git.filemode]
diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini
index b4bf6fb552..39c13c358e 100644
--- a/options/locale/locale_sk-SK.ini
+++ b/options/locale/locale_sk-SK.ini
@@ -1328,6 +1328,7 @@ runners.labels=Štítky
+
[projects]
[git.filemode]
diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini
index fc138381db..2fd8277b76 100644
--- a/options/locale/locale_sv-SE.ini
+++ b/options/locale/locale_sv-SE.ini
@@ -2005,6 +2005,7 @@ runs.commit=Commit
+
[projects]
[git.filemode]
diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini
index 9b7f2cb5c6..0c3884fd64 100644
--- a/options/locale/locale_tr-TR.ini
+++ b/options/locale/locale_tr-TR.ini
@@ -631,6 +631,7 @@ joined_on=%s tarihinde katıldı
repositories=Depolar
activity=Genel Aktivite
followers=Takipçiler
+show_more=Daha Fazla Göster
starred=Yıldızlanmış depolar
watched=İzlenen Depolar
code=Kod
@@ -3633,6 +3634,7 @@ variables.creation.success=`"%s" değişkeni eklendi.`
variables.update.failed=Değişken düzenlenemedi.
variables.update.success=Değişken düzenlendi.
+
[projects]
deleted.display_name=Silinmiş Proje
type-1.display_name=Kişisel Proje
diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini
index efefeeb436..e8553594a0 100644
--- a/options/locale/locale_uk-UA.ini
+++ b/options/locale/locale_uk-UA.ini
@@ -466,6 +466,7 @@ change_avatar=Змінити свій аватар…
repositories=Репозиторії
activity=Публічна активність
followers=Читачі
+show_more=Показати більше
starred=Обрані Репозиторії
watched=Відстежувані репозиторії
projects=Проєкт
@@ -2538,6 +2539,7 @@ runs.commit=Коміт
+
[projects]
[git.filemode]
diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index 3add7f8be3..572ad2d667 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=当您是管理员时,您不能删除自己。请先移除您的管理员权限
@@ -632,6 +649,7 @@ joined_on=加入于 %s
repositories=仓库列表
activity=公开活动
followers=关注者
+show_more=显示更多
starred=已点赞
watched=已关注仓库
code=代码
@@ -688,14 +706,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 +767,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 +810,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 +929,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 +940,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 +949,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 +1034,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 +1047,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 +1110,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 +1259,7 @@ releases=版本发布
tag=Git标签
released_this=发布
tagged_this=已标记
+file.title=%s 位于 %s
file_raw=原始文件
file_history=文件历史
file_view_source=源码模式
@@ -1238,6 +1276,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 +1468,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 +1497,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 +1529,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 +1584,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 +1675,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 +1753,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 +1779,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 +1900,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 +1946,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 的文档
@@ -1894,6 +1963,7 @@ milestones.no_due_date=暂无截止日期
milestones.open=开启
milestones.close=关闭
milestones.new_subheader=里程碑可以帮助您组织工单并跟踪其进度。
+milestones.completeness=%d%% 已完成
milestones.create=创建里程碑
milestones.title=标题
milestones.desc=描述
@@ -2116,6 +2186,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 +2226,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 +2363,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 +2409,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 +2463,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 +2482,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 +2502,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 +2632,7 @@ release.new_release=发布新版
release.draft=草稿
release.prerelease=预发行
release.stable=稳定
+release.latest=最新版本
release.compare=比较
release.edit=编辑
release.ahead.commits=%d 次提交
@@ -2593,6 +2717,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 +2790,7 @@ settings.delete_prompt=删除操作会永久清除该组织的信息,并且 所有仓库。
settings.labels_desc=添加能够被该组织下的 所有仓库 的工单使用的标签。
@@ -2689,6 +2815,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 +2884,7 @@ last_page=末页
total=总计:%d
settings=管理设置
+dashboard.new_version_hint=Gitea %s 现已可用,您正在运行 %s。查看 博客 了解详情。
dashboard.statistic=摘要
dashboard.maintenance_operations=运维
dashboard.system_status=系统状态
@@ -2799,6 +2927,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 +2957,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 +3041,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 +3077,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 +3177,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 +3305,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 +3354,7 @@ monitor.next=下次执行时间
monitor.previous=上次执行时间
monitor.execute_times=执行次数
monitor.process=运行中进程
+monitor.stacktrace=调用栈踪迹
monitor.processes_count=%d 个进程
monitor.download_diagnosis_report=下载诊断报告
monitor.desc=进程描述
@@ -3206,6 +3362,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 +3465,8 @@ raw_minutes=分钟
[dropzone]
default_message=拖动文件或者点击此处上传。
+invalid_input_type=您不能上传该类型的文件
+file_too_big=文件体积({{filesize}} MB)超过了最大允许体积({{maxFilesize}} MB)
remove_file=移除文件
[notification]
@@ -3378,6 +3538,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 +3618,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 +3742,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=该工作流由派生仓库的合并请求所触发,需要批准方可运行。
@@ -3604,7 +3773,9 @@ 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=组织项目
diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini
index 6f37d30efc..e1ca6ebfc8 100644
--- a/options/locale/locale_zh-HK.ini
+++ b/options/locale/locale_zh-HK.ini
@@ -975,6 +975,7 @@ runners.task_list.repository=儲存庫
+
[projects]
[git.filemode]
diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini
index 3b1d37a322..a3bf6ca888 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,13 +637,17 @@ 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=追蹤者
+show_more=顯示更多
starred=已加星號
watched=關注的儲存庫
code=程式碼
@@ -561,11 +660,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 +704,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 +725,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 +751,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 +761,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 +778,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 +802,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 +869,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 +882,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 +902,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 +922,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 +981,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 +1003,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 +1020,7 @@ template_helper=將儲存庫設為範本
template_description=儲存庫範本讓使用者可新增相同目錄結構、檔案以及設定的儲存庫。
visibility=瀏覽權限
visibility_description=只有組織擁有者或有權限的組織成員才能看到。
+visibility_helper=將儲存庫設為私有
visibility_helper_forced=您的網站管理員強制新的存儲庫必需設定為私有。
visibility_fork_helper=(修改本值將會影響所有 fork 儲存庫)
clone_helper=需要有關 Clone 的協助嗎?查看幫助 。
@@ -846,7 +1029,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 +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=問題標籤
@@ -861,6 +1053,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 +1067,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 +1089,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 +1105,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 +1137,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 +1155,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 +1180,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 +1190,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 +1200,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 +1215,7 @@ watch=關注
unstar=移除星號
star=加上星號
fork=Fork
+action.blocked_user=無法執行操作,因為您被儲存庫擁有者封鎖。
download_archive=下載此儲存庫
more_operations=更多操作
@@ -1031,6 +1253,7 @@ releases=版本發布
tag=標籤
released_this=發布了此版本
tagged_this=標記了此標籤
+file.title=%s 於 %s
file_raw=原始文件
file_history=歷史記錄
file_view_source=檢視原始碼
@@ -1038,22 +1261,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 +1318,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 +1339,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 +1372,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 +1384,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 +1402,7 @@ commitstatus.success=成功
ext_issues=存取外部問題
ext_issues.desc=連結到外部問題追蹤器。
+projects.desc=在專案看板中管理問題與合併請求。
projects.description=描述 (選用)
projects.description_placeholder=描述
projects.create=建立專案
@@ -1182,6 +1430,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 +1462,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 +1491,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 +1512,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 +1523,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 +1565,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 +1574,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 +1594,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 +1619,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 +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`
+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 +1706,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 +1730,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 +1747,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 +1758,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 +1773,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 +1793,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 +1812,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 +1851,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 +1892,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 +1904,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 +1914,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 +1938,14 @@ 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_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 +1953,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 +1971,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 +2020,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 +2092,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 +2110,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 +2131,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 +2177,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 +2207,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 +2247,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 +2277,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 +2294,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 +2317,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 +2347,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 +2400,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 +2473,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 +2493,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 +2590,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 +2623,7 @@ release.new_release=發布新版本
release.draft=草稿
release.prerelease=預發布版本
release.stable=穩定
+release.latest=最新
release.compare=比較
release.edit=編輯
release.ahead.commits=%d 次提交
@@ -2160,7 +2637,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 +2649,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 +2669,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 +2678,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 +2686,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 +2708,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 +2717,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 +2752,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 +2772,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 +2840,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 +2848,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 +2873,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 +2886,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 +2902,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 +2918,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 +2948,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 +2969,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 +3018,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 +3031,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 +3051,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 +3068,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 +3166,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 +3208,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 +3283,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 +3295,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 +3313,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 +3328,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 +3345,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 +3363,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 +3392,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 +3427,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 +3456,7 @@ raw_minutes=分鐘
[dropzone]
default_message=拖放檔案或是點擊此處上傳。
+invalid_input_type=您無法上傳此類型的檔案
file_too_big=檔案大小({{filesize}} MB) 超過了最大允許大小({{maxFilesize}} MB)
remove_file=移除文件
@@ -2915,8 +3494,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 +3529,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 +3556,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 +3581,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 +3589,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 +3618,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 +3647,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 +3662,7 @@ deletion=移除 Secret
deletion.description=移除 Secret 是永久的且不可還原,是否繼續?
deletion.success=已移除此 Secret。
deletion.failed=移除 Secret 失敗。
+management=Secret 管理
[actions]
actions=Actions
@@ -3077,6 +3674,7 @@ status.waiting=正在等候
status.running=正在執行
status.success=成功
status.failure=失敗
+status.cancelled=已取消
status.skipped=已略過
status.blocked=已阻塞
@@ -3093,6 +3691,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 +3712,70 @@ 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 8755cfe06f..1e3c5ab155 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",
@@ -33,12 +33,12 @@
"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,16 +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",
@@ -81,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.0.2",
+ "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",
@@ -102,15 +102,15 @@
"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"
},
@@ -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"
],
@@ -4383,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": {
@@ -4700,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",
@@ -4747,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": {
@@ -4812,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"
@@ -4968,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",
@@ -4993,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": {
@@ -5022,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"
@@ -5049,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"
},
@@ -5068,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": {
@@ -5091,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",
@@ -5113,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": {
@@ -5136,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"
@@ -5155,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": {
@@ -5182,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"
},
@@ -5263,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": {
@@ -5315,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": {
@@ -5422,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"
@@ -5978,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",
@@ -6048,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",
@@ -6300,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",
@@ -6319,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": {
@@ -6420,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",
@@ -6933,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": {
@@ -7549,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"
@@ -7585,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",
@@ -7635,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",
@@ -7737,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"
@@ -7790,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": {
@@ -7879,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",
@@ -8241,166 +7953,35 @@
}
},
"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.0.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-github/-/eslint-plugin-github-5.0.2.tgz",
+ "integrity": "sha512-nMdzWJQ5CimjQDY6SFeJ0KIXuNFf0dgDWEd4eP3UWfuTuP/dXcZJDg7MQRvAFt743T1zUi4+/HdOihfu8xJkLA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@eslint/compat": "^1.2.3",
- "@eslint/eslintrc": "^3.1.0",
- "@eslint/js": "^9.14.0",
"@github/browserslist-config": "^1.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"aria-query": "^5.3.0",
"eslint-config-prettier": ">=8.0.0",
- "eslint-plugin-escompat": "^3.11.3",
+ "eslint-plugin-escompat": "^3.3.3",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-i18n-text": "^1.0.1",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-no-only-tests": "^3.0.0",
- "eslint-plugin-prettier": "^5.2.1",
+ "eslint-plugin-prettier": "^5.0.0",
"eslint-rule-documentation": ">=1.0.0",
- "globals": "^15.12.0",
"jsx-ast-utils": "^3.3.2",
"prettier": "^3.0.0",
- "svg-element-attributes": "^1.3.1",
- "typescript-eslint": "^8.14.0"
+ "svg-element-attributes": "^1.3.1"
},
"bin": {
"eslint-ignore-errors": "bin/eslint-ignore-errors.js"
},
"peerDependencies": {
- "eslint": "^8 || ^9"
- }
- },
- "node_modules/eslint-plugin-github/node_modules/@eslint/compat": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.4.tgz",
- "integrity": "sha512-S8ZdQj/N69YAtuqFt7653jwcvuUj131+6qGLUyDqfDg1OIoBQ66OCuXC473YQfO2AaxITTutiRQiDwoo7ZLYyg==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "peerDependencies": {
- "eslint": "^9.10.0"
- },
- "peerDependenciesMeta": {
- "eslint": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-plugin-github/node_modules/@eslint/eslintrc": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
- "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-plugin-github/node_modules/@eslint/eslintrc/node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/eslint-plugin-github/node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "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/eslint-plugin-github/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-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==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint-plugin-github/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==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/eslint-plugin-github/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": "*"
+ "eslint": "^8.0.1"
}
},
"node_modules/eslint-plugin-i18n-text": {
@@ -8448,16 +8029,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",
@@ -8686,117 +8269,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",
@@ -8820,256 +8292,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==",
@@ -9085,400 +8334,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",
@@ -9514,9 +8369,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": {
@@ -10995,9 +9850,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"
@@ -11017,10 +9872,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": {
@@ -11066,9 +9920,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": {
@@ -11188,9 +10042,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"
@@ -11555,26 +10409,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",
@@ -11593,13 +10427,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": {
@@ -11767,9 +10601,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"
},
@@ -11959,9 +10793,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": {
@@ -12079,9 +10913,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": {
@@ -12099,9 +10933,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": {
@@ -12198,20 +11032,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",
@@ -12434,6 +11254,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",
@@ -12584,13 +11414,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"
@@ -12603,9 +11433,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": {
@@ -12695,6 +11525,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",
@@ -12810,9 +11647,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",
@@ -13127,18 +11964,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",
@@ -13197,13 +12022,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",
@@ -13438,6 +12256,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",
@@ -13509,12 +12340,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"
},
@@ -13716,9 +12547,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",
@@ -13727,7 +12558,7 @@
"ajv-keywords": "^5.1.0"
},
"engines": {
- "node": ">= 12.13.0"
+ "node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
@@ -14125,34 +12956,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",
@@ -14236,9 +13039,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": [
{
@@ -14288,7 +13091,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": {
@@ -14801,9 +13604,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",
@@ -14847,9 +13650,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",
@@ -14865,16 +13668,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"
@@ -14898,55 +13701,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",
@@ -15153,9 +13907,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": {
@@ -15178,37 +13932,10 @@
"node": ">=14.17"
}
},
- "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==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/eslint-plugin": "8.17.0",
- "@typescript-eslint/parser": "8.17.0",
- "@typescript-eslint/utils": "8.17.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
"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": {
@@ -15321,9 +14048,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": {
@@ -15518,9 +14245,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": {
@@ -15534,24 +14261,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"
}
},
@@ -15622,9 +14350,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": {
@@ -15822,9 +14550,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 61e65c1f43..6881ddb306 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",
@@ -32,12 +32,12 @@
"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,16 +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",
@@ -80,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.0.2",
+ "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,15 +101,15 @@
"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"
},
diff --git a/routers/api/actions/artifacts.go b/routers/api/actions/artifacts.go
index 0a7f92ac40..910edd6d58 100644
--- a/routers/api/actions/artifacts.go
+++ b/routers/api/actions/artifacts.go
@@ -126,11 +126,10 @@ func ArtifactsRoutes(prefix string) *web.Router {
func ArtifactContexter() func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
- base, baseCleanUp := context.NewBaseContext(resp, req)
- defer baseCleanUp()
+ base := context.NewBaseContext(resp, req)
ctx := &ArtifactContext{Base: base}
- ctx.AppendContextValue(artifactContextKey, ctx)
+ ctx.SetContextValue(artifactContextKey, ctx)
// action task call server api with Bearer ACTIONS_RUNTIME_TOKEN
// we should verify the ACTIONS_RUNTIME_TOKEN
diff --git a/routers/api/actions/artifactsv4.go b/routers/api/actions/artifactsv4.go
index 6dd36888d2..8917a7a8a2 100644
--- a/routers/api/actions/artifactsv4.go
+++ b/routers/api/actions/artifactsv4.go
@@ -126,12 +126,9 @@ type artifactV4Routes struct {
func ArtifactV4Contexter() func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
- base, baseCleanUp := context.NewBaseContext(resp, req)
- defer baseCleanUp()
-
+ base := context.NewBaseContext(resp, req)
ctx := &ArtifactContext{Base: base}
- ctx.AppendContextValue(artifactContextKey, ctx)
-
+ ctx.SetContextValue(artifactContextKey, ctx)
next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
diff --git a/routers/api/actions/runner/runner.go b/routers/api/actions/runner/runner.go
index 8f365cc926..c55b30f7eb 100644
--- a/routers/api/actions/runner/runner.go
+++ b/routers/api/actions/runner/runner.go
@@ -69,7 +69,7 @@ func (s *Service) Register(
labels := req.Msg.Labels
// create new runner
- name, _ := util.SplitStringAtByteN(req.Msg.Name, 255)
+ name := util.EllipsisDisplayString(req.Msg.Name, 255)
runner := &actions_model.ActionRunner{
UUID: gouuid.New().String(),
Name: name,
diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go
index 47ea7137b8..8c06836ff8 100644
--- a/routers/api/packages/api.go
+++ b/routers/api/packages/api.go
@@ -138,45 +138,10 @@ func CommonRoutes() *web.Router {
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/arch", func() {
r.Methods("HEAD,GET", "/repository.key", arch.GetRepositoryKey)
-
- r.Methods("HEAD,GET,PUT,DELETE", "*", func(ctx *context.Context) {
- path := strings.Trim(ctx.PathParam("*"), "/")
-
- if ctx.Req.Method == "PUT" {
- reqPackageAccess(perm.AccessModeWrite)(ctx)
- if ctx.Written() {
- return
- }
- ctx.SetPathParam("repository", path)
- arch.UploadPackageFile(ctx)
- return
- }
-
- pathFields := strings.Split(path, "/")
- pathFieldsLen := len(pathFields)
-
- if (ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET") && pathFieldsLen >= 2 {
- ctx.SetPathParam("repository", strings.Join(pathFields[:pathFieldsLen-2], "/"))
- ctx.SetPathParam("architecture", pathFields[pathFieldsLen-2])
- ctx.SetPathParam("filename", pathFields[pathFieldsLen-1])
- arch.GetPackageOrRepositoryFile(ctx)
- return
- }
-
- if ctx.Req.Method == "DELETE" && pathFieldsLen >= 3 {
- reqPackageAccess(perm.AccessModeWrite)(ctx)
- if ctx.Written() {
- return
- }
- ctx.SetPathParam("repository", strings.Join(pathFields[:pathFieldsLen-3], "/"))
- ctx.SetPathParam("name", pathFields[pathFieldsLen-3])
- ctx.SetPathParam("version", pathFields[pathFieldsLen-2])
- ctx.SetPathParam("architecture", pathFields[pathFieldsLen-1])
- arch.DeletePackageVersion(ctx)
- return
- }
-
- ctx.Status(http.StatusNotFound)
+ r.PathGroup("*", func(g *web.RouterPathGroup) {
+ g.MatchPath("PUT", "/", reqPackageAccess(perm.AccessModeWrite), arch.UploadPackageFile)
+ g.MatchPath("HEAD,GET", "///", arch.GetPackageOrRepositoryFile)
+ g.MatchPath("DELETE", "////", reqPackageAccess(perm.AccessModeWrite), arch.DeletePackageVersion)
})
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/cargo", func() {
@@ -465,6 +430,8 @@ func CommonRoutes() *web.Router {
r.Post("/api/charts", reqPackageAccess(perm.AccessModeWrite), helm.UploadPackage)
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/maven", func() {
+ // FIXME: this path design is not right.
+ // It should be `/.../{groupId}/{artifactId}/{version}`, but not `/.../{groupId}-{artifactId}/{version}`
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
r.Get("/*", maven.DownloadPackageFile)
r.Head("/*", maven.ProvidePackageFileHeader)
diff --git a/routers/api/v1/admin/adopt.go b/routers/api/v1/admin/adopt.go
index 613d123494..55ea8c6758 100644
--- a/routers/api/v1/admin/adopt.go
+++ b/routers/api/v1/admin/adopt.go
@@ -80,8 +80,8 @@ func AdoptRepository(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
// "403":
// "$ref": "#/responses/forbidden"
- ownerName := ctx.PathParam(":username")
- repoName := ctx.PathParam(":reponame")
+ ownerName := ctx.PathParam("username")
+ repoName := ctx.PathParam("reponame")
ctxUser, err := user_model.GetUserByName(ctx, ownerName)
if err != nil {
@@ -142,8 +142,8 @@ func DeleteUnadoptedRepository(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
- ownerName := ctx.PathParam(":username")
- repoName := ctx.PathParam(":reponame")
+ ownerName := ctx.PathParam("username")
+ repoName := ctx.PathParam("reponame")
ctxUser, err := user_model.GetUserByName(ctx, ownerName)
if err != nil {
diff --git a/routers/api/v1/admin/cron.go b/routers/api/v1/admin/cron.go
index fba9d33f25..962e007776 100644
--- a/routers/api/v1/admin/cron.go
+++ b/routers/api/v1/admin/cron.go
@@ -74,7 +74,7 @@ func PostCronTask(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
- task := cron.GetTask(ctx.PathParam(":task"))
+ task := cron.GetTask(ctx.PathParam("task"))
if task == nil {
ctx.NotFound()
return
diff --git a/routers/api/v1/admin/email.go b/routers/api/v1/admin/email.go
index 6fe418249b..3de94d6868 100644
--- a/routers/api/v1/admin/email.go
+++ b/routers/api/v1/admin/email.go
@@ -38,7 +38,7 @@ func GetAllEmails(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx)
emails, maxResults, err := user_model.SearchEmails(ctx, &user_model.SearchEmailOptions{
- Keyword: ctx.PathParam(":email"),
+ Keyword: ctx.PathParam("email"),
ListOptions: listOptions,
})
if err != nil {
@@ -82,6 +82,6 @@ func SearchEmail(ctx *context.APIContext) {
// "403":
// "$ref": "#/responses/forbidden"
- ctx.SetPathParam(":email", ctx.FormTrim("q"))
+ ctx.SetPathParam("email", ctx.FormTrim("q"))
GetAllEmails(ctx)
}
diff --git a/routers/api/v1/admin/hooks.go b/routers/api/v1/admin/hooks.go
index db481fbf59..6b4689047b 100644
--- a/routers/api/v1/admin/hooks.go
+++ b/routers/api/v1/admin/hooks.go
@@ -73,7 +73,7 @@ func GetHook(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/Hook"
- hookID := ctx.PathParamInt64(":id")
+ hookID := ctx.PathParamInt64("id")
hook, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
@@ -142,7 +142,7 @@ func EditHook(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.EditHookOption)
// TODO in body params
- hookID := ctx.PathParamInt64(":id")
+ hookID := ctx.PathParamInt64("id")
utils.EditSystemHook(ctx, form, hookID)
}
@@ -164,7 +164,7 @@ func DeleteHook(ctx *context.APIContext) {
// "204":
// "$ref": "#/responses/empty"
- hookID := ctx.PathParamInt64(":id")
+ hookID := ctx.PathParamInt64("id")
if err := webhook.DeleteDefaultSystemWebhook(ctx, hookID); err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.NotFound()
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index b0f40084da..21cb2f9ccd 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)
@@ -373,7 +375,7 @@ func DeleteUserPublicKey(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- if err := asymkey_service.DeletePublicKey(ctx, ctx.ContextUser, ctx.PathParamInt64(":id")); err != nil {
+ if err := asymkey_service.DeletePublicKey(ctx, ctx.ContextUser, ctx.PathParamInt64("id")); err != nil {
if asymkey_model.IsErrKeyNotExist(err) {
ctx.NotFound()
} else if asymkey_model.IsErrKeyAccessDenied(err) {
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 96365e7c14..2f943d306c 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -596,12 +596,12 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
var err error
if assignOrg {
- ctx.Org.Organization, err = organization.GetOrgByName(ctx, ctx.PathParam(":org"))
+ ctx.Org.Organization, err = organization.GetOrgByName(ctx, ctx.PathParam("org"))
if err != nil {
if organization.IsErrOrgNotExist(err) {
- redirectUserID, err := user_model.LookupUserRedirect(ctx, ctx.PathParam(":org"))
+ redirectUserID, err := user_model.LookupUserRedirect(ctx, ctx.PathParam("org"))
if err == nil {
- context.RedirectToUser(ctx.Base, ctx.PathParam(":org"), redirectUserID)
+ context.RedirectToUser(ctx.Base, ctx.PathParam("org"), redirectUserID)
} else if user_model.IsErrUserRedirectNotExist(err) {
ctx.NotFound("GetOrgByName", err)
} else {
@@ -616,7 +616,7 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
}
if assignTeam {
- ctx.Org.Team, err = organization.GetTeamByID(ctx, ctx.PathParamInt64(":teamid"))
+ ctx.Org.Team, err = organization.GetTeamByID(ctx, ctx.PathParamInt64("teamid"))
if err != nil {
if organization.IsErrTeamNotExist(err) {
ctx.NotFound()
diff --git a/routers/api/v1/notify/threads.go b/routers/api/v1/notify/threads.go
index 0761e684a3..58a38cfd18 100644
--- a/routers/api/v1/notify/threads.go
+++ b/routers/api/v1/notify/threads.go
@@ -101,7 +101,7 @@ func ReadThread(ctx *context.APIContext) {
}
func getThread(ctx *context.APIContext) *activities_model.Notification {
- n, err := activities_model.GetNotificationByID(ctx, ctx.PathParamInt64(":id"))
+ n, err := activities_model.GetNotificationByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if db.IsErrNotExist(err) {
ctx.Error(http.StatusNotFound, "GetNotificationByID", err)
diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go
index 24ee4ed642..2a9bd92e87 100644
--- a/routers/api/v1/org/label.go
+++ b/routers/api/v1/org/label.go
@@ -139,7 +139,7 @@ func GetLabel(ctx *context.APIContext) {
label *issues_model.Label
err error
)
- strID := ctx.PathParam(":id")
+ strID := ctx.PathParam("id")
if intID, err2 := strconv.ParseInt(strID, 10, 64); err2 != nil {
label, err = issues_model.GetLabelInOrgByName(ctx, ctx.Org.Organization.ID, strID)
} else {
@@ -190,7 +190,7 @@ func EditLabel(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditLabelOption)
- l, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.PathParamInt64(":id"))
+ l, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrOrgLabelNotExist(err) {
ctx.NotFound()
@@ -249,7 +249,7 @@ func DeleteLabel(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- if err := issues_model.DeleteLabel(ctx, ctx.Org.Organization.ID, ctx.PathParamInt64(":id")); err != nil {
+ if err := issues_model.DeleteLabel(ctx, ctx.Org.Organization.ID, ctx.PathParamInt64("id")); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteLabel", err)
return
}
diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go
index 294d33014d..23c7da3d96 100644
--- a/routers/api/v1/org/member.go
+++ b/routers/api/v1/org/member.go
@@ -143,7 +143,7 @@ func IsMember(ctx *context.APIContext) {
// "404":
// description: user is not a member
- userToCheck := user.GetUserByParams(ctx)
+ userToCheck := user.GetContextUserByPathParam(ctx)
if ctx.Written() {
return
}
@@ -194,7 +194,7 @@ func IsPublicMember(ctx *context.APIContext) {
// "404":
// description: user is not a public member
- userToCheck := user.GetUserByParams(ctx)
+ userToCheck := user.GetContextUserByPathParam(ctx)
if ctx.Written() {
return
}
@@ -236,7 +236,7 @@ func PublicizeMember(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- userToPublicize := user.GetUserByParams(ctx)
+ userToPublicize := user.GetContextUserByPathParam(ctx)
if ctx.Written() {
return
}
@@ -278,7 +278,7 @@ func ConcealMember(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- userToConceal := user.GetUserByParams(ctx)
+ userToConceal := user.GetContextUserByPathParam(ctx)
if ctx.Written() {
return
}
@@ -318,7 +318,7 @@ func DeleteMember(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- member := user.GetUserByParams(ctx)
+ member := user.GetContextUserByPathParam(ctx)
if ctx.Written() {
return
}
diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go
index 3fb653bcb6..d65f922434 100644
--- a/routers/api/v1/org/org.go
+++ b/routers/api/v1/org/org.go
@@ -131,7 +131,7 @@ func GetUserOrgsPermissions(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
var o *user_model.User
- if o = user.GetUserByParamsName(ctx, ":org"); o == nil {
+ if o = user.GetUserByPathParam(ctx, "org"); o == nil {
return
}
diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go
index bc50960b61..7f44f6ed95 100644
--- a/routers/api/v1/org/team.go
+++ b/routers/api/v1/org/team.go
@@ -449,7 +449,7 @@ func GetTeamMember(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- u := user.GetUserByParams(ctx)
+ u := user.GetContextUserByPathParam(ctx)
if ctx.Written() {
return
}
@@ -492,7 +492,7 @@ func AddTeamMember(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- u := user.GetUserByParams(ctx)
+ u := user.GetContextUserByPathParam(ctx)
if ctx.Written() {
return
}
@@ -532,7 +532,7 @@ func RemoveTeamMember(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- u := user.GetUserByParams(ctx)
+ u := user.GetContextUserByPathParam(ctx)
if ctx.Written() {
return
}
@@ -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)
@@ -645,7 +645,7 @@ func GetTeamRepo(ctx *context.APIContext) {
// getRepositoryByParams get repository by a team's organization ID and repo name
func getRepositoryByParams(ctx *context.APIContext) *repo_model.Repository {
- repo, err := repo_model.GetRepositoryByName(ctx, ctx.Org.Team.OrgID, ctx.PathParam(":reponame"))
+ repo, err := repo_model.GetRepositoryByName(ctx, ctx.Org.Team.OrgID, ctx.PathParam("reponame"))
if err != nil {
if repo_model.IsErrRepoNotExist(err) {
ctx.NotFound()
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index 946203e97e..67dfda39a8 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.")
@@ -487,7 +487,7 @@ func GetBranchProtection(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
repo := ctx.Repo.Repository
- bpName := ctx.PathParam(":name")
+ bpName := ctx.PathParam("name")
bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
@@ -729,15 +729,11 @@ func CreateBranchProtection(ctx *context.APIContext) {
} else {
if !isPlainRule {
if ctx.Repo.GitRepo == nil {
- ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
+ ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return
}
- defer func() {
- ctx.Repo.GitRepo.Close()
- ctx.Repo.GitRepo = nil
- }()
}
// FIXME: since we only need to recheck files protected rules, we could improve this
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName)
@@ -809,7 +805,7 @@ func EditBranchProtection(ctx *context.APIContext) {
// "$ref": "#/responses/repoArchivedError"
form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
repo := ctx.Repo.Repository
- bpName := ctx.PathParam(":name")
+ bpName := ctx.PathParam("name")
protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
@@ -1061,15 +1057,11 @@ func EditBranchProtection(ctx *context.APIContext) {
} else {
if !isPlainRule {
if ctx.Repo.GitRepo == nil {
- ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
+ ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return
}
- defer func() {
- ctx.Repo.GitRepo.Close()
- ctx.Repo.GitRepo = nil
- }()
}
// FIXME: since we only need to recheck files protected rules, we could improve this
@@ -1132,7 +1124,7 @@ func DeleteBranchProtection(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
repo := ctx.Repo.Repository
- bpName := ctx.PathParam(":name")
+ bpName := ctx.PathParam("name")
bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go
index 0bbf5a1ea4..da3ee54e69 100644
--- a/routers/api/v1/repo/collaborators.go
+++ b/routers/api/v1/repo/collaborators.go
@@ -103,7 +103,7 @@ func IsCollaborator(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
- user, err := user_model.GetUserByName(ctx, ctx.PathParam(":collaborator"))
+ user, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
@@ -163,7 +163,7 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.AddCollaboratorOption)
- collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam(":collaborator"))
+ collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
@@ -226,7 +226,7 @@ func DeleteCollaborator(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
- collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam(":collaborator"))
+ collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
@@ -274,12 +274,12 @@ func GetRepoPermissions(ctx *context.APIContext) {
// "403":
// "$ref": "#/responses/forbidden"
- if !ctx.Doer.IsAdmin && ctx.Doer.LoginName != ctx.PathParam(":collaborator") && !ctx.IsUserRepoAdmin() {
+ if !ctx.Doer.IsAdmin && ctx.Doer.LoginName != ctx.PathParam("collaborator") && !ctx.IsUserRepoAdmin() {
ctx.Error(http.StatusForbidden, "User", "Only admins can query all permissions, repo admins can query all repo permissions, collaborators can query only their own")
return
}
- collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam(":collaborator"))
+ collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusNotFound, "GetUserByName", err)
diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go
index 788c75fab2..3b144d0c43 100644
--- a/routers/api/v1/repo/commits.go
+++ b/routers/api/v1/repo/commits.go
@@ -63,7 +63,7 @@ func GetSingleCommit(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- sha := ctx.PathParam(":sha")
+ sha := ctx.PathParam("sha")
if !git.IsValidRefPattern(sha) {
ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
return
@@ -312,8 +312,8 @@ func DownloadCommitDiffOrPatch(ctx *context.APIContext) {
// "$ref": "#/responses/string"
// "404":
// "$ref": "#/responses/notFound"
- sha := ctx.PathParam(":sha")
- diffType := git.RawDiffType(ctx.PathParam(":diffType"))
+ sha := ctx.PathParam("sha")
+ diffType := git.RawDiffType(ctx.PathParam("diffType"))
if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, diffType, ctx.Resp); err != nil {
if git.IsErrNotExist(err) {
diff --git a/routers/api/v1/repo/compare.go b/routers/api/v1/repo/compare.go
index 1678bc033c..87b890cb62 100644
--- a/routers/api/v1/repo/compare.go
+++ b/routers/api/v1/repo/compare.go
@@ -44,13 +44,12 @@ func CompareDiff(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if ctx.Repo.GitRepo == nil {
- gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
+ var err error
+ ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return
}
- ctx.Repo.GitRepo = gitRepo
- defer gitRepo.Close()
}
infoPath := ctx.PathParam("*")
diff --git a/routers/api/v1/repo/download.go b/routers/api/v1/repo/download.go
index 3620c1465f..eb967772ed 100644
--- a/routers/api/v1/repo/download.go
+++ b/routers/api/v1/repo/download.go
@@ -28,13 +28,12 @@ func DownloadArchive(ctx *context.APIContext) {
}
if ctx.Repo.GitRepo == nil {
- gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
+ var err error
+ ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return
}
- ctx.Repo.GitRepo = gitRepo
- defer gitRepo.Close()
}
r, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*"), tp)
diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go
index 959a4b952a..6591b9a752 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"
)
@@ -287,13 +287,12 @@ func GetArchive(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
if ctx.Repo.GitRepo == nil {
- gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
+ var err error
+ ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return
}
- ctx.Repo.GitRepo = gitRepo
- defer gitRepo.Close()
}
archiveDownload(ctx)
@@ -736,12 +735,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 +886,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/git_hook.go b/routers/api/v1/repo/git_hook.go
index 0887a90096..9b66b69068 100644
--- a/routers/api/v1/repo/git_hook.go
+++ b/routers/api/v1/repo/git_hook.go
@@ -79,7 +79,7 @@ func GetGitHook(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- hookID := ctx.PathParam(":id")
+ hookID := ctx.PathParam("id")
hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil {
if err == git.ErrNotValidHook {
@@ -126,7 +126,7 @@ func EditGitHook(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.EditGitHookOption)
- hookID := ctx.PathParam(":id")
+ hookID := ctx.PathParam("id")
hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil {
if err == git.ErrNotValidHook {
@@ -175,7 +175,7 @@ func DeleteGitHook(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- hookID := ctx.PathParam(":id")
+ hookID := ctx.PathParam("id")
hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil {
if err == git.ErrNotValidHook {
diff --git a/routers/api/v1/repo/hook.go b/routers/api/v1/repo/hook.go
index 9ef57da1b9..03143c8f99 100644
--- a/routers/api/v1/repo/hook.go
+++ b/routers/api/v1/repo/hook.go
@@ -109,7 +109,7 @@ func GetHook(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
repo := ctx.Repo
- hookID := ctx.PathParamInt64(":id")
+ hookID := ctx.PathParamInt64("id")
hook, err := utils.GetRepoHook(ctx, repo.Repository.ID, hookID)
if err != nil {
return
@@ -168,7 +168,7 @@ func TestHook(ctx *context.APIContext) {
ref = r
}
- hookID := ctx.PathParamInt64(":id")
+ hookID := ctx.PathParamInt64("id")
hook, err := utils.GetRepoHook(ctx, ctx.Repo.Repository.ID, hookID)
if err != nil {
return
@@ -263,7 +263,7 @@ func EditHook(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.EditHookOption)
- hookID := ctx.PathParamInt64(":id")
+ hookID := ctx.PathParamInt64("id")
utils.EditRepoHook(ctx, form, hookID)
}
@@ -296,7 +296,7 @@ func DeleteHook(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
- if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":id")); err != nil {
+ if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id")); err != nil {
if webhook.IsErrWebhookNotExist(err) {
ctx.NotFound()
} else {
diff --git a/routers/api/v1/repo/hook_test.go b/routers/api/v1/repo/hook_test.go
index c2f3a972ef..c659a16f54 100644
--- a/routers/api/v1/repo/hook_test.go
+++ b/routers/api/v1/repo/hook_test.go
@@ -18,7 +18,7 @@ func TestTestHook(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/wiki/_pages")
- ctx.SetPathParam(":id", "1")
+ ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index cbe709c030..86dbcee5f7 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -613,7 +613,7 @@ func GetIssue(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@@ -733,7 +733,7 @@ func CreateIssue(ctx *context.APIContext) {
}
if form.Closed {
- if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", true); err != nil {
+ if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
if issues_model.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
return
@@ -793,7 +793,7 @@ func EditIssue(ctx *context.APIContext) {
// "$ref": "#/responses/error"
form := web.GetForm(ctx).(*api.EditIssueOption)
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@@ -912,27 +912,11 @@ func EditIssue(ctx *context.APIContext) {
}
}
- var isClosed bool
- switch state := api.StateType(*form.State); state {
- case api.StateOpen:
- isClosed = false
- case api.StateClosed:
- isClosed = true
- default:
- ctx.Error(http.StatusPreconditionFailed, "UnknownIssueStateError", fmt.Sprintf("unknown state: %s", state))
+ state := api.StateType(*form.State)
+ closeOrReopenIssue(ctx, issue, state)
+ if ctx.Written() {
return
}
-
- if issue.IsClosed != isClosed {
- if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed); err != nil {
- if issues_model.IsErrDependenciesLeft(err) {
- ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
- return
- }
- ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
- return
- }
- }
}
// Refetch from database to assign some automatic values
@@ -976,7 +960,7 @@ func DeleteIssue(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound(err)
@@ -1032,7 +1016,7 @@ func UpdateIssueDeadline(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.EditDeadlineOption)
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@@ -1055,3 +1039,26 @@ func UpdateIssueDeadline(ctx *context.APIContext) {
ctx.JSON(http.StatusCreated, api.IssueDeadline{Deadline: deadlineUnix.AsTimePtr()})
}
+
+func closeOrReopenIssue(ctx *context.APIContext, issue *issues_model.Issue, state api.StateType) {
+ if state != api.StateOpen && state != api.StateClosed {
+ ctx.Error(http.StatusPreconditionFailed, "UnknownIssueStateError", fmt.Sprintf("unknown state: %s", state))
+ return
+ }
+
+ if state == api.StateClosed && !issue.IsClosed {
+ if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
+ if issues_model.IsErrDependenciesLeft(err) {
+ ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue or pull request because it still has open dependencies")
+ return
+ }
+ ctx.Error(http.StatusInternalServerError, "CloseIssue", err)
+ return
+ }
+ } else if state == api.StateOpen && issue.IsClosed {
+ if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
+ ctx.Error(http.StatusInternalServerError, "ReopenIssue", err)
+ return
+ }
+ }
+}
diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go
index f9b5aa816b..96a61a527e 100644
--- a/routers/api/v1/repo/issue_comment.go
+++ b/routers/api/v1/repo/issue_comment.go
@@ -68,7 +68,7 @@ func ListIssueComments(ctx *context.APIContext) {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return
}
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
return
@@ -172,7 +172,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return
}
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
return
@@ -380,7 +380,7 @@ func CreateIssueComment(ctx *context.APIContext) {
// "$ref": "#/responses/repoArchivedError"
form := web.GetForm(ctx).(*api.CreateIssueCommentOption)
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
return
@@ -445,7 +445,7 @@ func GetIssueComment(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id"))
+ comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err)
@@ -579,7 +579,7 @@ func EditIssueCommentDeprecated(ctx *context.APIContext) {
}
func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {
- comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id"))
+ comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err)
@@ -696,7 +696,7 @@ func DeleteIssueCommentDeprecated(ctx *context.APIContext) {
}
func deleteIssueComment(ctx *context.APIContext) {
- comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id"))
+ comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err)
diff --git a/routers/api/v1/repo/issue_dependency.go b/routers/api/v1/repo/issue_dependency.go
index ae7502c661..19dcf999b8 100644
--- a/routers/api/v1/repo/issue_dependency.go
+++ b/routers/api/v1/repo/issue_dependency.go
@@ -61,7 +61,7 @@ func GetIssueDependencies(ctx *context.APIContext) {
return
}
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound("IsErrIssueNotExist", err)
@@ -499,7 +499,7 @@ func RemoveIssueBlocking(ctx *context.APIContext) {
}
func getParamsIssue(ctx *context.APIContext) *issues_model.Issue {
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound("IsErrIssueNotExist", err)
diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go
index cc517619e9..ee1a842bc6 100644
--- a/routers/api/v1/repo/issue_label.go
+++ b/routers/api/v1/repo/issue_label.go
@@ -47,7 +47,7 @@ func ListIssueLabels(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@@ -163,7 +163,7 @@ func DeleteIssueLabel(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@@ -178,7 +178,7 @@ func DeleteIssueLabel(ctx *context.APIContext) {
return
}
- label, err := issues_model.GetLabelByID(ctx, ctx.PathParamInt64(":id"))
+ label, err := issues_model.GetLabelByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrLabelNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
@@ -285,7 +285,7 @@ func ClearIssueLabels(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@@ -309,7 +309,7 @@ func ClearIssueLabels(ctx *context.APIContext) {
}
func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) (*issues_model.Issue, []*issues_model.Label, error) {
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@@ -335,6 +335,9 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption)
labelIDs = append(labelIDs, int64(rv.Float()))
case reflect.String:
labelNames = append(labelNames, rv.String())
+ default:
+ ctx.Error(http.StatusBadRequest, "InvalidLabel", "a label must be an integer or a string")
+ return nil, nil, fmt.Errorf("invalid label")
}
}
if len(labelIDs) > 0 && len(labelNames) > 0 {
@@ -342,11 +345,20 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption)
return nil, nil, fmt.Errorf("invalid labels")
}
if len(labelNames) > 0 {
- labelIDs, err = issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, labelNames)
+ repoLabelIDs, err := issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, labelNames)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetLabelIDsInRepoByNames", err)
return nil, nil, err
}
+ labelIDs = append(labelIDs, repoLabelIDs...)
+ if ctx.Repo.Owner.IsOrganization() {
+ orgLabelIDs, err := issues_model.GetLabelIDsInOrgByNames(ctx, ctx.Repo.Owner.ID, labelNames)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetLabelIDsInOrgByNames", err)
+ return nil, nil, err
+ }
+ labelIDs = append(labelIDs, orgLabelIDs...)
+ }
}
labels, err := issues_model.GetLabelsByIDs(ctx, labelIDs, "id", "repo_id", "org_id", "name", "exclusive")
diff --git a/routers/api/v1/repo/issue_pin.go b/routers/api/v1/repo/issue_pin.go
index 0ef9033291..388d4a3e99 100644
--- a/routers/api/v1/repo/issue_pin.go
+++ b/routers/api/v1/repo/issue_pin.go
@@ -41,7 +41,7 @@ func PinIssue(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@@ -98,7 +98,7 @@ func UnpinIssue(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@@ -159,7 +159,7 @@ func MoveIssuePin(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@@ -169,7 +169,7 @@ func MoveIssuePin(ctx *context.APIContext) {
return
}
- err = issue.MovePin(ctx, int(ctx.PathParamInt64(":position")))
+ err = issue.MovePin(ctx, int(ctx.PathParamInt64("position")))
if err != nil {
ctx.Error(http.StatusInternalServerError, "MovePin", err)
return
diff --git a/routers/api/v1/repo/issue_reaction.go b/routers/api/v1/repo/issue_reaction.go
index 8d43cd518b..ead86a717f 100644
--- a/routers/api/v1/repo/issue_reaction.go
+++ b/routers/api/v1/repo/issue_reaction.go
@@ -51,7 +51,7 @@ func GetIssueCommentReactions(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id"))
+ comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err)
@@ -188,7 +188,7 @@ func DeleteIssueCommentReaction(ctx *context.APIContext) {
}
func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
- comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id"))
+ comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err)
@@ -295,7 +295,7 @@ func GetIssueReactions(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@@ -419,7 +419,7 @@ func DeleteIssueReaction(ctx *context.APIContext) {
}
func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
- issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
diff --git a/routers/api/v1/repo/issue_stopwatch.go b/routers/api/v1/repo/issue_stopwatch.go
index 4605ae2110..e7fba6d0ed 100644
--- a/routers/api/v1/repo/issue_stopwatch.go
+++ b/routers/api/v1/repo/issue_stopwatch.go
@@ -161,7 +161,7 @@ func DeleteIssueStopwatch(ctx *context.APIContext) {
}
func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*issues_model.Issue, error) {
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
diff --git a/routers/api/v1/repo/issue_subscription.go b/routers/api/v1/repo/issue_subscription.go
index e51baad0b6..4fb80b1ec4 100644
--- a/routers/api/v1/repo/issue_subscription.go
+++ b/routers/api/v1/repo/issue_subscription.go
@@ -104,7 +104,7 @@ func DelIssueSubscription(ctx *context.APIContext) {
}
func setIssueSubscription(ctx *context.APIContext, watch bool) {
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@@ -115,7 +115,7 @@ func setIssueSubscription(ctx *context.APIContext, watch bool) {
return
}
- user, err := user_model.GetUserByName(ctx, ctx.PathParam(":user"))
+ user, err := user_model.GetUserByName(ctx, ctx.PathParam("user"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.NotFound()
@@ -185,7 +185,7 @@ func CheckIssueSubscription(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
@@ -251,7 +251,7 @@ func GetIssueSubscribers(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound()
diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go
index 8d5e9fdad4..57961b0660 100644
--- a/routers/api/v1/repo/issue_tracked_time.go
+++ b/routers/api/v1/repo/issue_tracked_time.go
@@ -75,7 +75,7 @@ func ListTrackedTimes(ctx *context.APIContext) {
ctx.NotFound("Timetracker is disabled")
return
}
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound(err)
@@ -181,7 +181,7 @@ func AddTime(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.AddTimeOption)
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound(err)
@@ -264,7 +264,7 @@ func ResetIssueTime(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound(err)
@@ -337,7 +337,7 @@ func DeleteTime(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound(err)
@@ -356,7 +356,7 @@ func DeleteTime(ctx *context.APIContext) {
return
}
- time, err := issues_model.GetTrackedTimeByID(ctx, ctx.PathParamInt64(":id"))
+ time, err := issues_model.GetTrackedTimeByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if db.IsErrNotExist(err) {
ctx.NotFound(err)
@@ -422,7 +422,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) {
ctx.Error(http.StatusBadRequest, "", "time tracking disabled")
return
}
- user, err := user_model.GetUserByName(ctx, ctx.PathParam(":timetrackingusername"))
+ user, err := user_model.GetUserByName(ctx, ctx.PathParam("timetrackingusername"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.NotFound(err)
diff --git a/routers/api/v1/repo/key.go b/routers/api/v1/repo/key.go
index e5115980eb..23cc922628 100644
--- a/routers/api/v1/repo/key.go
+++ b/routers/api/v1/repo/key.go
@@ -143,7 +143,7 @@ func GetDeployKey(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- key, err := asymkey_model.GetDeployKeyByID(ctx, ctx.PathParamInt64(":id"))
+ key, err := asymkey_model.GetDeployKeyByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if asymkey_model.IsErrDeployKeyNotExist(err) {
ctx.NotFound()
@@ -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/label.go b/routers/api/v1/repo/label.go
index c2c43db6a4..1ece2521e0 100644
--- a/routers/api/v1/repo/label.go
+++ b/routers/api/v1/repo/label.go
@@ -99,7 +99,7 @@ func GetLabel(ctx *context.APIContext) {
l *issues_model.Label
err error
)
- strID := ctx.PathParam(":id")
+ strID := ctx.PathParam("id")
if intID, err2 := strconv.ParseInt(strID, 10, 64); err2 != nil {
l, err = issues_model.GetLabelInRepoByName(ctx, ctx.Repo.Repository.ID, strID)
} else {
@@ -212,7 +212,7 @@ func EditLabel(ctx *context.APIContext) {
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditLabelOption)
- l, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":id"))
+ l, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrRepoLabelNotExist(err) {
ctx.NotFound()
@@ -276,7 +276,7 @@ func DeleteLabel(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- if err := issues_model.DeleteLabel(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":id")); err != nil {
+ if err := issues_model.DeleteLabel(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id")); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteLabel", err)
return
}
diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go
index dcbd8f3dd5..452825c0a3 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/milestone.go b/routers/api/v1/repo/milestone.go
index 78907c85a5..8d7516491e 100644
--- a/routers/api/v1/repo/milestone.go
+++ b/routers/api/v1/repo/milestone.go
@@ -280,7 +280,7 @@ func DeleteMilestone(ctx *context.APIContext) {
// getMilestoneByIDOrName get milestone by ID and if not available by name
func getMilestoneByIDOrName(ctx *context.APIContext) *issues_model.Milestone {
- mile := ctx.PathParam(":id")
+ mile := ctx.PathParam("id")
mileID, _ := strconv.ParseInt(mile, 0, 64)
if mileID != 0 {
diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go
index 310b82881e..c911f6830c 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"
)
@@ -224,7 +223,7 @@ func GetPushMirrorByName(ctx *context.APIContext) {
return
}
- mirrorName := ctx.PathParam(":name")
+ mirrorName := ctx.PathParam("name")
// Get push mirror of a specific repo by remoteName
pushMirror, exist, err := db.Get[repo_model.PushMirror](ctx, repo_model.PushMirrorOptions{
RepoID: ctx.Repo.Repository.ID,
@@ -325,7 +324,7 @@ func DeletePushMirrorByRemoteName(ctx *context.APIContext) {
return
}
- remoteName := ctx.PathParam(":name")
+ remoteName := ctx.PathParam("name")
// Delete push mirror on repo by name.
err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{RepoID: ctx.Repo.Repository.ID, RemoteName: remoteName})
if err != nil {
@@ -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/notes.go b/routers/api/v1/repo/notes.go
index 8689d25e15..8fec844cc4 100644
--- a/routers/api/v1/repo/notes.go
+++ b/routers/api/v1/repo/notes.go
@@ -52,7 +52,7 @@ func GetNote(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- sha := ctx.PathParam(":sha")
+ sha := ctx.PathParam("sha")
if !git.IsValidRefPattern(sha) {
ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
return
diff --git a/routers/api/v1/repo/patch.go b/routers/api/v1/repo/patch.go
index 0e0601b7d9..5e24dcf891 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 5c136d8d96..d212f7ff3e 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"
@@ -180,7 +179,7 @@ func GetPullRequest(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound()
@@ -265,7 +264,7 @@ func GetPullRequestByBaseHead(ctx *context.APIContext) {
headBranch = head
}
- pr, err := issues_model.GetPullRequestByBaseHeadInfo(ctx, ctx.Repo.Repository.ID, headRepoID, ctx.PathParam(":base"), headBranch)
+ pr, err := issues_model.GetPullRequestByBaseHeadInfo(ctx, ctx.Repo.Repository.ID, headRepoID, ctx.PathParam("base"), headBranch)
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound()
@@ -325,7 +324,7 @@ func DownloadPullDiffOrPatch(ctx *context.APIContext) {
// "$ref": "#/responses/string"
// "404":
// "$ref": "#/responses/notFound"
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound()
@@ -335,7 +334,7 @@ func DownloadPullDiffOrPatch(ctx *context.APIContext) {
return
}
var patch bool
- if ctx.PathParam(":diffType") == "diff" {
+ if ctx.PathParam("diffType") == "diff" {
patch = false
} else {
patch = true
@@ -604,7 +603,7 @@ func EditPullRequest(ctx *context.APIContext) {
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditPullRequestOption)
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound()
@@ -729,27 +728,11 @@ func EditPullRequest(ctx *context.APIContext) {
return
}
- var isClosed bool
- switch state := api.StateType(*form.State); state {
- case api.StateOpen:
- isClosed = false
- case api.StateClosed:
- isClosed = true
- default:
- ctx.Error(http.StatusPreconditionFailed, "UnknownPRStateError", fmt.Sprintf("unknown state: %s", state))
+ state := api.StateType(*form.State)
+ closeOrReopenIssue(ctx, issue, state)
+ if ctx.Written() {
return
}
-
- if issue.IsClosed != isClosed {
- if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed); err != nil {
- if issues_model.IsErrDependenciesLeft(err) {
- ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
- return
- }
- ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
- return
- }
- }
}
// change pull target branch
@@ -765,7 +748,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
}
@@ -832,7 +815,7 @@ func IsPullRequestMerged(ctx *context.APIContext) {
// "404":
// description: pull request has not been merged
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound()
@@ -890,7 +873,7 @@ func MergePullRequest(ctx *context.APIContext) {
form := web.GetForm(ctx).(*forms.MergePullRequestForm)
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound("GetPullRequestByIndex", err)
@@ -941,7 +924,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)
@@ -954,7 +937,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
}
@@ -1004,20 +987,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)
@@ -1261,7 +1244,7 @@ func UpdatePullRequest(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound()
@@ -1312,10 +1295,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
}
@@ -1360,7 +1343,7 @@ func CancelScheduledAutoMerge(ctx *context.APIContext) {
// "423":
// "$ref": "#/responses/repoArchivedError"
- pullIndex := ctx.PathParamInt64(":index")
+ pullIndex := ctx.PathParamInt64("index")
pull, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, pullIndex)
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
@@ -1446,7 +1429,7 @@ func GetPullRequestCommits(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound()
@@ -1569,7 +1552,7 @@ func GetPullRequestFiles(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound()
diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go
index def860eee8..6d7a326370 100644
--- a/routers/api/v1/repo/pull_review.go
+++ b/routers/api/v1/repo/pull_review.go
@@ -61,7 +61,7 @@ func ListPullReviews(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound("GetPullRequestByIndex", err)
@@ -306,7 +306,7 @@ func CreatePullReview(ctx *context.APIContext) {
// "$ref": "#/responses/validationError"
opts := web.GetForm(ctx).(*api.CreatePullReviewOptions)
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound("GetPullRequestByIndex", err)
@@ -533,7 +533,7 @@ func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest
// prepareSingleReview return review, related pull and false or nil, nil and true if an error happen
func prepareSingleReview(ctx *context.APIContext) (*issues_model.Review, *issues_model.PullRequest, bool) {
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound("GetPullRequestByIndex", err)
@@ -543,7 +543,7 @@ func prepareSingleReview(ctx *context.APIContext) (*issues_model.Review, *issues
return nil, nil, true
}
- review, err := issues_model.GetReviewByID(ctx, ctx.PathParamInt64(":id"))
+ review, err := issues_model.GetReviewByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrReviewNotExist(err) {
ctx.NotFound("GetReviewByID", err)
@@ -698,7 +698,7 @@ func parseReviewersByNames(ctx *context.APIContext, reviewerNames, teamReviewerN
}
func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions, isAdd bool) {
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound("GetPullRequestByIndex", err)
diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go
index 478bdbd797..076f00f1d1 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"
@@ -51,7 +50,7 @@ func GetRelease(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- id := ctx.PathParamInt64(":id")
+ id := ctx.PathParamInt64("id")
release, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
@@ -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))
@@ -320,7 +319,7 @@ func EditRelease(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.EditReleaseOption)
- id := ctx.PathParamInt64(":id")
+ id := ctx.PathParamInt64("id")
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
@@ -397,7 +396,7 @@ func DeleteRelease(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
- id := ctx.PathParamInt64(":id")
+ id := ctx.PathParamInt64("id")
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", 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_attachment.go b/routers/api/v1/repo/release_attachment.go
index ed6cc8e1ea..54ca1fc843 100644
--- a/routers/api/v1/repo/release_attachment.go
+++ b/routers/api/v1/repo/release_attachment.go
@@ -72,12 +72,12 @@ func GetReleaseAttachment(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- releaseID := ctx.PathParamInt64(":id")
+ releaseID := ctx.PathParamInt64("id")
if !checkReleaseMatchRepo(ctx, releaseID) {
return
}
- attachID := ctx.PathParamInt64(":attachment_id")
+ attachID := ctx.PathParamInt64("attachment_id")
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil {
if repo_model.IsErrAttachmentNotExist(err) {
@@ -126,7 +126,7 @@ func ListReleaseAttachments(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- releaseID := ctx.PathParamInt64(":id")
+ releaseID := ctx.PathParamInt64("id")
release, err := repo_model.GetReleaseByID(ctx, releaseID)
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
@@ -199,7 +199,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
}
// Check if release exists an load release
- releaseID := ctx.PathParamInt64(":id")
+ releaseID := ctx.PathParamInt64("id")
if !checkReleaseMatchRepo(ctx, releaseID) {
return
}
@@ -299,12 +299,12 @@ func EditReleaseAttachment(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.EditAttachmentOptions)
// Check if release exists an load release
- releaseID := ctx.PathParamInt64(":id")
+ releaseID := ctx.PathParamInt64("id")
if !checkReleaseMatchRepo(ctx, releaseID) {
return
}
- attachID := ctx.PathParamInt64(":attachment_id")
+ attachID := ctx.PathParamInt64("attachment_id")
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil {
if repo_model.IsErrAttachmentNotExist(err) {
@@ -372,12 +372,12 @@ func DeleteReleaseAttachment(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
// Check if release exists an load release
- releaseID := ctx.PathParamInt64(":id")
+ releaseID := ctx.PathParamInt64("id")
if !checkReleaseMatchRepo(ctx, releaseID) {
return
}
- attachID := ctx.PathParamInt64(":attachment_id")
+ attachID := ctx.PathParamInt64("attachment_id")
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil {
if repo_model.IsErrAttachmentNotExist(err) {
diff --git a/routers/api/v1/repo/release_tags.go b/routers/api/v1/repo/release_tags.go
index 6df47af8d9..7380c5231c 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
@@ -42,7 +41,7 @@ func GetReleaseByTag(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- tag := ctx.PathParam(":tag")
+ tag := ctx.PathParam("tag")
release, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tag)
if err != nil {
@@ -95,7 +94,7 @@ func DeleteReleaseByTag(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
- tag := ctx.PathParam(":tag")
+ tag := ctx.PathParam("tag")
release, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, tag)
if err != nil {
@@ -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/repo.go b/routers/api/v1/repo/repo.go
index 40990a28cb..a192e241b7 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -495,7 +495,7 @@ func CreateOrgRepo(ctx *context.APIContext) {
// "403":
// "$ref": "#/responses/forbidden"
opt := web.GetForm(ctx).(*api.CreateRepoOption)
- org, err := organization.GetOrgByName(ctx, ctx.PathParam(":org"))
+ org, err := organization.GetOrgByName(ctx, ctx.PathParam("org"))
if err != nil {
if organization.IsErrOrgNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
@@ -575,7 +575,7 @@ func GetByID(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- repo, err := repo_model.GetRepositoryByID(ctx, ctx.PathParamInt64(":id"))
+ repo, err := repo_model.GetRepositoryByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if repo_model.IsErrRepoNotExist(err) {
ctx.NotFound()
@@ -726,12 +726,11 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
if ctx.Repo.GitRepo == nil && !repo.IsEmpty {
var err error
- ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, repo)
+ ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, repo)
if err != nil {
ctx.Error(http.StatusInternalServerError, "Unable to OpenRepository", err)
return err
}
- defer ctx.Repo.GitRepo.Close()
}
// Default branch only updated if changed and exist or the repository is empty
diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go
index a72df78666..8447a8f1f2 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
}
@@ -358,7 +357,7 @@ func GetTagProtection(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
repo := ctx.Repo.Repository
- id := ctx.PathParamInt64(":id")
+ id := ctx.PathParamInt64("id")
pt, err := git_model.GetProtectedTagByID(ctx, id)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
@@ -522,7 +521,7 @@ func EditTagProtection(ctx *context.APIContext) {
repo := ctx.Repo.Repository
form := web.GetForm(ctx).(*api.EditTagProtectionOption)
- id := ctx.PathParamInt64(":id")
+ id := ctx.PathParamInt64("id")
pt, err := git_model.GetProtectedTagByID(ctx, id)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
@@ -617,7 +616,7 @@ func DeleteTagProtection(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
repo := ctx.Repo.Repository
- id := ctx.PathParamInt64(":id")
+ id := ctx.PathParamInt64("id")
pt, err := git_model.GetProtectedTagByID(ctx, id)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
diff --git a/routers/api/v1/repo/teams.go b/routers/api/v1/repo/teams.go
index 82ecaf3020..e5a2d5c320 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
@@ -221,7 +221,7 @@ func changeRepoTeam(ctx *context.APIContext, add bool) {
}
func getTeamByParam(ctx *context.APIContext) *organization.Team {
- team, err := organization.GetTeam(ctx, ctx.Repo.Owner.ID, ctx.PathParam(":team"))
+ team, err := organization.GetTeam(ctx, ctx.Repo.Owner.ID, ctx.PathParam("team"))
if err != nil {
if organization.IsErrTeamNotExist(err) {
ctx.Error(http.StatusNotFound, "TeamNotExit", err)
diff --git a/routers/api/v1/repo/topic.go b/routers/api/v1/repo/topic.go
index 6b9eedf6e0..a1a15e7f46 100644
--- a/routers/api/v1/repo/topic.go
+++ b/routers/api/v1/repo/topic.go
@@ -162,7 +162,7 @@ func AddTopic(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/invalidTopicsError"
- topicName := strings.TrimSpace(strings.ToLower(ctx.PathParam(":topic")))
+ topicName := strings.TrimSpace(strings.ToLower(ctx.PathParam("topic")))
if !repo_model.ValidateTopic(topicName) {
ctx.JSON(http.StatusUnprocessableEntity, map[string]any{
@@ -229,7 +229,7 @@ func DeleteTopic(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/invalidTopicsError"
- topicName := strings.TrimSpace(strings.ToLower(ctx.PathParam(":topic")))
+ topicName := strings.TrimSpace(strings.ToLower(ctx.PathParam("topic")))
if !repo_model.ValidateTopic(topicName) {
ctx.JSON(http.StatusUnprocessableEntity, map[string]any{
diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go
index 776b336761..b2090cac41 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"
@@ -101,14 +100,14 @@ func Transfer(ctx *context.APIContext) {
}
if ctx.Repo.GitRepo != nil {
- ctx.Repo.GitRepo.Close()
+ _ = ctx.Repo.GitRepo.Close()
ctx.Repo.GitRepo = nil
}
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/tree.go b/routers/api/v1/repo/tree.go
index efb247c19e..768e5d41c1 100644
--- a/routers/api/v1/repo/tree.go
+++ b/routers/api/v1/repo/tree.go
@@ -56,7 +56,7 @@ func GetTree(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- sha := ctx.PathParam(":sha")
+ sha := ctx.PathParam("sha")
if len(sha) == 0 {
ctx.Error(http.StatusBadRequest, "", "sha not provided")
return
diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go
index f9906ed250..352d8f48fc 100644
--- a/routers/api/v1/repo/wiki.go
+++ b/routers/api/v1/repo/wiki.go
@@ -136,7 +136,7 @@ func EditWikiPage(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.CreateWikiPageOptions)
- oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
+ oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName"))
newWikiName := wiki_service.UserTitleToWebPath("", form.Title)
if len(newWikiName) == 0 {
@@ -242,7 +242,7 @@ func DeleteWikiPage(ctx *context.APIContext) {
// "423":
// "$ref": "#/responses/repoArchivedError"
- wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
+ wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName"))
if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil {
if err.Error() == "file does not exist" {
@@ -370,7 +370,7 @@ func GetWikiPage(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
// get requested pagename
- pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
+ pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName"))
wikiPage := getWikiPage(ctx, pageName)
if !ctx.Written() {
@@ -420,7 +420,7 @@ func ListPageRevisions(ctx *context.APIContext) {
}
// get requested pagename
- pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw(":pageName"))
+ pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName"))
if len(pageName) == 0 {
pageName = "Home"
}
diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go
index 9583bb548c..bfbc2ba622 100644
--- a/routers/api/v1/user/app.go
+++ b/routers/api/v1/user/app.go
@@ -165,7 +165,7 @@ func DeleteAccessToken(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/error"
- token := ctx.PathParam(":id")
+ token := ctx.PathParam("id")
tokenID, _ := strconv.ParseInt(token, 0, 64)
if tokenID == 0 {
@@ -306,7 +306,7 @@ func DeleteOauth2Application(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
- appID := ctx.PathParamInt64(":id")
+ appID := ctx.PathParamInt64("id")
if err := auth_model.DeleteOAuth2Application(ctx, appID, ctx.Doer.ID); err != nil {
if auth_model.IsErrOAuthApplicationNotFound(err) {
ctx.NotFound()
@@ -338,7 +338,7 @@ func GetOauth2Application(ctx *context.APIContext) {
// "$ref": "#/responses/OAuth2Application"
// "404":
// "$ref": "#/responses/notFound"
- appID := ctx.PathParamInt64(":id")
+ appID := ctx.PathParamInt64("id")
app, err := auth_model.GetOAuth2ApplicationByID(ctx, appID)
if err != nil {
if auth_model.IsErrOauthClientIDInvalid(err) || auth_model.IsErrOAuthApplicationNotFound(err) {
@@ -382,7 +382,7 @@ func UpdateOauth2Application(ctx *context.APIContext) {
// "$ref": "#/responses/OAuth2Application"
// "404":
// "$ref": "#/responses/notFound"
- appID := ctx.PathParamInt64(":id")
+ appID := ctx.PathParamInt64("id")
data := web.GetForm(ctx).(*api.CreateOAuth2ApplicationOptions)
diff --git a/routers/api/v1/user/follower.go b/routers/api/v1/user/follower.go
index 6abb70de19..8f46808f9e 100644
--- a/routers/api/v1/user/follower.go
+++ b/routers/api/v1/user/follower.go
@@ -201,7 +201,7 @@ func CheckFollowing(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- target := GetUserByParamsName(ctx, ":target")
+ target := GetUserByPathParam(ctx, "target") // FIXME: it is not right to call this function, it should load the "target" directly
if ctx.Written() {
return
}
diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go
index ba5c0fdc45..ef667a1883 100644
--- a/routers/api/v1/user/gpg_key.go
+++ b/routers/api/v1/user/gpg_key.go
@@ -116,7 +116,7 @@ func GetGPGKey(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- key, err := asymkey_model.GetGPGKeyForUserByID(ctx, ctx.Doer.ID, ctx.PathParamInt64(":id"))
+ key, err := asymkey_model.GetGPGKeyForUserByID(ctx, ctx.Doer.ID, ctx.PathParamInt64("id"))
if err != nil {
if asymkey_model.IsErrGPGKeyNotExist(err) {
ctx.NotFound()
@@ -280,7 +280,7 @@ func DeleteGPGKey(ctx *context.APIContext) {
return
}
- if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.PathParamInt64(":id")); err != nil {
+ if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.PathParamInt64("id")); err != nil {
if asymkey_model.IsErrGPGKeyAccessDenied(err) {
ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
} else {
diff --git a/routers/api/v1/user/helper.go b/routers/api/v1/user/helper.go
index 23a526cd67..9a6f305700 100644
--- a/routers/api/v1/user/helper.go
+++ b/routers/api/v1/user/helper.go
@@ -10,8 +10,9 @@ import (
"code.gitea.io/gitea/services/context"
)
-// GetUserByParamsName get user by name
-func GetUserByParamsName(ctx *context.APIContext, name string) *user_model.User {
+// GetUserByPathParam get user by the path param name
+// it will redirect to the user's new name if the user's name has been changed
+func GetUserByPathParam(ctx *context.APIContext, name string) *user_model.User {
username := ctx.PathParam(name)
user, err := user_model.GetUserByName(ctx, username)
if err != nil {
@@ -29,7 +30,7 @@ func GetUserByParamsName(ctx *context.APIContext, name string) *user_model.User
return user
}
-// GetUserByParams returns user whose name is presented in URL (":username").
-func GetUserByParams(ctx *context.APIContext) *user_model.User {
- return GetUserByParamsName(ctx, ":username")
+// GetContextUserByPathParam returns user whose name is presented in URL (path param "username").
+func GetContextUserByPathParam(ctx *context.APIContext) *user_model.User {
+ return GetUserByPathParam(ctx, "username")
}
diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go
index e4278c2ec0..5a9125b4f3 100644
--- a/routers/api/v1/user/key.go
+++ b/routers/api/v1/user/key.go
@@ -179,7 +179,7 @@ func GetPublicKey(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- key, err := asymkey_model.GetPublicKeyByID(ctx, ctx.PathParamInt64(":id"))
+ key, err := asymkey_model.GetPublicKeyByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if asymkey_model.IsErrKeyNotExist(err) {
ctx.NotFound()
@@ -274,7 +274,7 @@ func DeletePublicKey(ctx *context.APIContext) {
return
}
- id := ctx.PathParamInt64(":id")
+ id := ctx.PathParamInt64("id")
externallyManaged, err := asymkey_model.PublicKeyIsExternallyManaged(ctx, id)
if err != nil {
if asymkey_model.IsErrKeyNotExist(err) {
diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go
index e668326861..43dabe1b60 100644
--- a/routers/api/v1/user/user.go
+++ b/routers/api/v1/user/user.go
@@ -121,7 +121,7 @@ func GetInfo(ctx *context.APIContext) {
if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
// fake ErrUserNotExist error message to not leak information about existence
- ctx.NotFound("GetUserByName", user_model.ErrUserNotExist{Name: ctx.PathParam(":username")})
+ ctx.NotFound("GetUserByName", user_model.ErrUserNotExist{Name: ctx.PathParam("username")})
return
}
ctx.JSON(http.StatusOK, convert.ToUser(ctx, ctx.ContextUser, ctx.Doer))
diff --git a/routers/common/errpage.go b/routers/common/errpage.go
index 402ca44c12..c0b16dbdde 100644
--- a/routers/common/errpage.go
+++ b/routers/common/errpage.go
@@ -8,7 +8,6 @@ import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -18,7 +17,7 @@ import (
"code.gitea.io/gitea/services/context"
)
-const tplStatus500 base.TplName = "status/500"
+const tplStatus500 templates.TplName = "status/500"
// RenderPanicErrorPage renders a 500 page, and it never panics
func RenderPanicErrorPage(w http.ResponseWriter, req *http.Request, err any) {
@@ -47,7 +46,7 @@ func RenderPanicErrorPage(w http.ResponseWriter, req *http.Request, err any) {
ctxData["ErrorMsg"] = "PANIC: " + combinedErr
}
- err = templates.HTMLRenderer().HTML(w, http.StatusInternalServerError, string(tplStatus500), ctxData, tmplCtx)
+ err = templates.HTMLRenderer().HTML(w, http.StatusInternalServerError, tplStatus500, ctxData, tmplCtx)
if err != nil {
log.Error("Error occurs again when rendering error page: %v", err)
w.WriteHeader(http.StatusInternalServerError)
diff --git a/routers/common/errpage_test.go b/routers/common/errpage_test.go
index 4fd63ba49e..dfea55f510 100644
--- a/routers/common/errpage_test.go
+++ b/routers/common/errpage_test.go
@@ -12,8 +12,8 @@ import (
"testing"
"code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/test"
- "code.gitea.io/gitea/modules/web/middleware"
"github.com/stretchr/testify/assert"
)
@@ -21,7 +21,7 @@ import (
func TestRenderPanicErrorPage(t *testing.T) {
w := httptest.NewRecorder()
req := &http.Request{URL: &url.URL{}}
- req = req.WithContext(middleware.WithContextData(context.Background()))
+ req = req.WithContext(reqctx.NewRequestContextForTest(context.Background()))
RenderPanicErrorPage(w, req, errors.New("fake panic error (for test only)"))
respContent := w.Body.String()
assert.Contains(t, respContent, `class="page-content status-page-500"`)
diff --git a/routers/common/middleware.go b/routers/common/middleware.go
index 51e42d87a0..12b0c67b01 100644
--- a/routers/common/middleware.go
+++ b/routers/common/middleware.go
@@ -4,16 +4,14 @@
package common
import (
- go_context "context"
"fmt"
"net/http"
"strings"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/httplib"
- "code.gitea.io/gitea/modules/process"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/modules/web/routing"
"code.gitea.io/gitea/services/context"
@@ -24,54 +22,12 @@ import (
// ProtocolMiddlewares returns HTTP protocol related middlewares, and it provides a global panic recovery
func ProtocolMiddlewares() (handlers []any) {
- // make sure chi uses EscapedPath(RawPath) as RoutePath, then "%2f" could be handled correctly
- handlers = append(handlers, func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
- ctx := chi.RouteContext(req.Context())
- if req.URL.RawPath == "" {
- ctx.RoutePath = req.URL.EscapedPath()
- } else {
- ctx.RoutePath = req.URL.RawPath
- }
- next.ServeHTTP(resp, req)
- })
- })
+ // the order is important
+ handlers = append(handlers, ChiRoutePathHandler()) // make sure chi has correct paths
+ handlers = append(handlers, RequestContextHandler()) // prepare the context and panic recovery
- // prepare the ContextData and panic recovery
- handlers = append(handlers, func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
- defer func() {
- if err := recover(); err != nil {
- RenderPanicErrorPage(resp, req, err) // it should never panic
- }
- }()
- req = req.WithContext(middleware.WithContextData(req.Context()))
- req = req.WithContext(go_context.WithValue(req.Context(), httplib.RequestContextKey, req))
- next.ServeHTTP(resp, req)
- })
- })
-
- // wrap the request and response, use the process context and add it to the process manager
- handlers = append(handlers, func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
- ctx, _, finished := process.GetManager().AddTypedContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI), process.RequestProcessType, true)
- defer finished()
- next.ServeHTTP(context.WrapResponseWriter(resp), req.WithContext(cache.WithCacheContext(ctx)))
- })
- })
-
- if setting.ReverseProxyLimit > 0 {
- opt := proxy.NewForwardedHeadersOptions().
- WithForwardLimit(setting.ReverseProxyLimit).
- ClearTrustedProxies()
- for _, n := range setting.ReverseProxyTrustedProxies {
- if !strings.Contains(n, "/") {
- opt.AddTrustedProxy(n)
- } else {
- opt.AddTrustedNetwork(n)
- }
- }
- handlers = append(handlers, proxy.ForwardedHeaders(opt))
+ if setting.ReverseProxyLimit > 0 && len(setting.ReverseProxyTrustedProxies) > 0 {
+ handlers = append(handlers, ForwardedHeadersHandler(setting.ReverseProxyLimit, setting.ReverseProxyTrustedProxies))
}
if setting.IsRouteLogEnabled() {
@@ -85,6 +41,59 @@ func ProtocolMiddlewares() (handlers []any) {
return handlers
}
+func RequestContextHandler() func(h http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ profDesc := fmt.Sprintf("%s: %s", req.Method, req.RequestURI)
+ ctx, finished := reqctx.NewRequestContext(req.Context(), profDesc)
+ defer finished()
+
+ defer func() {
+ if err := recover(); err != nil {
+ RenderPanicErrorPage(resp, req, err) // it should never panic
+ }
+ }()
+
+ ds := reqctx.GetRequestDataStore(ctx)
+ req = req.WithContext(cache.WithCacheContext(ctx))
+ ds.SetContextValue(httplib.RequestContextKey, req)
+ ds.AddCleanUp(func() {
+ if req.MultipartForm != nil {
+ _ = req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
+ }
+ })
+ next.ServeHTTP(context.WrapResponseWriter(resp), req)
+ })
+ }
+}
+
+func ChiRoutePathHandler() func(h http.Handler) http.Handler {
+ // make sure chi uses EscapedPath(RawPath) as RoutePath, then "%2f" could be handled correctly
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ ctx := chi.RouteContext(req.Context())
+ if req.URL.RawPath == "" {
+ ctx.RoutePath = req.URL.EscapedPath()
+ } else {
+ ctx.RoutePath = req.URL.RawPath
+ }
+ next.ServeHTTP(resp, req)
+ })
+ }
+}
+
+func ForwardedHeadersHandler(limit int, trustedProxies []string) func(h http.Handler) http.Handler {
+ opt := proxy.NewForwardedHeadersOptions().WithForwardLimit(limit).ClearTrustedProxies()
+ for _, n := range trustedProxies {
+ if !strings.Contains(n, "/") {
+ opt.AddTrustedProxy(n)
+ } else {
+ opt.AddTrustedNetwork(n)
+ }
+ }
+ return proxy.ForwardedHeaders(opt)
+}
+
func Sessioner() func(next http.Handler) http.Handler {
return session.Sessioner(session.Options{
Provider: setting.SessionConfig.Provider,
diff --git a/routers/init.go b/routers/init.go
index 2091f5967a..e7aa765bf0 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -133,7 +133,7 @@ func InitWebInstalled(ctx context.Context) {
highlight.NewContext()
external.RegisterRenderers()
- markup.Init(markup_service.ProcessorHelper())
+ markup.Init(markup_service.FormalRenderHelperFuncs())
if setting.EnableSQLite3 {
log.Info("SQLite3 support is enabled")
@@ -171,7 +171,7 @@ func InitWebInstalled(ctx context.Context) {
auth.Init()
mustInit(svg.Init)
- actions_service.Init()
+ mustInitCtx(ctx, actions_service.Init)
mustInit(repo_service.InitLicenseClassifier)
diff --git a/routers/install/install.go b/routers/install/install.go
index e420d36da5..8a1d57aa0b 100644
--- a/routers/install/install.go
+++ b/routers/install/install.go
@@ -21,11 +21,11 @@ import (
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password/hash"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
@@ -43,8 +43,8 @@ import (
const (
// tplInstall template for installation page
- tplInstall base.TplName = "install"
- tplPostInstall base.TplName = "post-install"
+ tplInstall templates.TplName = "install"
+ tplPostInstall templates.TplName = "post-install"
)
// getSupportedDbTypeNames returns a slice for supported database types and names. The slice is used to keep the order
@@ -62,15 +62,11 @@ func Contexter() func(next http.Handler) http.Handler {
envConfigKeys := setting.CollectEnvConfigKeys()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
- base, baseCleanUp := context.NewBaseContext(resp, req)
- defer baseCleanUp()
-
+ base := context.NewBaseContext(resp, req)
ctx := context.NewWebContext(base, rnd, session.GetSession(req))
- ctx.AppendContextValue(context.WebContextKey, ctx)
+ ctx.SetContextValue(context.WebContextKey, ctx)
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
- ctx.Data.MergeFrom(middleware.ContextData{
- "Context": ctx, // TODO: use "ctx" in template and remove this
- "locale": ctx.Locale,
+ ctx.Data.MergeFrom(reqctx.ContextData{
"Title": ctx.Locale.Tr("install.install"),
"PageIsInstall": true,
"DbTypeNames": dbTypeNames,
diff --git a/routers/private/default_branch.go b/routers/private/default_branch.go
index 8f6e9084df..c375d70dc6 100644
--- a/routers/private/default_branch.go
+++ b/routers/private/default_branch.go
@@ -16,9 +16,9 @@ import (
// SetDefaultBranch updates the default branch
func SetDefaultBranch(ctx *gitea_context.PrivateContext) {
- ownerName := ctx.PathParam(":owner")
- repoName := ctx.PathParam(":repo")
- branch := ctx.PathParam(":branch")
+ ownerName := ctx.PathParam("owner")
+ repoName := ctx.PathParam("repo")
+ branch := ctx.PathParam("branch")
ctx.Repo.Repository.DefaultBranch = branch
if err := gitrepo.SetDefaultBranch(ctx, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch); err != nil {
diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go
index 8d12b7a953..32c2828739 100644
--- a/routers/private/hook_post_receive.go
+++ b/routers/private/hook_post_receive.go
@@ -40,8 +40,8 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
// b) our update function will likely change the repository in the db so we will need to refresh it
// c) we don't always need the repo
- ownerName := ctx.PathParam(":owner")
- repoName := ctx.PathParam(":repo")
+ ownerName := ctx.PathParam("owner")
+ repoName := ctx.PathParam("repo")
// defer getting the repository at this point - as we should only retrieve it if we're going to call update
var (
diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go
index 73fe9b886c..eb7bb2b480 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/private/internal.go b/routers/private/internal.go
index 1fb72f13d9..a78c76f897 100644
--- a/routers/private/internal.go
+++ b/routers/private/internal.go
@@ -63,8 +63,8 @@ func Routes() *web.Router {
r.Post("/ssh/{id}/update/{repoid}", UpdatePublicKeyInRepo)
r.Post("/ssh/log", bind(private.SSHLogOption{}), SSHLog)
r.Post("/hook/pre-receive/{owner}/{repo}", RepoAssignment, bind(private.HookOptions{}), HookPreReceive)
- r.Post("/hook/post-receive/{owner}/{repo}", context.OverrideContext, bind(private.HookOptions{}), HookPostReceive)
- r.Post("/hook/proc-receive/{owner}/{repo}", context.OverrideContext, RepoAssignment, bind(private.HookOptions{}), HookProcReceive)
+ r.Post("/hook/post-receive/{owner}/{repo}", context.OverrideContext(), bind(private.HookOptions{}), HookPostReceive)
+ r.Post("/hook/proc-receive/{owner}/{repo}", context.OverrideContext(), RepoAssignment, bind(private.HookOptions{}), HookProcReceive)
r.Post("/hook/set-default-branch/{owner}/{repo}/{branch}", RepoAssignment, SetDefaultBranch)
r.Get("/serv/none/{keyid}", ServNoCommand)
r.Get("/serv/command/{keyid}/{owner}/{repo}", ServCommand)
@@ -88,7 +88,7 @@ func Routes() *web.Router {
// Fortunately, the LFS handlers are able to handle requests without a complete web context
common.AddOwnerRepoGitLFSRoutes(r, func(ctx *context.PrivateContext) {
webContext := &context.Context{Base: ctx.Base}
- ctx.AppendContextValue(context.WebContextKey, webContext)
+ ctx.SetContextValue(context.WebContextKey, webContext)
})
})
diff --git a/routers/private/internal_repo.go b/routers/private/internal_repo.go
index aad0a3fb1a..8a53e1ed23 100644
--- a/routers/private/internal_repo.go
+++ b/routers/private/internal_repo.go
@@ -4,7 +4,6 @@
package private
import (
- "context"
"fmt"
"net/http"
@@ -17,40 +16,29 @@ import (
// This file contains common functions relating to setting the Repository for the internal routes
-// RepoAssignment assigns the repository and gitrepository to the private context
-func RepoAssignment(ctx *gitea_context.PrivateContext) context.CancelFunc {
- ownerName := ctx.PathParam(":owner")
- repoName := ctx.PathParam(":repo")
+// RepoAssignment assigns the repository and git repository to the private context
+func RepoAssignment(ctx *gitea_context.PrivateContext) {
+ ownerName := ctx.PathParam("owner")
+ repoName := ctx.PathParam("repo")
repo := loadRepository(ctx, ownerName, repoName)
if ctx.Written() {
// Error handled in loadRepository
- return nil
+ return
}
- gitRepo, err := gitrepo.OpenRepository(ctx, repo)
+ gitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, repo)
if err != nil {
log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err),
})
- return nil
+ return
}
-
ctx.Repo = &gitea_context.Repository{
Repository: repo,
GitRepo: gitRepo,
}
-
- // We opened it, we should close it
- cancel := func() {
- // If it's been set to nil then assume someone else has closed it.
- if ctx.Repo.GitRepo != nil {
- ctx.Repo.GitRepo.Close()
- }
- }
-
- return cancel
}
func loadRepository(ctx *gitea_context.PrivateContext, ownerName, repoName string) *repo_model.Repository {
diff --git a/routers/private/key.go b/routers/private/key.go
index 063db76520..9fd0a16c07 100644
--- a/routers/private/key.go
+++ b/routers/private/key.go
@@ -14,8 +14,8 @@ import (
// UpdatePublicKeyInRepo update public key and deploy key updates
func UpdatePublicKeyInRepo(ctx *context.PrivateContext) {
- keyID := ctx.PathParamInt64(":id")
- repoID := ctx.PathParamInt64(":repoid")
+ keyID := ctx.PathParamInt64("id")
+ repoID := ctx.PathParamInt64("repoid")
if err := asymkey_model.UpdatePublicKeyUpdated(ctx, keyID); err != nil {
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: err.Error(),
diff --git a/routers/private/serv.go b/routers/private/serv.go
index 4dd7d06fb3..ecff3b7a53 100644
--- a/routers/private/serv.go
+++ b/routers/private/serv.go
@@ -25,7 +25,7 @@ import (
// ServNoCommand returns information about the provided keyid
func ServNoCommand(ctx *context.PrivateContext) {
- keyID := ctx.PathParamInt64(":keyid")
+ keyID := ctx.PathParamInt64("keyid")
if keyID <= 0 {
ctx.JSON(http.StatusBadRequest, private.Response{
UserMsg: fmt.Sprintf("Bad key id: %d", keyID),
@@ -77,9 +77,9 @@ func ServNoCommand(ctx *context.PrivateContext) {
// ServCommand returns information about the provided keyid
func ServCommand(ctx *context.PrivateContext) {
- keyID := ctx.PathParamInt64(":keyid")
- ownerName := ctx.PathParam(":owner")
- repoName := ctx.PathParam(":repo")
+ keyID := ctx.PathParamInt64("keyid")
+ ownerName := ctx.PathParam("owner")
+ repoName := ctx.PathParam("repo")
mode := perm.AccessMode(ctx.FormInt("mode"))
// Set the basic parts of the results to return
diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go
index 37c54b5362..3902a1efb1 100644
--- a/routers/web/admin/admin.go
+++ b/routers/web/admin/admin.go
@@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/updatechecker"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
@@ -31,14 +32,14 @@ import (
)
const (
- tplDashboard base.TplName = "admin/dashboard"
- tplSystemStatus base.TplName = "admin/system_status"
- tplSelfCheck base.TplName = "admin/self_check"
- tplCron base.TplName = "admin/cron"
- tplQueue base.TplName = "admin/queue"
- tplStacktrace base.TplName = "admin/stacktrace"
- tplQueueManage base.TplName = "admin/queue_manage"
- tplStats base.TplName = "admin/stats"
+ tplDashboard templates.TplName = "admin/dashboard"
+ tplSystemStatus templates.TplName = "admin/system_status"
+ tplSelfCheck templates.TplName = "admin/self_check"
+ tplCron templates.TplName = "admin/cron"
+ tplQueue templates.TplName = "admin/queue"
+ tplStacktrace templates.TplName = "admin/stacktrace"
+ tplQueueManage templates.TplName = "admin/queue_manage"
+ tplStats templates.TplName = "admin/stats"
)
var sysStatus struct {
diff --git a/routers/web/admin/applications.go b/routers/web/admin/applications.go
index 9b48f21eca..aec6349f21 100644
--- a/routers/web/admin/applications.go
+++ b/routers/web/admin/applications.go
@@ -9,15 +9,15 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
"code.gitea.io/gitea/services/context"
)
var (
- tplSettingsApplications base.TplName = "admin/applications/list"
- tplSettingsOauth2ApplicationEdit base.TplName = "admin/applications/oauth2_edit"
+ tplSettingsApplications templates.TplName = "admin/applications/list"
+ tplSettingsOauth2ApplicationEdit templates.TplName = "admin/applications/oauth2_edit"
)
func newOAuth2CommonHandlers() *user_setting.OAuth2CommonHandlers {
diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go
index 60e2b7c86f..249347e835 100644
--- a/routers/web/admin/auths.go
+++ b/routers/web/admin/auths.go
@@ -15,9 +15,9 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/auth/pam"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
auth_service "code.gitea.io/gitea/services/auth"
@@ -33,9 +33,9 @@ import (
)
const (
- tplAuths base.TplName = "admin/auth/list"
- tplAuthNew base.TplName = "admin/auth/new"
- tplAuthEdit base.TplName = "admin/auth/edit"
+ tplAuths templates.TplName = "admin/auth/list"
+ tplAuthNew templates.TplName = "admin/auth/new"
+ tplAuthEdit templates.TplName = "admin/auth/edit"
)
var (
@@ -337,7 +337,7 @@ func EditAuthSource(ctx *context.Context) {
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers
- source, err := auth.GetSourceByID(ctx, ctx.PathParamInt64(":authid"))
+ source, err := auth.GetSourceByID(ctx, ctx.PathParamInt64("authid"))
if err != nil {
ctx.ServerError("auth.GetSourceByID", err)
return
@@ -371,7 +371,7 @@ func EditAuthSourcePost(ctx *context.Context) {
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers
- source, err := auth.GetSourceByID(ctx, ctx.PathParamInt64(":authid"))
+ source, err := auth.GetSourceByID(ctx, ctx.PathParamInt64("authid"))
if err != nil {
ctx.ServerError("auth.GetSourceByID", err)
return
@@ -442,7 +442,7 @@ func EditAuthSourcePost(ctx *context.Context) {
// DeleteAuthSource response for deleting an auth source
func DeleteAuthSource(ctx *context.Context) {
- source, err := auth.GetSourceByID(ctx, ctx.PathParamInt64(":authid"))
+ source, err := auth.GetSourceByID(ctx, ctx.PathParamInt64("authid"))
if err != nil {
ctx.ServerError("auth.GetSourceByID", err)
return
@@ -454,7 +454,7 @@ func DeleteAuthSource(ctx *context.Context) {
} else {
ctx.Flash.Error(fmt.Sprintf("auth_service.DeleteSource: %v", err))
}
- ctx.JSONRedirect(setting.AppSubURL + "/-/admin/auths/" + url.PathEscape(ctx.PathParam(":authid")))
+ ctx.JSONRedirect(setting.AppSubURL + "/-/admin/auths/" + url.PathEscape(ctx.PathParam("authid")))
return
}
log.Trace("Authentication deleted by admin(%s): %d", ctx.Doer.Name, source.ID)
diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go
index d067250a5b..520f14e89f 100644
--- a/routers/web/admin/config.go
+++ b/routers/web/admin/config.go
@@ -11,13 +11,13 @@ import (
"strings"
system_model "code.gitea.io/gitea/models/system"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/setting/config"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/mailer"
@@ -26,8 +26,8 @@ import (
)
const (
- tplConfig base.TplName = "admin/config"
- tplConfigSettings base.TplName = "admin/config_settings"
+ tplConfig templates.TplName = "admin/config"
+ tplConfigSettings templates.TplName = "admin/config_settings"
)
// SendTestMail send test mail to confirm mail service is OK
diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go
index e9c97d8b8f..e925de8937 100644
--- a/routers/web/admin/emails.go
+++ b/routers/web/admin/emails.go
@@ -10,16 +10,16 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/user"
)
const (
- tplEmails base.TplName = "admin/emails/list"
+ tplEmails templates.TplName = "admin/emails/list"
)
// Emails show all emails
diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go
index 91ca6e3fa7..34dc0fc9b0 100644
--- a/routers/web/admin/hooks.go
+++ b/routers/web/admin/hooks.go
@@ -7,15 +7,15 @@ import (
"net/http"
"code.gitea.io/gitea/models/webhook"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
)
const (
// tplAdminHooks template path to render hook settings
- tplAdminHooks base.TplName = "admin/hooks"
+ tplAdminHooks templates.TplName = "admin/hooks"
)
// DefaultOrSystemWebhooks renders both admin default and system webhook list pages
diff --git a/routers/web/admin/notice.go b/routers/web/admin/notice.go
index 5f7432e629..21a8ab0d17 100644
--- a/routers/web/admin/notice.go
+++ b/routers/web/admin/notice.go
@@ -10,14 +10,14 @@ import (
"code.gitea.io/gitea/models/db"
system_model "code.gitea.io/gitea/models/system"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
)
const (
- tplNotices base.TplName = "admin/notice"
+ tplNotices templates.TplName = "admin/notice"
)
// Notices show notices for admin
diff --git a/routers/web/admin/orgs.go b/routers/web/admin/orgs.go
index cea28f8220..35e61efa17 100644
--- a/routers/web/admin/orgs.go
+++ b/routers/web/admin/orgs.go
@@ -7,15 +7,15 @@ package admin
import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/routers/web/explore"
"code.gitea.io/gitea/services/context"
)
const (
- tplOrgs base.TplName = "admin/org/list"
+ tplOrgs templates.TplName = "admin/org/list"
)
// Organizations show all the organizations
diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go
index 2b9edc622d..da345f2f89 100644
--- a/routers/web/admin/packages.go
+++ b/routers/web/admin/packages.go
@@ -10,16 +10,16 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
packages_cleanup_service "code.gitea.io/gitea/services/packages/cleanup"
)
const (
- tplPackagesList base.TplName = "admin/packages/list"
+ tplPackagesList templates.TplName = "admin/packages/list"
)
// Packages shows all packages
diff --git a/routers/web/admin/repos.go b/routers/web/admin/repos.go
index 75e5ee5d86..27d39dcf4b 100644
--- a/routers/web/admin/repos.go
+++ b/routers/web/admin/repos.go
@@ -12,9 +12,9 @@ import (
"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/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/explore"
"code.gitea.io/gitea/services/context"
@@ -22,8 +22,8 @@ import (
)
const (
- tplRepos base.TplName = "admin/repo/list"
- tplUnadoptedRepos base.TplName = "admin/repo/unadopted"
+ tplRepos templates.TplName = "admin/repo/list"
+ tplUnadoptedRepos templates.TplName = "admin/repo/unadopted"
)
// Repos show all the repositories
diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go
index a6b0b5c78b..fdd18b2f9d 100644
--- a/routers/web/admin/users.go
+++ b/routers/web/admin/users.go
@@ -11,17 +11,17 @@ 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"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/explore"
@@ -33,10 +33,10 @@ import (
)
const (
- tplUsers base.TplName = "admin/user/list"
- tplUserNew base.TplName = "admin/user/new"
- tplUserView base.TplName = "admin/user/view"
- tplUserEdit base.TplName = "admin/user/edit"
+ tplUsers templates.TplName = "admin/user/list"
+ tplUserNew templates.TplName = "admin/user/new"
+ tplUserView templates.TplName = "admin/user/view"
+ tplUserEdit templates.TplName = "admin/user/edit"
)
// UserSearchDefaultAdminSort is the default sort type for admin view
@@ -219,7 +219,7 @@ func NewUserPost(ctx *context.Context) {
}
func prepareUserInfo(ctx *context.Context) *user_model.User {
- u, err := user_model.GetUserByID(ctx, ctx.PathParamInt64(":userid"))
+ u, err := user_model.GetUserByID(ctx, ctx.PathParamInt64("userid"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.Redirect(setting.AppSubURL + "/-/admin/users")
@@ -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)
@@ -481,12 +481,12 @@ func EditUserPost(ctx *context.Context) {
}
ctx.Flash.Success(ctx.Tr("admin.users.update_profile_success"))
- ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam(":userid")))
+ ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam("userid")))
}
// DeleteUser response for deleting a user
func DeleteUser(ctx *context.Context) {
- u, err := user_model.GetUserByID(ctx, ctx.PathParamInt64(":userid"))
+ u, err := user_model.GetUserByID(ctx, ctx.PathParamInt64("userid"))
if err != nil {
ctx.ServerError("GetUserByID", err)
return
@@ -495,24 +495,24 @@ func DeleteUser(ctx *context.Context) {
// admin should not delete themself
if u.ID == ctx.Doer.ID {
ctx.Flash.Error(ctx.Tr("admin.users.cannot_delete_self"))
- ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam(":userid")))
+ ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam("userid")))
return
}
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):
+ ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam("userid")))
+ 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):
+ ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam("userid")))
+ 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):
+ ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam("userid")))
+ case user_model.IsErrDeleteLastAdminUser(err):
ctx.Flash.Error(ctx.Tr("auth.last_admin"))
- ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam(":userid")))
+ ctx.Redirect(setting.AppSubURL + "/-/admin/users/" + url.PathEscape(ctx.PathParam("userid")))
default:
ctx.ServerError("DeleteUser", err)
}
diff --git a/routers/web/auth/2fa.go b/routers/web/auth/2fa.go
index f93177bf96..fe363fe90a 100644
--- a/routers/web/auth/2fa.go
+++ b/routers/web/auth/2fa.go
@@ -9,8 +9,8 @@ import (
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
@@ -18,8 +18,8 @@ import (
)
var (
- tplTwofa base.TplName = "user/auth/twofa"
- tplTwofaScratch base.TplName = "user/auth/twofa_scratch"
+ tplTwofa templates.TplName = "user/auth/twofa"
+ tplTwofaScratch templates.TplName = "user/auth/twofa_scratch"
)
// TwoFactor shows the user a two-factor authentication page.
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index 3f16da3cdd..3fe1d5970e 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -15,13 +15,13 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/eventsource"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
@@ -38,10 +38,10 @@ import (
)
const (
- tplSignIn base.TplName = "user/auth/signin" // for sign in page
- tplSignUp base.TplName = "user/auth/signup" // for sign up page
- TplActivate base.TplName = "user/auth/activate" // for activate user
- TplActivatePrompt base.TplName = "user/auth/activate_prompt" // for showing a message for user activation
+ tplSignIn templates.TplName = "user/auth/signin" // for sign in page
+ tplSignUp templates.TplName = "user/auth/signup" // for sign up page
+ TplActivate templates.TplName = "user/auth/activate" // for activate user
+ TplActivatePrompt templates.TplName = "user/auth/activate_prompt" // for showing a message for user activation
)
// autoSignIn reads cookie and try to auto-login.
@@ -517,7 +517,7 @@ func SignUpPost(ctx *context.Context) {
// createAndHandleCreatedUser calls createUserInContext and
// then handleUserCreated.
-func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) bool {
+func createAndHandleCreatedUser(ctx *context.Context, tpl templates.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) bool {
if !createUserInContext(ctx, tpl, form, u, overwrites, gothUser, allowLink) {
return false
}
@@ -526,7 +526,7 @@ func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form any
// createUserInContext creates a user and handles errors within a given context.
// Optionally a template can be specified.
-func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) (ok bool) {
+func createUserInContext(ctx *context.Context, tpl templates.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) (ok bool) {
meta := &user_model.Meta{
InitialIP: ctx.RemoteAddr(),
InitialUserAgent: ctx.Req.UserAgent(),
@@ -689,7 +689,7 @@ func Activate(ctx *context.Context) {
}
// TODO: ctx.Doer/ctx.Data["SignedUser"] could be nil or not the same user as the one being activated
- user := user_model.VerifyUserActiveCode(ctx, code)
+ user := user_model.VerifyUserTimeLimitCode(ctx, &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeActivateAccount}, code)
if user == nil { // if code is wrong
renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.invalid_code"))
return
@@ -734,7 +734,7 @@ func ActivatePost(ctx *context.Context) {
}
// TODO: ctx.Doer/ctx.Data["SignedUser"] could be nil or not the same user as the one being activated
- user := user_model.VerifyUserActiveCode(ctx, code)
+ user := user_model.VerifyUserTimeLimitCode(ctx, &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeActivateAccount}, code)
if user == nil { // if code is wrong
renderActivationPromptMessage(ctx, ctx.Locale.Tr("auth.invalid_code"))
return
diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go
index 519431d92b..147d8d3802 100644
--- a/routers/web/auth/linkaccount.go
+++ b/routers/web/auth/linkaccount.go
@@ -11,9 +11,9 @@ import (
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
auth_service "code.gitea.io/gitea/services/auth"
@@ -25,7 +25,7 @@ import (
"github.com/markbates/goth"
)
-var tplLinkAccount base.TplName = "user/auth/link_account"
+var tplLinkAccount templates.TplName = "user/auth/link_account"
// LinkAccount shows the page where the user can decide to login or create a new account
func LinkAccount(ctx *context.Context) {
@@ -92,7 +92,7 @@ func LinkAccount(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplLinkAccount)
}
-func handleSignInError(ctx *context.Context, userName string, ptrForm any, tmpl base.TplName, invoker string, err error) {
+func handleSignInError(ctx *context.Context, userName string, ptrForm any, tmpl templates.TplName, invoker string, err error) {
if errors.Is(err, util.ErrNotExist) {
ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tmpl, ptrForm)
} else if errors.Is(err, util.ErrInvalidArgument) {
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index 75f94de0ed..7a9721cf56 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -15,11 +15,11 @@ 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/base"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web/middleware"
source_service "code.gitea.io/gitea/services/auth/source"
"code.gitea.io/gitea/services/auth/source/oauth2"
@@ -34,7 +34,7 @@ import (
// SignInOAuth handles the OAuth2 login buttons
func SignInOAuth(ctx *context.Context) {
- provider := ctx.PathParam(":provider")
+ provider := ctx.PathParam("provider")
authSource, err := auth.GetActiveOAuth2SourceByName(ctx, provider)
if err != nil {
@@ -73,7 +73,7 @@ func SignInOAuth(ctx *context.Context) {
// SignInOAuthCallback handles the callback from the given provider
func SignInOAuthCallback(ctx *context.Context) {
- provider := ctx.PathParam(":provider")
+ provider := ctx.PathParam("provider")
if ctx.Req.FormValue("error") != "" {
var errorKeyValues []string
@@ -194,7 +194,7 @@ func SignInOAuthCallback(ctx *context.Context) {
u.IsAdmin = isAdmin.ValueOrDefault(false)
u.IsRestricted = isRestricted.ValueOrDefault(false)
- if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
+ if !createAndHandleCreatedUser(ctx, templates.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
// error already handled
return
}
diff --git a/routers/web/auth/oauth2_provider.go b/routers/web/auth/oauth2_provider.go
index 1aebc047bd..6262ad8a6d 100644
--- a/routers/web/auth/oauth2_provider.go
+++ b/routers/web/auth/oauth2_provider.go
@@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/context"
@@ -29,8 +30,8 @@ import (
)
const (
- tplGrantAccess base.TplName = "user/auth/grant"
- tplGrantError base.TplName = "user/auth/grant_error"
+ tplGrantAccess templates.TplName = "user/auth/grant"
+ tplGrantError templates.TplName = "user/auth/grant_error"
)
// TODO move error and responses to SDK or models
diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go
index 83268faacb..41d37ecb8b 100644
--- a/routers/web/auth/openid.go
+++ b/routers/web/auth/openid.go
@@ -10,9 +10,9 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/openid"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/auth"
@@ -21,9 +21,9 @@ import (
)
const (
- tplSignInOpenID base.TplName = "user/auth/signin_openid"
- tplConnectOID base.TplName = "user/auth/signup_openid_connect"
- tplSignUpOID base.TplName = "user/auth/signup_openid_register"
+ tplSignInOpenID templates.TplName = "user/auth/signin_openid"
+ tplConnectOID templates.TplName = "user/auth/signup_openid_connect"
+ tplSignUpOID templates.TplName = "user/auth/signup_openid_register"
)
// SignInOpenID render sign in page
diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go
index 334d864c6a..614e086f77 100644
--- a/routers/web/auth/password.go
+++ b/routers/web/auth/password.go
@@ -11,10 +11,10 @@ import (
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
@@ -26,9 +26,9 @@ import (
var (
// tplMustChangePassword template for updating a user's password
- tplMustChangePassword base.TplName = "user/auth/change_passwd"
- tplForgotPassword base.TplName = "user/auth/forgot_passwd"
- tplResetPassword base.TplName = "user/auth/reset_passwd"
+ tplMustChangePassword templates.TplName = "user/auth/change_passwd"
+ tplForgotPassword templates.TplName = "user/auth/forgot_passwd"
+ tplResetPassword templates.TplName = "user/auth/reset_passwd"
)
// ForgotPasswd render the forget password page
@@ -113,7 +113,7 @@ func commonResetPassword(ctx *context.Context) (*user_model.User, *auth.TwoFacto
}
// Fail early, don't frustrate the user
- u := user_model.VerifyUserActiveCode(ctx, code)
+ u := user_model.VerifyUserTimeLimitCode(ctx, &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeResetPassword}, code)
if u == nil {
ctx.Flash.Error(ctx.Tr("auth.invalid_code_forgot_password", fmt.Sprintf("%s/user/forgot_password", setting.AppSubURL)), true)
return nil, nil
diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go
index ba25d45070..69031adeaa 100644
--- a/routers/web/auth/webauthn.go
+++ b/routers/web/auth/webauthn.go
@@ -11,9 +11,9 @@ import (
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
wa "code.gitea.io/gitea/modules/auth/webauthn"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/externalaccount"
@@ -21,7 +21,7 @@ import (
"github.com/go-webauthn/webauthn/webauthn"
)
-var tplWebAuthn base.TplName = "user/auth/webauthn"
+var tplWebAuthn templates.TplName = "user/auth/webauthn"
// WebAuthn shows the WebAuthn login page
func WebAuthn(ctx *context.Context) {
diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go
index 0068c9fe88..1ea1398173 100644
--- a/routers/web/devtest/devtest.go
+++ b/routers/web/devtest/devtest.go
@@ -9,7 +9,10 @@ import (
"strings"
"time"
- "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/models/asymkey"
+ "code.gitea.io/gitea/models/db"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
)
@@ -42,16 +45,85 @@ func FetchActionTest(ctx *context.Context) {
ctx.JSONRedirect("")
}
-func Tmpl(ctx *context.Context) {
- now := time.Now()
- ctx.Data["TimeNow"] = now
- ctx.Data["TimePast5s"] = now.Add(-5 * time.Second)
- ctx.Data["TimeFuture5s"] = now.Add(5 * time.Second)
- ctx.Data["TimePast2m"] = now.Add(-2 * time.Minute)
- ctx.Data["TimeFuture2m"] = now.Add(2 * time.Minute)
- ctx.Data["TimePast1y"] = now.Add(-1 * 366 * 86400 * time.Second)
- ctx.Data["TimeFuture1y"] = now.Add(1 * 366 * 86400 * time.Second)
+func prepareMockData(ctx *context.Context) {
+ if ctx.Req.URL.Path == "/devtest/gitea-ui" {
+ now := time.Now()
+ ctx.Data["TimeNow"] = now
+ ctx.Data["TimePast5s"] = now.Add(-5 * time.Second)
+ ctx.Data["TimeFuture5s"] = now.Add(5 * time.Second)
+ ctx.Data["TimePast2m"] = now.Add(-2 * time.Minute)
+ ctx.Data["TimeFuture2m"] = now.Add(2 * time.Minute)
+ ctx.Data["TimePast1y"] = now.Add(-1 * 366 * 86400 * time.Second)
+ ctx.Data["TimeFuture1y"] = now.Add(1 * 366 * 86400 * time.Second)
+ }
+ if ctx.Req.URL.Path == "/devtest/commit-sign-badge" {
+ var commits []*asymkey.SignCommit
+ mockUsers, _ := db.Find[user_model.User](ctx, user_model.SearchUserOptions{ListOptions: db.ListOptions{PageSize: 1}})
+ mockUser := mockUsers[0]
+ commits = append(commits, &asymkey.SignCommit{
+ Verification: &asymkey.CommitVerification{},
+ UserCommit: &user_model.UserCommit{
+ Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
+ },
+ })
+ commits = append(commits, &asymkey.SignCommit{
+ Verification: &asymkey.CommitVerification{
+ Verified: true,
+ Reason: "name / key-id",
+ SigningUser: mockUser,
+ SigningKey: &asymkey.GPGKey{KeyID: "12345678"},
+ TrustStatus: "trusted",
+ },
+ UserCommit: &user_model.UserCommit{
+ User: mockUser,
+ Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
+ },
+ })
+ commits = append(commits, &asymkey.SignCommit{
+ Verification: &asymkey.CommitVerification{
+ Verified: true,
+ Reason: "name / key-id",
+ SigningUser: mockUser,
+ SigningSSHKey: &asymkey.PublicKey{Fingerprint: "aa:bb:cc:dd:ee"},
+ TrustStatus: "untrusted",
+ },
+ UserCommit: &user_model.UserCommit{
+ User: mockUser,
+ Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
+ },
+ })
+ commits = append(commits, &asymkey.SignCommit{
+ Verification: &asymkey.CommitVerification{
+ Verified: true,
+ Reason: "name / key-id",
+ SigningUser: mockUser,
+ SigningSSHKey: &asymkey.PublicKey{Fingerprint: "aa:bb:cc:dd:ee"},
+ TrustStatus: "other(unmatch)",
+ },
+ UserCommit: &user_model.UserCommit{
+ User: mockUser,
+ Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
+ },
+ })
+ commits = append(commits, &asymkey.SignCommit{
+ Verification: &asymkey.CommitVerification{
+ Warning: true,
+ Reason: "gpg.error",
+ SigningEmail: "test@example.com",
+ },
+ UserCommit: &user_model.UserCommit{
+ User: mockUser,
+ Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
+ },
+ })
+
+ ctx.Data["MockCommits"] = commits
+ }
+}
+
+func Tmpl(ctx *context.Context) {
+ prepareMockData(ctx)
if ctx.Req.Method == "POST" {
_ = ctx.Req.ParseForm()
ctx.Flash.Info("form: "+ctx.Req.Method+" "+ctx.Req.RequestURI+"
"+
@@ -61,6 +133,5 @@ func Tmpl(ctx *context.Context) {
)
time.Sleep(2 * time.Second)
}
-
- ctx.HTML(http.StatusOK, base.TplName("devtest"+path.Clean("/"+ctx.PathParam("sub"))))
+ ctx.HTML(http.StatusOK, templates.TplName("devtest"+path.Clean("/"+ctx.PathParam("sub"))))
}
diff --git a/routers/web/devtest/mock_actions.go b/routers/web/devtest/mock_actions.go
index 46e302d634..f29b8e4046 100644
--- a/routers/web/devtest/mock_actions.go
+++ b/routers/web/devtest/mock_actions.go
@@ -31,7 +31,11 @@ func generateMockStepsLog(logCur actions.LogCursor) (stepsLog []*actions.ViewSte
"##[endgroup]",
}
cur := logCur.Cursor // usually the cursor is the "file offset", but here we abuse it as "line number" to make the mock easier, intentionally
- for i := 0; i < util.Iif(logCur.Step == 0, 3, 1); i++ {
+ mockCount := util.Iif(logCur.Step == 0, 3, 1)
+ if logCur.Step == 1 && logCur.Cursor == 0 {
+ mockCount = 30 // for the first batch, return as many as possible to test the auto-expand and auto-scroll
+ }
+ for i := 0; i < mockCount; i++ {
logStr := mockedLogs[int(cur)%len(mockedLogs)]
cur++
logStr = strings.ReplaceAll(logStr, "{step}", fmt.Sprintf("%d", logCur.Step))
@@ -56,6 +60,21 @@ func MockActionsRunsJobs(ctx *context.Context) {
resp.State.Run.Status = actions_model.StatusRunning.String()
resp.State.Run.CanCancel = true
resp.State.Run.CanDeleteArtifact = true
+ resp.State.Run.WorkflowID = "workflow-id"
+ resp.State.Run.WorkflowLink = "./workflow-link"
+ resp.State.Run.Commit = actions.ViewCommit{
+ ShortSha: "ccccdddd",
+ Link: "./commit-link",
+ Pusher: actions.ViewUser{
+ DisplayName: "pusher user",
+ Link: "./pusher-link",
+ },
+ Branch: actions.ViewBranch{
+ Name: "commit-branch",
+ Link: "./branch-link",
+ IsDeleted: false,
+ },
+ }
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
Name: "artifact-a",
Size: 100 * 1024,
diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go
index 48f890332b..4df89253b4 100644
--- a/routers/web/explore/code.go
+++ b/routers/web/explore/code.go
@@ -8,15 +8,15 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/base"
code_indexer "code.gitea.io/gitea/modules/indexer/code"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
)
const (
// tplExploreCode explore code page template
- tplExploreCode base.TplName = "explore/code"
+ tplExploreCode templates.TplName = "explore/code"
)
// Code render explore code page
diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go
index 5b6f612e72..c421aea715 100644
--- a/routers/web/explore/repo.go
+++ b/routers/web/explore/repo.go
@@ -9,17 +9,17 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sitemap"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
)
const (
// tplExploreRepos explore repositories page template
- tplExploreRepos base.TplName = "explore/repos"
- relevantReposOnlyParam string = "only_show_relevant"
+ tplExploreRepos templates.TplName = "explore/repos"
+ relevantReposOnlyParam string = "only_show_relevant"
)
// RepoSearchOptions when calling search repositories
@@ -29,7 +29,7 @@ type RepoSearchOptions struct {
Restricted bool
PageSize int
OnlyShowRelevant bool
- TplName base.TplName
+ TplName templates.TplName
}
// RenderRepoSearch render repositories search page
diff --git a/routers/web/explore/user.go b/routers/web/explore/user.go
index c009982d42..ef103af8cf 100644
--- a/routers/web/explore/user.go
+++ b/routers/web/explore/user.go
@@ -9,20 +9,20 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sitemap"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
)
const (
// tplExploreUsers explore users page template
- tplExploreUsers base.TplName = "explore/users"
+ tplExploreUsers templates.TplName = "explore/users"
)
var nullByte = []byte{0x00}
@@ -32,7 +32,7 @@ func isKeywordValid(keyword string) bool {
}
// RenderUserSearch render user search page
-func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, tplName base.TplName) {
+func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, tplName templates.TplName) {
// Sitemap index for sitemap paths
opts.Page = int(ctx.PathParamInt64("idx"))
isSitemap := ctx.PathParam("idx") != ""
diff --git a/routers/web/feed/render.go b/routers/web/feed/render.go
index f975fc7cb2..462ebb97b5 100644
--- a/routers/web/feed/render.go
+++ b/routers/web/feed/render.go
@@ -9,7 +9,7 @@ import (
// RenderBranchFeed render format for branch or file
func RenderBranchFeed(ctx *context.Context) {
- _, _, showFeedType := GetFeedType(ctx.PathParam(":reponame"), ctx.Req)
+ _, _, showFeedType := GetFeedType(ctx.PathParam("reponame"), ctx.Req)
if ctx.Repo.TreePath == "" {
ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
} else {
diff --git a/routers/web/home.go b/routers/web/home.go
index d4be0931e8..9ad495d54f 100644
--- a/routers/web/home.go
+++ b/routers/web/home.go
@@ -11,12 +11,12 @@ import (
"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/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sitemap"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/web/auth"
"code.gitea.io/gitea/routers/web/user"
@@ -25,7 +25,7 @@ import (
const (
// tplHome home page template
- tplHome base.TplName = "home"
+ tplHome templates.TplName = "home"
)
// Home render home page
diff --git a/routers/web/misc/swagger.go b/routers/web/misc/swagger.go
index 5fddfa8885..1ca347551c 100644
--- a/routers/web/misc/swagger.go
+++ b/routers/web/misc/swagger.go
@@ -6,12 +6,12 @@ package misc
import (
"net/http"
- "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
)
// tplSwagger swagger page template
-const tplSwagger base.TplName = "swagger/ui"
+const tplSwagger templates.TplName = "swagger/ui"
// Swagger render swagger-ui page with v1 json
func Swagger(ctx *context.Context) {
diff --git a/routers/web/org/block.go b/routers/web/org/block.go
index d40458e250..aeb4bd51a8 100644
--- a/routers/web/org/block.go
+++ b/routers/web/org/block.go
@@ -6,13 +6,13 @@ package org
import (
"net/http"
- "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/templates"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
)
const (
- tplSettingsBlockedUsers base.TplName = "org/settings/blocked_users"
+ tplSettingsBlockedUsers templates.TplName = "org/settings/blocked_users"
)
func BlockedUsers(ctx *context.Context) {
diff --git a/routers/web/org/home.go b/routers/web/org/home.go
index f02c08ae76..bdc43acc30 100644
--- a/routers/web/org/home.go
+++ b/routers/web/org/home.go
@@ -13,29 +13,29 @@ import (
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/renderhelper"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
)
const (
- tplOrgHome base.TplName = "org/home"
+ tplOrgHome templates.TplName = "org/home"
)
// Home show organization home page
func Home(ctx *context.Context) {
- uname := ctx.PathParam(":username")
+ uname := ctx.PathParam("username")
if strings.HasSuffix(uname, ".keys") || strings.HasSuffix(uname, ".gpg") {
ctx.NotFound("", nil)
return
}
- ctx.SetPathParam(":org", uname)
+ ctx.SetPathParam("org", uname)
context.HandleOrgAssignment(ctx)
if ctx.Written() {
return
diff --git a/routers/web/org/members.go b/routers/web/org/members.go
index 7af087c4df..5a134caecb 100644
--- a/routers/web/org/members.go
+++ b/routers/web/org/members.go
@@ -9,9 +9,9 @@ import (
"code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
org_service "code.gitea.io/gitea/services/org"
@@ -19,7 +19,7 @@ import (
const (
// tplMembers template for organization members page
- tplMembers base.TplName = "org/member/members"
+ tplMembers templates.TplName = "org/member/members"
)
// Members render organization users page
@@ -90,7 +90,7 @@ func MembersAction(ctx *context.Context) {
org := ctx.Org.Organization
- switch ctx.PathParam(":action") {
+ switch ctx.PathParam("action") {
case "private":
if ctx.Doer.ID != member.ID && !ctx.Org.IsOwner {
ctx.Error(http.StatusNotFound)
@@ -131,7 +131,7 @@ func MembersAction(ctx *context.Context) {
}
if err != nil {
- log.Error("Action(%s): %v", ctx.PathParam(":action"), err)
+ log.Error("Action(%s): %v", ctx.PathParam("action"), err)
ctx.JSON(http.StatusOK, map[string]any{
"ok": false,
"err": err.Error(),
@@ -140,7 +140,7 @@ func MembersAction(ctx *context.Context) {
}
redirect := ctx.Org.OrgLink + "/members"
- if ctx.PathParam(":action") == "leave" {
+ if ctx.PathParam("action") == "leave" {
redirect = setting.AppSubURL + "/"
}
diff --git a/routers/web/org/org.go b/routers/web/org/org.go
index f94dd16eae..856a605764 100644
--- a/routers/web/org/org.go
+++ b/routers/web/org/org.go
@@ -11,9 +11,9 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
@@ -21,7 +21,7 @@ import (
const (
// tplCreateOrg template path for create organization
- tplCreateOrg base.TplName = "org/create"
+ tplCreateOrg templates.TplName = "org/create"
)
// Create render the page for create organization
diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go
index 3b9ec2a7b8..efcc8fadc8 100644
--- a/routers/web/org/projects.go
+++ b/routers/web/org/projects.go
@@ -15,7 +15,6 @@ import (
project_model "code.gitea.io/gitea/models/project"
attachment_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
@@ -29,9 +28,9 @@ import (
)
const (
- tplProjects base.TplName = "org/projects/list"
- tplProjectsNew base.TplName = "org/projects/new"
- tplProjectsView base.TplName = "org/projects/view"
+ tplProjects templates.TplName = "org/projects/list"
+ tplProjectsNew templates.TplName = "org/projects/new"
+ tplProjectsView templates.TplName = "org/projects/view"
)
// MustEnableProjects check if projects are enabled in settings
@@ -197,7 +196,7 @@ func NewProjectPost(ctx *context.Context) {
// ChangeProjectStatus updates the status of a project between "open" and "close"
func ChangeProjectStatus(ctx *context.Context) {
var toClose bool
- switch ctx.PathParam(":action") {
+ switch ctx.PathParam("action") {
case "open":
toClose = false
case "close":
@@ -206,7 +205,7 @@ func ChangeProjectStatus(ctx *context.Context) {
ctx.JSONRedirect(ctx.ContextUser.HomeLink() + "/-/projects")
return
}
- id := ctx.PathParamInt64(":id")
+ id := ctx.PathParamInt64("id")
if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, 0, id, toClose); err != nil {
ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
@@ -217,7 +216,7 @@ func ChangeProjectStatus(ctx *context.Context) {
// DeleteProject delete a project
func DeleteProject(ctx *context.Context) {
- p, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ p, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
@@ -246,7 +245,7 @@ func RenderEditProject(ctx *context.Context) {
shared_user.RenderUserHeader(ctx)
- p, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ p, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
@@ -270,7 +269,7 @@ func RenderEditProject(ctx *context.Context) {
// EditProjectPost response for editing a project
func EditProjectPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.CreateProjectForm)
- projectID := ctx.PathParamInt64(":id")
+ projectID := ctx.PathParamInt64("id")
ctx.Data["Title"] = ctx.Tr("repo.projects.edit")
ctx.Data["PageIsEditProjects"] = true
ctx.Data["PageIsViewProjects"] = true
@@ -319,7 +318,7 @@ func EditProjectPost(ctx *context.Context) {
// ViewProject renders the project with board view for a project
func ViewProject(ctx *context.Context) {
- project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
@@ -448,18 +447,18 @@ func DeleteProjectColumn(ctx *context.Context) {
return
}
- project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
}
- pb, err := project_model.GetColumn(ctx, ctx.PathParamInt64(":columnID"))
+ pb, err := project_model.GetColumn(ctx, ctx.PathParamInt64("columnID"))
if err != nil {
ctx.ServerError("GetProjectColumn", err)
return
}
- if pb.ProjectID != ctx.PathParamInt64(":id") {
+ if pb.ProjectID != ctx.PathParamInt64("id") {
ctx.JSON(http.StatusUnprocessableEntity, map[string]string{
"message": fmt.Sprintf("ProjectColumn[%d] is not in Project[%d] as expected", pb.ID, project.ID),
})
@@ -473,7 +472,7 @@ func DeleteProjectColumn(ctx *context.Context) {
return
}
- if err := project_model.DeleteColumnByID(ctx, ctx.PathParamInt64(":columnID")); err != nil {
+ if err := project_model.DeleteColumnByID(ctx, ctx.PathParamInt64("columnID")); err != nil {
ctx.ServerError("DeleteProjectColumnByID", err)
return
}
@@ -485,7 +484,7 @@ func DeleteProjectColumn(ctx *context.Context) {
func AddColumnToProjectPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.EditProjectColumnForm)
- project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
@@ -513,18 +512,18 @@ func CheckProjectColumnChangePermissions(ctx *context.Context) (*project_model.P
return nil, nil
}
- project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return nil, nil
}
- column, err := project_model.GetColumn(ctx, ctx.PathParamInt64(":columnID"))
+ column, err := project_model.GetColumn(ctx, ctx.PathParamInt64("columnID"))
if err != nil {
ctx.ServerError("GetProjectColumn", err)
return nil, nil
}
- if column.ProjectID != ctx.PathParamInt64(":id") {
+ if column.ProjectID != ctx.PathParamInt64("id") {
ctx.JSON(http.StatusUnprocessableEntity, map[string]string{
"message": fmt.Sprintf("ProjectColumn[%d] is not in Project[%d] as expected", column.ID, project.ID),
})
@@ -588,7 +587,7 @@ func MoveIssues(ctx *context.Context) {
return
}
- project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
@@ -598,7 +597,7 @@ func MoveIssues(ctx *context.Context) {
return
}
- column, err := project_model.GetColumn(ctx, ctx.PathParamInt64(":columnID"))
+ column, err := project_model.GetColumn(ctx, ctx.PathParamInt64("columnID"))
if err != nil {
ctx.NotFoundOrServerError("GetProjectColumn", project_model.IsErrProjectColumnNotExist, err)
return
diff --git a/routers/web/org/projects_test.go b/routers/web/org/projects_test.go
index c52cb7ed4c..c3a769e621 100644
--- a/routers/web/org/projects_test.go
+++ b/routers/web/org/projects_test.go
@@ -18,8 +18,8 @@ func TestCheckProjectColumnChangePermissions(t *testing.T) {
ctx, _ := contexttest.MockContext(t, "user2/-/projects/4/4")
contexttest.LoadUser(t, ctx, 2)
ctx.ContextUser = ctx.Doer // user2
- ctx.SetPathParam(":id", "4")
- ctx.SetPathParam(":columnID", "4")
+ ctx.SetPathParam("id", "4")
+ ctx.SetPathParam("columnID", "4")
project, column := org.CheckProjectColumnChangePermissions(ctx)
assert.NotNil(t, project)
diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go
index 494ada4323..cb1c4213c9 100644
--- a/routers/web/org/setting.go
+++ b/routers/web/org/setting.go
@@ -8,16 +8,16 @@ 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"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
@@ -30,13 +30,13 @@ import (
const (
// tplSettingsOptions template path for render settings
- tplSettingsOptions base.TplName = "org/settings/options"
+ tplSettingsOptions templates.TplName = "org/settings/options"
// tplSettingsDelete template path for render delete repository
- tplSettingsDelete base.TplName = "org/settings/delete"
+ tplSettingsDelete templates.TplName = "org/settings/delete"
// tplSettingsHooks template path for render hook settings
- tplSettingsHooks base.TplName = "org/settings/hooks"
+ tplSettingsHooks templates.TplName = "org/settings/hooks"
// tplSettingsLabels template path for render labels settings
- tplSettingsLabels base.TplName = "org/settings/labels"
+ tplSettingsLabels templates.TplName = "org/settings/labels"
)
// Settings render the main settings page
@@ -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/setting_oauth2.go b/routers/web/org/setting_oauth2.go
index 7f855795d3..c93058477e 100644
--- a/routers/web/org/setting_oauth2.go
+++ b/routers/web/org/setting_oauth2.go
@@ -9,16 +9,16 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
"code.gitea.io/gitea/services/context"
)
const (
- tplSettingsApplications base.TplName = "org/settings/applications"
- tplSettingsOAuthApplicationEdit base.TplName = "org/settings/applications_oauth2_edit"
+ tplSettingsApplications templates.TplName = "org/settings/applications"
+ tplSettingsOAuthApplicationEdit templates.TplName = "org/settings/applications_oauth2_edit"
)
func newOAuth2CommonHandlers(org *context.Organization) *user_setting.OAuth2CommonHandlers {
diff --git a/routers/web/org/setting_packages.go b/routers/web/org/setting_packages.go
index af9836e42c..0912a9e0fd 100644
--- a/routers/web/org/setting_packages.go
+++ b/routers/web/org/setting_packages.go
@@ -7,17 +7,17 @@ import (
"fmt"
"net/http"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
shared "code.gitea.io/gitea/routers/web/shared/packages"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
)
const (
- tplSettingsPackages base.TplName = "org/settings/packages"
- tplSettingsPackagesRuleEdit base.TplName = "org/settings/packages_cleanup_rules_edit"
- tplSettingsPackagesRulePreview base.TplName = "org/settings/packages_cleanup_rules_preview"
+ tplSettingsPackages templates.TplName = "org/settings/packages"
+ tplSettingsPackagesRuleEdit templates.TplName = "org/settings/packages_cleanup_rules_edit"
+ tplSettingsPackagesRulePreview templates.TplName = "org/settings/packages_cleanup_rules_preview"
)
func Packages(ctx *context.Context) {
diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go
index bd78832103..0137f2cc96 100644
--- a/routers/web/org/teams.go
+++ b/routers/web/org/teams.go
@@ -19,9 +19,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
@@ -33,15 +33,15 @@ import (
const (
// tplTeams template path for teams list page
- tplTeams base.TplName = "org/team/teams"
+ tplTeams templates.TplName = "org/team/teams"
// tplTeamNew template path for create new team page
- tplTeamNew base.TplName = "org/team/new"
+ tplTeamNew templates.TplName = "org/team/new"
// tplTeamMembers template path for showing team members page
- tplTeamMembers base.TplName = "org/team/members"
+ tplTeamMembers templates.TplName = "org/team/members"
// tplTeamRepositories template path for showing team repositories page
- tplTeamRepositories base.TplName = "org/team/repositories"
+ tplTeamRepositories templates.TplName = "org/team/repositories"
// tplTeamInvite template path for team invites page
- tplTeamInvite base.TplName = "org/team/invite"
+ tplTeamInvite templates.TplName = "org/team/invite"
)
// Teams render teams list page
@@ -71,7 +71,7 @@ func Teams(ctx *context.Context) {
func TeamsAction(ctx *context.Context) {
page := ctx.FormString("page")
var err error
- switch ctx.PathParam(":action") {
+ switch ctx.PathParam("action") {
case "join":
if !ctx.Org.IsOwner {
ctx.Error(http.StatusNotFound)
@@ -84,7 +84,7 @@ func TeamsAction(ctx *context.Context) {
if org_model.IsErrLastOrgOwner(err) {
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
} else {
- log.Error("Action(%s): %v", ctx.PathParam(":action"), err)
+ log.Error("Action(%s): %v", ctx.PathParam("action"), err)
ctx.JSON(http.StatusOK, map[string]any{
"ok": false,
"err": err.Error(),
@@ -111,7 +111,7 @@ func TeamsAction(ctx *context.Context) {
if org_model.IsErrLastOrgOwner(err) {
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
} else {
- log.Error("Action(%s): %v", ctx.PathParam(":action"), err)
+ log.Error("Action(%s): %v", ctx.PathParam("action"), err)
ctx.JSON(http.StatusOK, map[string]any{
"ok": false,
"err": err.Error(),
@@ -178,7 +178,7 @@ func TeamsAction(ctx *context.Context) {
}
if err := org_model.RemoveInviteByID(ctx, iid, ctx.Org.Team.ID); err != nil {
- log.Error("Action(%s): %v", ctx.PathParam(":action"), err)
+ log.Error("Action(%s): %v", ctx.PathParam("action"), err)
ctx.ServerError("RemoveInviteByID", err)
return
}
@@ -192,7 +192,7 @@ func TeamsAction(ctx *context.Context) {
} else if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Flash.Error(ctx.Tr("org.teams.members.blocked_user"))
} else {
- log.Error("Action(%s): %v", ctx.PathParam(":action"), err)
+ log.Error("Action(%s): %v", ctx.PathParam("action"), err)
ctx.JSON(http.StatusOK, map[string]any{
"ok": false,
"err": err.Error(),
@@ -233,7 +233,7 @@ func TeamsRepoAction(ctx *context.Context) {
}
var err error
- action := ctx.PathParam(":action")
+ action := ctx.PathParam("action")
switch action {
case "add":
repoName := path.Base(ctx.FormString("repo_name"))
@@ -258,7 +258,7 @@ func TeamsRepoAction(ctx *context.Context) {
}
if err != nil {
- log.Error("Action(%s): '%s' %v", ctx.PathParam(":action"), ctx.Org.Team.Name, err)
+ log.Error("Action(%s): '%s' %v", ctx.PathParam("action"), ctx.Org.Team.Name, err)
ctx.ServerError("TeamsRepoAction", err)
return
}
@@ -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 1de1835936..099593bff0 100644
--- a/routers/web/repo/actions/actions.go
+++ b/routers/web/repo/actions/actions.go
@@ -17,12 +17,12 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/actions"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
@@ -33,8 +33,8 @@ import (
)
const (
- tplListActions base.TplName = "repo/actions/list"
- tplViewActions base.TplName = "repo/actions/view"
+ tplListActions templates.TplName = "repo/actions/list"
+ tplViewActions templates.TplName = "repo/actions/view"
)
type Workflow struct {
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index b711038da0..ba17fa427d 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/activity.go b/routers/web/repo/activity.go
index 65dd9e392f..1d809ad8e9 100644
--- a/routers/web/repo/activity.go
+++ b/routers/web/repo/activity.go
@@ -9,12 +9,12 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
)
const (
- tplActivity base.TplName = "repo/activity"
+ tplActivity templates.TplName = "repo/activity"
)
// Activity render the page to show repository latest changes
diff --git a/routers/web/repo/attachment.go b/routers/web/repo/attachment.go
index 04f480f611..f8e51521be 100644
--- a/routers/web/repo/attachment.go
+++ b/routers/web/repo/attachment.go
@@ -154,5 +154,5 @@ func ServeAttachment(ctx *context.Context, uuid string) {
// GetAttachment serve attachments
func GetAttachment(ctx *context.Context) {
- ServeAttachment(ctx, ctx.PathParam(":uuid"))
+ ServeAttachment(ctx, ctx.PathParam("uuid"))
}
diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go
index c918cd7a72..72fd958e28 100644
--- a/routers/web/repo/branch.go
+++ b/routers/web/repo/branch.go
@@ -11,27 +11,27 @@ 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"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"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"
)
const (
- tplBranch base.TplName = "repo/branch/list"
+ tplBranch templates.TplName = "repo/branch/list"
)
// Branches render repository branch page
@@ -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 61aff78d49..35f158df52 100644
--- a/routers/web/repo/cherry_pick.go
+++ b/routers/web/repo/cherry_pick.go
@@ -8,12 +8,11 @@ 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"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
@@ -21,12 +20,12 @@ import (
"code.gitea.io/gitea/services/repository/files"
)
-var tplCherryPick base.TplName = "repo/editor/cherry_pick"
+var tplCherryPick templates.TplName = "repo/editor/cherry_pick"
// CherryPick handles cherrypick GETs
func CherryPick(ctx *context.Context) {
- ctx.Data["SHA"] = ctx.PathParam(":sha")
- cherryPickCommit, err := ctx.Repo.GitRepo.GetCommit(ctx.PathParam(":sha"))
+ ctx.Data["SHA"] = ctx.PathParam("sha")
+ cherryPickCommit, err := ctx.Repo.GitRepo.GetCommit(ctx.PathParam("sha"))
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound("Missing Commit", err)
@@ -38,7 +37,7 @@ func CherryPick(ctx *context.Context) {
if ctx.FormString("cherry-pick-type") == "revert" {
ctx.Data["CherryPickType"] = "revert"
- ctx.Data["commit_summary"] = "revert " + ctx.PathParam(":sha")
+ ctx.Data["commit_summary"] = "revert " + ctx.PathParam("sha")
ctx.Data["commit_message"] = "revert " + cherryPickCommit.Message()
} else {
ctx.Data["CherryPickType"] = "cherry-pick"
@@ -67,7 +66,7 @@ func CherryPick(ctx *context.Context) {
func CherryPickPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.CherryPickForm)
- sha := ctx.PathParam(":sha")
+ sha := ctx.PathParam("sha")
ctx.Data["SHA"] = sha
if form.Revert {
ctx.Data["CherryPickType"] = "revert"
@@ -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
}
@@ -141,7 +140,7 @@ func CherryPickPost(ctx *context.Context) {
if form.Revert {
if err := git.GetReverseRawDiff(ctx, ctx.Repo.Repository.RepoPath(), sha, buf); err != nil {
if git.IsErrNotExist(err) {
- ctx.NotFound("GetRawDiff", errors.New("commit "+ctx.PathParam(":sha")+" does not exist."))
+ ctx.NotFound("GetRawDiff", errors.New("commit "+ctx.PathParam("sha")+" does not exist."))
return
}
ctx.ServerError("GetRawDiff", err)
@@ -150,7 +149,7 @@ func CherryPickPost(ctx *context.Context) {
} else {
if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, git.RawDiffType("patch"), buf); err != nil {
if git.IsErrNotExist(err) {
- ctx.NotFound("GetRawDiff", errors.New("commit "+ctx.PathParam(":sha")+" does not exist."))
+ ctx.NotFound("GetRawDiff", errors.New("commit "+ctx.PathParam("sha")+" does not exist."))
return
}
ctx.ServerError("GetRawDiff", err)
@@ -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/code_frequency.go b/routers/web/repo/code_frequency.go
index c76f492da0..6572adce74 100644
--- a/routers/web/repo/code_frequency.go
+++ b/routers/web/repo/code_frequency.go
@@ -7,13 +7,13 @@ import (
"errors"
"net/http"
- "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
contributors_service "code.gitea.io/gitea/services/repository"
)
const (
- tplCodeFrequency base.TplName = "repo/activity"
+ tplCodeFrequency templates.TplName = "repo/activity"
)
// CodeFrequency renders the page to show repository code frequency
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index 6d53df7c10..6f534b9e2e 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -27,6 +27,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/gitdiff"
@@ -34,10 +35,10 @@ import (
)
const (
- tplCommits base.TplName = "repo/commits"
- tplGraph base.TplName = "repo/graph"
- tplGraphDiv base.TplName = "repo/graph/div"
- tplCommitPage base.TplName = "repo/commit_page"
+ tplCommits templates.TplName = "repo/commits"
+ tplGraph templates.TplName = "repo/graph"
+ tplGraphDiv templates.TplName = "repo/graph/div"
+ tplCommitPage templates.TplName = "repo/commit_page"
)
// RefCommits render commits page
@@ -281,7 +282,7 @@ func Diff(ctx *context.Context) {
userName := ctx.Repo.Owner.Name
repoName := ctx.Repo.Repository.Name
- commitID := ctx.PathParam(":sha")
+ commitID := ctx.PathParam("sha")
var (
gitRepo *git.Repository
err error
@@ -426,13 +427,13 @@ func RawDiff(ctx *context.Context) {
}
if err := git.GetRawDiff(
gitRepo,
- ctx.PathParam(":sha"),
- git.RawDiffType(ctx.PathParam(":ext")),
+ ctx.PathParam("sha"),
+ git.RawDiffType(ctx.PathParam("ext")),
ctx.Resp,
); err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound("GetRawDiff",
- errors.New("commit "+ctx.PathParam(":sha")+" does not exist."))
+ errors.New("commit "+ctx.PathParam("sha")+" does not exist."))
return
}
ctx.ServerError("GetRawDiff", err)
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index a500dd6f1e..b037c438e9 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -33,6 +33,7 @@ import (
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/common"
@@ -42,9 +43,9 @@ import (
)
const (
- tplCompare base.TplName = "repo/diff/compare"
- tplBlobExcerpt base.TplName = "repo/diff/blob_excerpt"
- tplDiffBox base.TplName = "repo/diff/box"
+ tplCompare templates.TplName = "repo/diff/compare"
+ tplBlobExcerpt templates.TplName = "repo/diff/blob_excerpt"
+ tplDiffBox templates.TplName = "repo/diff/box"
)
// setCompareContext sets context data.
@@ -675,7 +676,7 @@ func PrepareCompareDiff(
}
if len(title) > 255 {
var trailer string
- title, trailer = util.SplitStringAtByteN(title, 255)
+ title, trailer = util.EllipsisDisplayStringX(title, 255)
if len(trailer) > 0 {
if ctx.Data["content"] != nil {
ctx.Data["content"] = fmt.Sprintf("%s\n\n%s", trailer, ctx.Data["content"])
@@ -875,7 +876,7 @@ func ExcerptBlob(ctx *context.Context) {
direction := ctx.FormString("direction")
filePath := ctx.FormString("path")
gitRepo := ctx.Repo.GitRepo
- if ctx.FormBool("wiki") {
+ if ctx.Data["PageIsWiki"] == true {
var err error
gitRepo, err = gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
if err != nil {
diff --git a/routers/web/repo/contributors.go b/routers/web/repo/contributors.go
index 762fbf9379..e9c0919955 100644
--- a/routers/web/repo/contributors.go
+++ b/routers/web/repo/contributors.go
@@ -7,13 +7,13 @@ import (
"errors"
"net/http"
- "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
contributors_service "code.gitea.io/gitea/services/repository"
)
const (
- tplContributors base.TplName = "repo/activity"
+ tplContributors templates.TplName = "repo/activity"
)
// Contributors render the page to show repository contributors graph
diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go
index 9396115b0d..5fbdeee27e 100644
--- a/routers/web/repo/editor.go
+++ b/routers/web/repo/editor.go
@@ -10,17 +10,16 @@ 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"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
@@ -32,10 +31,10 @@ import (
)
const (
- tplEditFile base.TplName = "repo/editor/edit"
- tplEditDiffPreview base.TplName = "repo/editor/diff_preview"
- tplDeleteFile base.TplName = "repo/editor/delete"
- tplUploadFile base.TplName = "repo/editor/upload"
+ tplEditFile templates.TplName = "repo/editor/edit"
+ tplEditDiffPreview templates.TplName = "repo/editor/diff_preview"
+ tplDeleteFile templates.TplName = "repo/editor/delete"
+ tplUploadFile templates.TplName = "repo/editor/upload"
frmCommitChoiceDirect string = "direct"
frmCommitChoiceNewBranch string = "commit-to-new-branch"
@@ -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/editor_test.go b/routers/web/repo/editor_test.go
index 68d69408ac..566db31693 100644
--- a/routers/web/repo/editor_test.go
+++ b/routers/web/repo/editor_test.go
@@ -43,7 +43,7 @@ func TestCleanUploadName(t *testing.T) {
func TestGetUniquePatchBranchName(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam(":id", "1")
+ ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
@@ -58,7 +58,7 @@ func TestGetUniquePatchBranchName(t *testing.T) {
func TestGetClosestParentWithFiles(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam(":id", "1")
+ ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
diff --git a/routers/web/repo/find.go b/routers/web/repo/find.go
index 2c44552f9c..3a3a7610e7 100644
--- a/routers/web/repo/find.go
+++ b/routers/web/repo/find.go
@@ -6,13 +6,13 @@ package repo
import (
"net/http"
- "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
)
const (
- tplFindFiles base.TplName = "repo/find/files"
+ tplFindFiles templates.TplName = "repo/find/files"
)
// FindFiles render the page to find repository files
diff --git a/routers/web/repo/fork.go b/routers/web/repo/fork.go
index 9ea2a8559d..e4f8ea517a 100644
--- a/routers/web/repo/fork.go
+++ b/routers/web/repo/fork.go
@@ -13,12 +13,12 @@ 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/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/templates"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
@@ -26,7 +26,7 @@ import (
)
const (
- tplFork base.TplName = "repo/pulls/fork"
+ tplFork templates.TplName = "repo/pulls/fork"
)
func getForkRepository(ctx *context.Context) *repo_model.Repository {
diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go
index 58a2bdbab1..6b2a7fd076 100644
--- a/routers/web/repo/githttp.go
+++ b/routers/web/repo/githttp.go
@@ -57,8 +57,8 @@ func CorsHandler() func(next http.Handler) http.Handler {
// httpBase implementation git smart HTTP protocol
func httpBase(ctx *context.Context) *serviceHandler {
- username := ctx.PathParam(":username")
- reponame := strings.TrimSuffix(ctx.PathParam(":reponame"), ".git")
+ username := ctx.PathParam("username")
+ reponame := strings.TrimSuffix(ctx.PathParam("reponame"), ".git")
if ctx.FormString("go-get") == "1" {
context.EarlyResponseForGoGetMeta(ctx)
@@ -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/issue.go b/routers/web/repo/issue.go
index 5397411b59..a3a4e73d7b 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -21,7 +21,6 @@ import (
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/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/optional"
@@ -37,14 +36,14 @@ import (
)
const (
- tplAttachment base.TplName = "repo/issue/view_content/attachments"
+ tplAttachment templates.TplName = "repo/issue/view_content/attachments"
- tplIssues base.TplName = "repo/issue/list"
- tplIssueNew base.TplName = "repo/issue/new"
- tplIssueChoose base.TplName = "repo/issue/choose"
- tplIssueView base.TplName = "repo/issue/view"
+ tplIssues templates.TplName = "repo/issue/list"
+ tplIssueNew templates.TplName = "repo/issue/new"
+ tplIssueChoose templates.TplName = "repo/issue/choose"
+ tplIssueView templates.TplName = "repo/issue/view"
- tplReactions base.TplName = "repo/issue/view_content/reactions"
+ tplReactions templates.TplName = "repo/issue/view_content/reactions"
issueTemplateKey = "IssueTemplate"
issueTemplateTitleKey = "IssueTemplateTitle"
@@ -182,7 +181,7 @@ func retrieveProjectsInternal(ctx *context.Context, repo *repo_model.Repository)
// GetActionIssue will return the issue which is used in the context.
func GetActionIssue(ctx *context.Context) *issues_model.Issue {
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
ctx.NotFoundOrServerError("GetIssueByIndex", issues_model.IsErrIssueNotExist, err)
return nil
@@ -247,7 +246,7 @@ func getActionIssues(ctx *context.Context) issues_model.IssueList {
// GetIssueInfo get an issue of a repository
func GetIssueInfo(ctx *context.Context) {
- issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.Error(http.StatusNotFound)
@@ -380,7 +379,7 @@ func UpdateIssueContent(ctx *context.Context) {
// UpdateIssueDeadline updates an issue deadline
func UpdateIssueDeadline(ctx *context.Context) {
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound("GetIssueByIndex", err)
@@ -507,7 +506,7 @@ func ChangeIssueReaction(ctx *context.Context) {
return
}
- switch ctx.PathParam(":action") {
+ switch ctx.PathParam("action") {
case "react":
reaction, err := issue_service.CreateIssueReaction(ctx, ctx.Doer, issue, form.Content)
if err != nil {
@@ -541,7 +540,7 @@ func ChangeIssueReaction(ctx *context.Context) {
log.Trace("Reaction for issue removed: %d/%d", ctx.Repo.Repository.ID, issue.ID)
default:
- ctx.NotFound(fmt.Sprintf("Unknown action %s", ctx.PathParam(":action")), nil)
+ ctx.NotFound(fmt.Sprintf("Unknown action %s", ctx.PathParam("action")), nil)
return
}
diff --git a/routers/web/repo/issue_comment.go b/routers/web/repo/issue_comment.go
index 6b7b29d9d7..8564c613de 100644
--- a/routers/web/repo/issue_comment.go
+++ b/routers/web/repo/issue_comment.go
@@ -154,25 +154,28 @@ func NewComment(ctx *context.Context) {
if pr != nil {
ctx.Flash.Info(ctx.Tr("repo.pulls.open_unmerged_pull_exists", pr.Index))
} else {
- isClosed := form.Status == "close"
- if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed); err != nil {
- log.Error("ChangeStatus: %v", err)
-
- if issues_model.IsErrDependenciesLeft(err) {
- if issue.IsPull {
- ctx.JSONError(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
- } else {
- ctx.JSONError(ctx.Tr("repo.issues.dependency.issue_close_blocked"))
+ if form.Status == "close" && !issue.IsClosed {
+ if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
+ log.Error("CloseIssue: %v", err)
+ if issues_model.IsErrDependenciesLeft(err) {
+ if issue.IsPull {
+ ctx.JSONError(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
+ } else {
+ ctx.JSONError(ctx.Tr("repo.issues.dependency.issue_close_blocked"))
+ }
+ return
}
- return
+ } else {
+ if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
+ ctx.ServerError("stopTimerIfAvailable", err)
+ return
+ }
+ log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)
}
- } else {
- if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
- ctx.ServerError("CreateOrStopIssueStopwatch", err)
- return
+ } else if form.Status == "reopen" && issue.IsClosed {
+ if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
+ log.Error("ReopenIssue: %v", err)
}
-
- log.Trace("Issue [%d] status changed to closed: %v", issue.ID, issue.IsClosed)
}
}
}
@@ -209,7 +212,7 @@ func NewComment(ctx *context.Context) {
// UpdateCommentContent change comment of issue's content
func UpdateCommentContent(ctx *context.Context) {
- comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id"))
+ comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
ctx.NotFoundOrServerError("GetCommentByID", issues_model.IsErrCommentNotExist, err)
return
@@ -287,7 +290,7 @@ func UpdateCommentContent(ctx *context.Context) {
// DeleteComment delete comment of issue
func DeleteComment(ctx *context.Context) {
- comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id"))
+ comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
ctx.NotFoundOrServerError("GetCommentByID", issues_model.IsErrCommentNotExist, err)
return
@@ -322,7 +325,7 @@ func DeleteComment(ctx *context.Context) {
// ChangeCommentReaction create a reaction for comment
func ChangeCommentReaction(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.ReactionForm)
- comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id"))
+ comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
ctx.NotFoundOrServerError("GetCommentByID", issues_model.IsErrCommentNotExist, err)
return
@@ -366,7 +369,7 @@ func ChangeCommentReaction(ctx *context.Context) {
return
}
- switch ctx.PathParam(":action") {
+ switch ctx.PathParam("action") {
case "react":
reaction, err := issue_service.CreateCommentReaction(ctx, ctx.Doer, comment, form.Content)
if err != nil {
@@ -400,7 +403,7 @@ func ChangeCommentReaction(ctx *context.Context) {
log.Trace("Reaction for comment removed: %d/%d/%d", ctx.Repo.Repository.ID, comment.Issue.ID, comment.ID)
default:
- ctx.NotFound(fmt.Sprintf("Unknown action %s", ctx.PathParam(":action")), nil)
+ ctx.NotFound(fmt.Sprintf("Unknown action %s", ctx.PathParam("action")), nil)
return
}
@@ -427,7 +430,7 @@ func ChangeCommentReaction(ctx *context.Context) {
// GetCommentAttachments returns attachments for the comment
func GetCommentAttachments(ctx *context.Context) {
- comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id"))
+ comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
ctx.NotFoundOrServerError("GetCommentByID", issues_model.IsErrCommentNotExist, err)
return
diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go
index 4874baaa54..5ef6b09faa 100644
--- a/routers/web/repo/issue_label.go
+++ b/routers/web/repo/issue_label.go
@@ -9,10 +9,10 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
@@ -20,7 +20,7 @@ import (
)
const (
- tplLabels base.TplName = "repo/issue/labels"
+ tplLabels templates.TplName = "repo/issue/labels"
)
// Labels render issue's labels page
diff --git a/routers/web/repo/issue_list.go b/routers/web/repo/issue_list.go
index ff98bf8ec8..2f615a100e 100644
--- a/routers/web/repo/issue_list.go
+++ b/routers/web/repo/issue_list.go
@@ -418,14 +418,11 @@ func UpdateIssueStatus(ctx *context.Context) {
return
}
- var isClosed bool
- switch action := ctx.FormString("action"); action {
- case "open":
- isClosed = false
- case "close":
- isClosed = true
- default:
+ action := ctx.FormString("action")
+ if action != "open" && action != "close" {
log.Warn("Unrecognized action: %s", action)
+ ctx.JSONOK()
+ return
}
if _, err := issues.LoadRepositories(ctx); err != nil {
@@ -441,15 +438,20 @@ func UpdateIssueStatus(ctx *context.Context) {
if issue.IsPull && issue.PullRequest.HasMerged {
continue
}
- if issue.IsClosed != isClosed {
- if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", isClosed); err != nil {
+ if action == "close" && !issue.IsClosed {
+ if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
if issues_model.IsErrDependenciesLeft(err) {
ctx.JSON(http.StatusPreconditionFailed, map[string]any{
"error": ctx.Tr("repo.issues.dependency.issue_batch_close_blocked", issue.Index),
})
return
}
- ctx.ServerError("ChangeStatus", err)
+ ctx.ServerError("CloseIssue", err)
+ return
+ }
+ } else if action == "open" && issue.IsClosed {
+ if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
+ ctx.ServerError("ReopenIssue", err)
return
}
}
@@ -748,7 +750,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
// Issues render issues page
func Issues(ctx *context.Context) {
- isPullList := ctx.PathParam(":type") == "pulls"
+ isPullList := ctx.PathParam("type") == "pulls"
if isPullList {
MustAllowPulls(ctx)
if ctx.Written() {
diff --git a/routers/web/repo/issue_pin.go b/routers/web/repo/issue_pin.go
index 0074e31f03..d7d3205c37 100644
--- a/routers/web/repo/issue_pin.go
+++ b/routers/web/repo/issue_pin.go
@@ -39,7 +39,7 @@ func IssuePinOrUnpin(ctx *context.Context) {
// IssueUnpin unpins a Issue
func IssueUnpin(ctx *context.Context) {
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
ctx.Status(http.StatusInternalServerError)
log.Error(err.Error())
diff --git a/routers/web/repo/issue_timetrack.go b/routers/web/repo/issue_timetrack.go
index 88c539488e..36e931a48f 100644
--- a/routers/web/repo/issue_timetrack.go
+++ b/routers/web/repo/issue_timetrack.go
@@ -60,7 +60,7 @@ func DeleteTime(c *context.Context) {
return
}
- t, err := issues_model.GetTrackedTimeByID(c, c.PathParamInt64(":timeid"))
+ t, err := issues_model.GetTrackedTimeByID(c, c.PathParamInt64("timeid"))
if err != nil {
if db.IsErrNotExist(err) {
c.NotFound("time not found", err)
diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go
index 09b57f4e78..61e75e211b 100644
--- a/routers/web/repo/issue_view.go
+++ b/routers/web/repo/issue_view.go
@@ -265,13 +265,13 @@ func combineLabelComments(issue *issues_model.Issue) {
// ViewIssue render issue view page
func ViewIssue(ctx *context.Context) {
- if ctx.PathParam(":type") == "issues" {
+ if ctx.PathParam("type") == "issues" {
// If issue was requested we check if repo has external tracker and redirect
extIssueUnit, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypeExternalTracker)
if err == nil && extIssueUnit != nil {
if extIssueUnit.ExternalTrackerConfig().ExternalTrackerStyle == markup.IssueNameStyleNumeric || extIssueUnit.ExternalTrackerConfig().ExternalTrackerStyle == "" {
metas := ctx.Repo.Repository.ComposeMetas(ctx)
- metas["index"] = ctx.PathParam(":index")
+ metas["index"] = ctx.PathParam("index")
res, err := vars.Expand(extIssueUnit.ExternalTrackerConfig().ExternalTrackerFormat, metas)
if err != nil {
log.Error("unable to expand template vars for issue url. issue: %s, err: %v", metas["index"], err)
@@ -287,7 +287,7 @@ func ViewIssue(ctx *context.Context) {
}
}
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound("GetIssueByIndex", err)
@@ -301,10 +301,10 @@ func ViewIssue(ctx *context.Context) {
}
// Make sure type and URL matches.
- if ctx.PathParam(":type") == "issues" && issue.IsPull {
+ if ctx.PathParam("type") == "issues" && issue.IsPull {
ctx.Redirect(issue.Link())
return
- } else if ctx.PathParam(":type") == "pulls" && !issue.IsPull {
+ } else if ctx.PathParam("type") == "pulls" && !issue.IsPull {
ctx.Redirect(issue.Link())
return
}
diff --git a/routers/web/repo/issue_watch.go b/routers/web/repo/issue_watch.go
index 8b033f3b17..a2a4be1758 100644
--- a/routers/web/repo/issue_watch.go
+++ b/routers/web/repo/issue_watch.go
@@ -8,13 +8,13 @@ import (
"strconv"
issues_model "code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
)
const (
- tplWatching base.TplName = "repo/issue/view_content/watching"
+ tplWatching templates.TplName = "repo/issue/view_content/watching"
)
// IssueWatch sets issue watching
diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go
index 3eaf05f383..3a7dc29466 100644
--- a/routers/web/repo/migrate.go
+++ b/routers/web/repo/migrate.go
@@ -9,17 +9,17 @@ 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"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
@@ -29,7 +29,7 @@ import (
)
const (
- tplMigrate base.TplName = "repo/migrate/migrate"
+ tplMigrate templates.TplName = "repo/migrate/migrate"
)
// Migrate render migration of repository page
@@ -67,10 +67,10 @@ func Migrate(ctx *context.Context) {
}
ctx.Data["ContextUser"] = ctxUser
- ctx.HTML(http.StatusOK, base.TplName("repo/migrate/"+serviceType.Name()))
+ ctx.HTML(http.StatusOK, templates.TplName("repo/migrate/"+serviceType.Name()))
}
-func handleMigrateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl base.TplName, form *forms.MigrateRepoForm) {
+func handleMigrateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl templates.TplName, form *forms.MigrateRepoForm) {
if setting.Repository.DisableMigrations {
ctx.Error(http.StatusForbidden, "MigrateError: the site administrator has disabled migrations")
return
@@ -122,9 +122,9 @@ 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)
+func handleMigrateRemoteAddrError(ctx *context.Context, err error, tpl templates.TplName, form *forms.MigrateRepoForm) {
+ if git.IsErrInvalidCloneAddr(err) {
+ addrErr := err.(*git.ErrInvalidCloneAddr)
switch {
case addrErr.IsProtocolInvalid:
ctx.RenderWithErr(ctx.Tr("repo.mirror_address_protocol_invalid"), tpl, form)
@@ -169,14 +169,14 @@ func MigratePost(ctx *context.Context) {
}
ctx.Data["ContextUser"] = ctxUser
- tpl := base.TplName("repo/migrate/" + form.Service.Name())
+ tpl := templates.TplName("repo/migrate/" + form.Service.Name())
if ctx.HasError() {
ctx.HTML(http.StatusOK, tpl)
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/milestone.go b/routers/web/repo/milestone.go
index 33c15e7767..392d87167a 100644
--- a/routers/web/repo/milestone.go
+++ b/routers/web/repo/milestone.go
@@ -11,10 +11,10 @@ import (
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/renderhelper"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/services/context"
@@ -25,9 +25,9 @@ import (
)
const (
- tplMilestone base.TplName = "repo/issue/milestones"
- tplMilestoneNew base.TplName = "repo/issue/milestone_new"
- tplMilestoneIssues base.TplName = "repo/issue/milestone_issues"
+ tplMilestone templates.TplName = "repo/issue/milestones"
+ tplMilestoneNew templates.TplName = "repo/issue/milestone_new"
+ tplMilestoneIssues templates.TplName = "repo/issue/milestone_issues"
)
// Milestones render milestones page
@@ -147,7 +147,7 @@ func EditMilestone(ctx *context.Context) {
ctx.Data["PageIsMilestones"] = true
ctx.Data["PageIsEditMilestone"] = true
- m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":id"))
+ m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrMilestoneNotExist(err) {
ctx.NotFound("", nil)
@@ -183,7 +183,7 @@ func EditMilestonePost(ctx *context.Context) {
return
}
- m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":id"))
+ m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
if err != nil {
if issues_model.IsErrMilestoneNotExist(err) {
ctx.NotFound("", nil)
@@ -207,7 +207,7 @@ func EditMilestonePost(ctx *context.Context) {
// ChangeMilestoneStatus response for change a milestone's status
func ChangeMilestoneStatus(ctx *context.Context) {
var toClose bool
- switch ctx.PathParam(":action") {
+ switch ctx.PathParam("action") {
case "open":
toClose = false
case "close":
@@ -216,7 +216,7 @@ func ChangeMilestoneStatus(ctx *context.Context) {
ctx.JSONRedirect(ctx.Repo.RepoLink + "/milestones")
return
}
- id := ctx.PathParamInt64(":id")
+ id := ctx.PathParamInt64("id")
if err := issues_model.ChangeMilestoneStatusByRepoIDAndID(ctx, ctx.Repo.Repository.ID, id, toClose); err != nil {
if issues_model.IsErrMilestoneNotExist(err) {
@@ -226,7 +226,7 @@ func ChangeMilestoneStatus(ctx *context.Context) {
}
return
}
- ctx.JSONRedirect(ctx.Repo.RepoLink + "/milestones?state=" + url.QueryEscape(ctx.PathParam(":action")))
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/milestones?state=" + url.QueryEscape(ctx.PathParam("action")))
}
// DeleteMilestone delete a milestone
@@ -242,7 +242,7 @@ func DeleteMilestone(ctx *context.Context) {
// MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone
func MilestoneIssuesAndPulls(ctx *context.Context) {
- milestoneID := ctx.PathParamInt64(":id")
+ milestoneID := ctx.PathParamInt64("id")
projectID := ctx.FormInt64("project")
milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID)
if err != nil {
diff --git a/routers/web/repo/packages.go b/routers/web/repo/packages.go
index 57e578da37..c8d3719bc0 100644
--- a/routers/web/repo/packages.go
+++ b/routers/web/repo/packages.go
@@ -9,14 +9,14 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
)
const (
- tplPackagesList base.TplName = "repo/packages"
+ tplPackagesList templates.TplName = "repo/packages"
)
// Packages displays a list of all packages in the repository
diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go
index 0dee02dd9c..1807cf31a1 100644
--- a/routers/web/repo/patch.go
+++ b/routers/web/repo/patch.go
@@ -6,11 +6,10 @@ 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"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
@@ -19,7 +18,7 @@ import (
)
const (
- tplPatchFile base.TplName = "repo/editor/patch"
+ tplPatchFile templates.TplName = "repo/editor/patch"
)
// NewDiffPatch render create patch page
@@ -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/projects.go b/routers/web/repo/projects.go
index 3be9578670..4313b6c403 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -16,11 +16,11 @@ import (
"code.gitea.io/gitea/models/renderhelper"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/shared/issue"
@@ -31,9 +31,9 @@ import (
)
const (
- tplProjects base.TplName = "repo/projects/list"
- tplProjectsNew base.TplName = "repo/projects/new"
- tplProjectsView base.TplName = "repo/projects/view"
+ tplProjects templates.TplName = "repo/projects/list"
+ tplProjectsNew templates.TplName = "repo/projects/new"
+ tplProjectsView templates.TplName = "repo/projects/view"
)
// MustEnableRepoProjects check if repo projects are enabled in settings
@@ -166,7 +166,7 @@ func NewProjectPost(ctx *context.Context) {
// ChangeProjectStatus updates the status of a project between "open" and "close"
func ChangeProjectStatus(ctx *context.Context) {
var toClose bool
- switch ctx.PathParam(":action") {
+ switch ctx.PathParam("action") {
case "open":
toClose = false
case "close":
@@ -175,7 +175,7 @@ func ChangeProjectStatus(ctx *context.Context) {
ctx.JSONRedirect(ctx.Repo.RepoLink + "/projects")
return
}
- id := ctx.PathParamInt64(":id")
+ id := ctx.PathParamInt64("id")
if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, ctx.Repo.Repository.ID, id, toClose); err != nil {
ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
@@ -186,7 +186,7 @@ func ChangeProjectStatus(ctx *context.Context) {
// DeleteProject delete a project
func DeleteProject(ctx *context.Context) {
- p, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ p, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
@@ -216,7 +216,7 @@ func RenderEditProject(ctx *context.Context) {
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.Data["CardTypes"] = project_model.GetCardConfig()
- p, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ p, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
@@ -243,7 +243,7 @@ func RenderEditProject(ctx *context.Context) {
// EditProjectPost response for editing a project
func EditProjectPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.CreateProjectForm)
- projectID := ctx.PathParamInt64(":id")
+ projectID := ctx.PathParamInt64("id")
ctx.Data["Title"] = ctx.Tr("repo.projects.edit")
ctx.Data["PageIsEditProjects"] = true
@@ -288,7 +288,7 @@ func EditProjectPost(ctx *context.Context) {
// ViewProject renders the project with board view
func ViewProject(ctx *context.Context) {
- project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
@@ -468,7 +468,7 @@ func DeleteProjectColumn(ctx *context.Context) {
return
}
- project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
@@ -478,12 +478,12 @@ func DeleteProjectColumn(ctx *context.Context) {
return
}
- pb, err := project_model.GetColumn(ctx, ctx.PathParamInt64(":columnID"))
+ pb, err := project_model.GetColumn(ctx, ctx.PathParamInt64("columnID"))
if err != nil {
ctx.ServerError("GetProjectColumn", err)
return
}
- if pb.ProjectID != ctx.PathParamInt64(":id") {
+ if pb.ProjectID != ctx.PathParamInt64("id") {
ctx.JSON(http.StatusUnprocessableEntity, map[string]string{
"message": fmt.Sprintf("ProjectColumn[%d] is not in Project[%d] as expected", pb.ID, project.ID),
})
@@ -497,7 +497,7 @@ func DeleteProjectColumn(ctx *context.Context) {
return
}
- if err := project_model.DeleteColumnByID(ctx, ctx.PathParamInt64(":columnID")); err != nil {
+ if err := project_model.DeleteColumnByID(ctx, ctx.PathParamInt64("columnID")); err != nil {
ctx.ServerError("DeleteProjectColumnByID", err)
return
}
@@ -515,7 +515,7 @@ func AddColumnToProjectPost(ctx *context.Context) {
return
}
- project, err := project_model.GetProjectForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":id"))
+ project, err := project_model.GetProjectForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
@@ -553,7 +553,7 @@ func checkProjectColumnChangePermissions(ctx *context.Context) (*project_model.P
return nil, nil
}
- project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
@@ -563,12 +563,12 @@ func checkProjectColumnChangePermissions(ctx *context.Context) (*project_model.P
return nil, nil
}
- column, err := project_model.GetColumn(ctx, ctx.PathParamInt64(":columnID"))
+ column, err := project_model.GetColumn(ctx, ctx.PathParamInt64("columnID"))
if err != nil {
ctx.ServerError("GetProjectColumn", err)
return nil, nil
}
- if column.ProjectID != ctx.PathParamInt64(":id") {
+ if column.ProjectID != ctx.PathParamInt64("id") {
ctx.JSON(http.StatusUnprocessableEntity, map[string]string{
"message": fmt.Sprintf("ProjectColumn[%d] is not in Project[%d] as expected", column.ID, project.ID),
})
@@ -639,7 +639,7 @@ func MoveIssues(ctx *context.Context) {
return
}
- project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("ProjectNotExist", nil)
@@ -653,7 +653,7 @@ func MoveIssues(ctx *context.Context) {
return
}
- column, err := project_model.GetColumn(ctx, ctx.PathParamInt64(":columnID"))
+ column, err := project_model.GetColumn(ctx, ctx.PathParamInt64("columnID"))
if err != nil {
if project_model.IsErrProjectColumnNotExist(err) {
ctx.NotFound("ProjectColumnNotExist", nil)
diff --git a/routers/web/repo/projects_test.go b/routers/web/repo/projects_test.go
index 1a42c615ab..d0690d9a4d 100644
--- a/routers/web/repo/projects_test.go
+++ b/routers/web/repo/projects_test.go
@@ -17,8 +17,8 @@ func TestCheckProjectColumnChangePermissions(t *testing.T) {
ctx, _ := contexttest.MockContext(t, "user2/repo1/projects/1/2")
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 1)
- ctx.SetPathParam(":id", "1")
- ctx.SetPathParam(":columnID", "2")
+ ctx.SetPathParam("id", "1")
+ ctx.SetPathParam("columnID", "2")
project, column := checkProjectColumnChangePermissions(ctx)
assert.NotNil(t, project)
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index 9694ae845b..9f3d1c1b7c 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"
@@ -24,13 +23,13 @@ import (
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/base"
"code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
issue_template "code.gitea.io/gitea/modules/issue/template"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
@@ -50,9 +49,9 @@ import (
)
const (
- tplCompareDiff base.TplName = "repo/diff/compare"
- tplPullCommits base.TplName = "repo/pulls/commits"
- tplPullFiles base.TplName = "repo/pulls/files"
+ tplCompareDiff templates.TplName = "repo/diff/compare"
+ tplPullCommits templates.TplName = "repo/pulls/commits"
+ tplPullFiles templates.TplName = "repo/pulls/files"
pullRequestTemplateKey = "PullRequestTemplate"
)
@@ -109,7 +108,7 @@ func getRepository(ctx *context.Context, repoID int64) *repo_model.Repository {
}
func getPullInfo(ctx *context.Context) (issue *issues_model.Issue, ok bool) {
- issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound("GetIssueByIndex", err)
@@ -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())
@@ -1548,7 +1544,7 @@ func DownloadPullPatch(ctx *context.Context) {
// DownloadPullDiffOrPatch render a pull's raw diff or patch
func DownloadPullDiffOrPatch(ctx *context.Context, patch bool) {
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound("GetPullRequestByIndex", err)
@@ -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)
@@ -1641,7 +1637,7 @@ func UpdatePullRequestTarget(ctx *context.Context) {
func SetAllowEdits(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.UpdateAllowEditsForm)
- pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index"))
+ pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
if issues_model.IsErrPullRequestNotExist(err) {
ctx.NotFound("GetPullRequestByIndex", err)
diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go
index aa2e689e42..3e9e615b15 100644
--- a/routers/web/repo/pull_review.go
+++ b/routers/web/repo/pull_review.go
@@ -12,10 +12,10 @@ import (
"code.gitea.io/gitea/models/organization"
pull_model "code.gitea.io/gitea/models/pull"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/context/upload"
@@ -26,10 +26,10 @@ import (
)
const (
- tplDiffConversation base.TplName = "repo/diff/conversation"
- tplConversationOutdated base.TplName = "repo/diff/conversation_outdated"
- tplTimelineConversation base.TplName = "repo/issue/view_content/conversation"
- tplNewComment base.TplName = "repo/diff/new_comment"
+ tplDiffConversation templates.TplName = "repo/diff/conversation"
+ tplConversationOutdated templates.TplName = "repo/diff/conversation_outdated"
+ tplTimelineConversation templates.TplName = "repo/issue/view_content/conversation"
+ tplNewComment templates.TplName = "repo/diff/new_comment"
)
// RenderNewCodeCommentForm will render the form for creating a new review comment
diff --git a/routers/web/repo/recent_commits.go b/routers/web/repo/recent_commits.go
index c158fb30b6..dc72081900 100644
--- a/routers/web/repo/recent_commits.go
+++ b/routers/web/repo/recent_commits.go
@@ -7,13 +7,13 @@ import (
"errors"
"net/http"
- "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
contributors_service "code.gitea.io/gitea/services/repository"
)
const (
- tplRecentCommits base.TplName = "repo/activity"
+ tplRecentCommits templates.TplName = "repo/activity"
)
// RecentCommits renders the page to show recent commit frequency on repository
diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go
index b3a91a6070..b8176cb70b 100644
--- a/routers/web/repo/release.go
+++ b/routers/web/repo/release.go
@@ -10,19 +10,18 @@ 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"
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/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/feed"
@@ -30,13 +29,13 @@ 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 (
- tplReleasesList base.TplName = "repo/release/list"
- tplReleaseNew base.TplName = "repo/release/new"
- tplTagsList base.TplName = "repo/tag/list"
+ tplReleasesList templates.TplName = "repo/release/list"
+ tplReleaseNew templates.TplName = "repo/release/new"
+ tplTagsList templates.TplName = "repo/tag/list"
)
// calReleaseNumCommitsBehind calculates given release has how many commits behind release target.
@@ -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 f5e59b0357..85a745acbd 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"
@@ -19,7 +18,6 @@ import (
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/base"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
@@ -28,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
@@ -39,8 +38,8 @@ import (
)
const (
- tplCreate base.TplName = "repo/create"
- tplAlertDetails base.TplName = "base/alert_details"
+ tplCreate templates.TplName = "repo/create"
+ tplAlertDetails templates.TplName = "base/alert_details"
)
// MustBeNotEmpty render when a repo is a empty git dir
@@ -186,7 +185,7 @@ func Create(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplCreate)
}
-func handleCreateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl base.TplName, form any) {
+func handleCreateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl templates.TplName, form any) {
switch {
case repo_model.IsErrReachLimitOfRepo(err):
maxCreationLimit := owner.MaxCreationLimit()
@@ -305,14 +304,14 @@ func CreatePost(ctx *context.Context) {
}
const (
- tplWatchUnwatch base.TplName = "repo/watch_unwatch"
- tplStarUnstar base.TplName = "repo/star_unstar"
+ tplWatchUnwatch templates.TplName = "repo/watch_unwatch"
+ tplStarUnstar templates.TplName = "repo/star_unstar"
)
// Action response for actions to a repository
func Action(ctx *context.Context) {
var err error
- switch ctx.PathParam(":action") {
+ switch ctx.PathParam("action") {
case "watch":
err = repo_model.WatchRepo(ctx, ctx.Doer, ctx.Repo.Repository, true)
case "unwatch":
@@ -340,12 +339,12 @@ func Action(ctx *context.Context) {
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Flash.Error(ctx.Tr("repo.action.blocked_user"))
} else {
- ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam(":action")), err)
+ ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err)
return
}
}
- switch ctx.PathParam(":action") {
+ switch ctx.PathParam("action") {
case "watch", "unwatch":
ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
case "star", "unstar":
@@ -355,17 +354,17 @@ func Action(ctx *context.Context) {
// see the `hx-trigger="refreshUserCards ..."` comments in tmpl
ctx.RespHeader().Add("hx-trigger", "refreshUserCards")
- switch ctx.PathParam(":action") {
+ switch ctx.PathParam("action") {
case "watch", "unwatch", "star", "unstar":
// we have to reload the repository because NumStars or NumWatching (used in the templates) has just changed
ctx.Data["Repository"], err = repo_model.GetRepositoryByName(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.Name)
if err != nil {
- ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam(":action")), err)
+ ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err)
return
}
}
- switch ctx.PathParam(":action") {
+ switch ctx.PathParam("action") {
case "watch", "unwatch":
ctx.HTML(http.StatusOK, tplWatchUnwatch)
return
@@ -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/search.go b/routers/web/repo/search.go
index 920a865555..a037a34833 100644
--- a/routers/web/repo/search.go
+++ b/routers/web/repo/search.go
@@ -8,14 +8,14 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
code_indexer "code.gitea.io/gitea/modules/indexer/code"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
)
-const tplSearch base.TplName = "repo/search"
+const tplSearch templates.TplName = "repo/search"
func indexSettingToGitGrepPathspecList() (list []string) {
for _, expr := range setting.Indexer.IncludePatterns {
diff --git a/routers/web/repo/setting/collaboration.go b/routers/web/repo/setting/collaboration.go
index cdf91edf4a..df7cc5e39b 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 abc3eb4af1..193562528b 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/git_hooks.go b/routers/web/repo/setting/git_hooks.go
index 2e9caa4c86..1d92211303 100644
--- a/routers/web/repo/setting/git_hooks.go
+++ b/routers/web/repo/setting/git_hooks.go
@@ -30,7 +30,7 @@ func GitHooksEdit(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.githooks")
ctx.Data["PageIsSettingsGitHooks"] = true
- name := ctx.PathParam(":name")
+ name := ctx.PathParam("name")
hook, err := ctx.Repo.GitRepo.GetHook(name)
if err != nil {
if err == git.ErrNotValidHook {
@@ -46,7 +46,7 @@ func GitHooksEdit(ctx *context.Context) {
// GitHooksEditPost response for editing a git hook of a repository
func GitHooksEditPost(ctx *context.Context) {
- name := ctx.PathParam(":name")
+ name := ctx.PathParam("name")
hook, err := ctx.Repo.GitRepo.GetHook(name)
if err != nil {
if err == git.ErrNotValidHook {
diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go
index fad6359668..2df483fa34 100644
--- a/routers/web/repo/setting/lfs.go
+++ b/routers/web/repo/setting/lfs.go
@@ -15,7 +15,6 @@ import (
"strings"
git_model "code.gitea.io/gitea/models/git"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
@@ -25,17 +24,18 @@ import (
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
)
const (
- tplSettingsLFS base.TplName = "repo/settings/lfs"
- tplSettingsLFSLocks base.TplName = "repo/settings/lfs_locks"
- tplSettingsLFSFile base.TplName = "repo/settings/lfs_file"
- tplSettingsLFSFileFind base.TplName = "repo/settings/lfs_file_find"
- tplSettingsLFSPointers base.TplName = "repo/settings/lfs_pointers"
+ tplSettingsLFS templates.TplName = "repo/settings/lfs"
+ tplSettingsLFSLocks templates.TplName = "repo/settings/lfs_locks"
+ tplSettingsLFSFile templates.TplName = "repo/settings/lfs_file"
+ tplSettingsLFSFileFind templates.TplName = "repo/settings/lfs_file_find"
+ tplSettingsLFSPointers templates.TplName = "repo/settings/lfs_pointers"
)
// LFSFiles shows a repository's LFS files
diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go
index f651d8f318..022a24a9ad 100644
--- a/routers/web/repo/setting/protected_branch.go
+++ b/routers/web/repo/setting/protected_branch.go
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/repo"
"code.gitea.io/gitea/services/context"
@@ -26,7 +27,7 @@ import (
)
const (
- tplProtectedBranch base.TplName = "repo/settings/protected_branch"
+ tplProtectedBranch templates.TplName = "repo/settings/protected_branch"
)
// ProtectedBranchRules render the page to protect the repository
diff --git a/routers/web/repo/setting/protected_tag.go b/routers/web/repo/setting/protected_tag.go
index fcfa77aa8c..1730ad4a8b 100644
--- a/routers/web/repo/setting/protected_tag.go
+++ b/routers/web/repo/setting/protected_tag.go
@@ -14,13 +14,14 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
const (
- tplTags base.TplName = "repo/settings/tags"
+ tplTags templates.TplName = "repo/settings/tags"
)
// Tags render the page to protect tags
@@ -169,7 +170,7 @@ func setTagsContext(ctx *context.Context) error {
func selectProtectedTagByContext(ctx *context.Context) *git_model.ProtectedTag {
id := ctx.FormInt64("id")
if id == 0 {
- id = ctx.PathParamInt64(":id")
+ id = ctx.PathParamInt64("id")
}
tag, err := git_model.GetProtectedTagByID(ctx, id)
diff --git a/routers/web/repo/setting/runners.go b/routers/web/repo/setting/runners.go
index 3141d8f42a..94f2ae7a0c 100644
--- a/routers/web/repo/setting/runners.go
+++ b/routers/web/repo/setting/runners.go
@@ -10,8 +10,8 @@ import (
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
actions_shared "code.gitea.io/gitea/routers/web/shared/actions"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
@@ -19,14 +19,14 @@ import (
const (
// TODO: Separate secrets from runners when layout is ready
- tplRepoRunners base.TplName = "repo/settings/actions"
- tplOrgRunners base.TplName = "org/settings/actions"
- tplAdminRunners base.TplName = "admin/actions"
- tplUserRunners base.TplName = "user/settings/actions"
- tplRepoRunnerEdit base.TplName = "repo/settings/runner_edit"
- tplOrgRunnerEdit base.TplName = "org/settings/runners_edit"
- tplAdminRunnerEdit base.TplName = "admin/runners/edit"
- tplUserRunnerEdit base.TplName = "user/settings/runner_edit"
+ tplRepoRunners templates.TplName = "repo/settings/actions"
+ tplOrgRunners templates.TplName = "org/settings/actions"
+ tplAdminRunners templates.TplName = "admin/actions"
+ tplUserRunners templates.TplName = "user/settings/actions"
+ tplRepoRunnerEdit templates.TplName = "repo/settings/runner_edit"
+ tplOrgRunnerEdit templates.TplName = "org/settings/runners_edit"
+ tplAdminRunnerEdit templates.TplName = "admin/runners/edit"
+ tplUserRunnerEdit templates.TplName = "user/settings/runner_edit"
)
type runnersCtx struct {
@@ -36,8 +36,8 @@ type runnersCtx struct {
IsOrg bool
IsAdmin bool
IsUser bool
- RunnersTemplate base.TplName
- RunnerEditTemplate base.TplName
+ RunnersTemplate templates.TplName
+ RunnerEditTemplate templates.TplName
RedirectLink string
}
@@ -147,7 +147,7 @@ func RunnersEdit(ctx *context.Context) {
}
actions_shared.RunnerDetails(ctx, page,
- ctx.PathParamInt64(":runnerid"), rCtx.OwnerID, rCtx.RepoID,
+ ctx.PathParamInt64("runnerid"), rCtx.OwnerID, rCtx.RepoID,
)
ctx.HTML(http.StatusOK, rCtx.RunnerEditTemplate)
}
@@ -158,9 +158,9 @@ func RunnersEditPost(ctx *context.Context) {
ctx.ServerError("getRunnersCtx", err)
return
}
- actions_shared.RunnerDetailsEditPost(ctx, ctx.PathParamInt64(":runnerid"),
+ actions_shared.RunnerDetailsEditPost(ctx, ctx.PathParamInt64("runnerid"),
rCtx.OwnerID, rCtx.RepoID,
- rCtx.RedirectLink+url.PathEscape(ctx.PathParam(":runnerid")))
+ rCtx.RedirectLink+url.PathEscape(ctx.PathParam("runnerid")))
}
func ResetRunnerRegistrationToken(ctx *context.Context) {
@@ -179,7 +179,7 @@ func RunnerDeletePost(ctx *context.Context) {
ctx.ServerError("getRunnersCtx", err)
return
}
- actions_shared.RunnerDeletePost(ctx, ctx.PathParamInt64(":runnerid"), rCtx.RedirectLink, rCtx.RedirectLink+url.PathEscape(ctx.PathParam(":runnerid")))
+ actions_shared.RunnerDeletePost(ctx, ctx.PathParamInt64("runnerid"), rCtx.RedirectLink, rCtx.RedirectLink+url.PathEscape(ctx.PathParam("runnerid")))
}
func RedirectToDefaultSetting(ctx *context.Context) {
diff --git a/routers/web/repo/setting/secrets.go b/routers/web/repo/setting/secrets.go
index df11729344..46cb875f9b 100644
--- a/routers/web/repo/setting/secrets.go
+++ b/routers/web/repo/setting/secrets.go
@@ -8,8 +8,8 @@ import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
shared "code.gitea.io/gitea/routers/web/shared/secrets"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
@@ -17,9 +17,9 @@ import (
const (
// TODO: Separate secrets from runners when layout is ready
- tplRepoSecrets base.TplName = "repo/settings/actions"
- tplOrgSecrets base.TplName = "org/settings/actions"
- tplUserSecrets base.TplName = "user/settings/actions"
+ tplRepoSecrets templates.TplName = "repo/settings/actions"
+ tplOrgSecrets templates.TplName = "org/settings/actions"
+ tplUserSecrets templates.TplName = "user/settings/actions"
)
type secretsCtx struct {
@@ -28,7 +28,7 @@ type secretsCtx struct {
IsRepo bool
IsOrg bool
IsUser bool
- SecretsTemplate base.TplName
+ SecretsTemplate templates.TplName
RedirectLink string
}
diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go
index 717d7cbce1..7399c681e2 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"
@@ -19,7 +18,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
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/indexer/code"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
@@ -28,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/modules/web"
@@ -42,12 +41,12 @@ import (
)
const (
- tplSettingsOptions base.TplName = "repo/settings/options"
- tplCollaboration base.TplName = "repo/settings/collaboration"
- tplBranches base.TplName = "repo/settings/branches"
- tplGithooks base.TplName = "repo/settings/githooks"
- tplGithookEdit base.TplName = "repo/settings/githook_edit"
- tplDeployKeys base.TplName = "repo/settings/deploy_keys"
+ tplSettingsOptions templates.TplName = "repo/settings/options"
+ tplCollaboration templates.TplName = "repo/settings/collaboration"
+ tplBranches templates.TplName = "repo/settings/branches"
+ tplGithooks templates.TplName = "repo/settings/githooks"
+ tplGithookEdit templates.TplName = "repo/settings/githook_edit"
+ tplDeployKeys templates.TplName = "repo/settings/deploy_keys"
)
// SettingsCtxData is a middleware that sets all the general context data for the
@@ -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/setting/variables.go b/routers/web/repo/setting/variables.go
index cc2e619f66..9b5453f043 100644
--- a/routers/web/repo/setting/variables.go
+++ b/routers/web/repo/setting/variables.go
@@ -7,18 +7,18 @@ import (
"errors"
"net/http"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
shared "code.gitea.io/gitea/routers/web/shared/actions"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
)
const (
- tplRepoVariables base.TplName = "repo/settings/actions"
- tplOrgVariables base.TplName = "org/settings/actions"
- tplUserVariables base.TplName = "user/settings/actions"
- tplAdminVariables base.TplName = "admin/actions"
+ tplRepoVariables templates.TplName = "repo/settings/actions"
+ tplOrgVariables templates.TplName = "org/settings/actions"
+ tplUserVariables templates.TplName = "user/settings/actions"
+ tplAdminVariables templates.TplName = "admin/actions"
)
type variablesCtx struct {
@@ -28,7 +28,7 @@ type variablesCtx struct {
IsOrg bool
IsUser bool
IsGlobal bool
- VariablesTemplate base.TplName
+ VariablesTemplate templates.TplName
RedirectLink string
}
diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go
index 8d548c4e3d..1b0ba83af4 100644
--- a/routers/web/repo/setting/webhook.go
+++ b/routers/web/repo/setting/webhook.go
@@ -17,11 +17,11 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -32,11 +32,11 @@ import (
)
const (
- tplHooks base.TplName = "repo/settings/webhook/base"
- tplHookNew base.TplName = "repo/settings/webhook/new"
- tplOrgHookNew base.TplName = "org/settings/hook_new"
- tplUserHookNew base.TplName = "user/settings/hook_new"
- tplAdminHookNew base.TplName = "admin/hook_new"
+ tplHooks templates.TplName = "repo/settings/webhook/base"
+ tplHookNew templates.TplName = "repo/settings/webhook/new"
+ tplOrgHookNew templates.TplName = "org/settings/hook_new"
+ tplUserHookNew templates.TplName = "user/settings/hook_new"
+ tplAdminHookNew templates.TplName = "admin/hook_new"
)
// Webhooks render web hooks list page
@@ -64,7 +64,7 @@ type ownerRepoCtx struct {
IsSystemWebhook bool
Link string
LinkNew string
- NewTemplate base.TplName
+ NewTemplate templates.TplName
}
// getOwnerRepoCtx determines whether this is a repo, owner, or admin (both default and system) context.
@@ -99,9 +99,9 @@ func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) {
if ctx.Data["PageIsAdmin"] == true {
return &ownerRepoCtx{
IsAdmin: true,
- IsSystemWebhook: ctx.PathParam(":configType") == "system-hooks",
+ IsSystemWebhook: ctx.PathParam("configType") == "system-hooks",
Link: path.Join(setting.AppSubURL, "/-/admin/hooks"),
- LinkNew: path.Join(setting.AppSubURL, "/-/admin/", ctx.PathParam(":configType")),
+ LinkNew: path.Join(setting.AppSubURL, "/-/admin/", ctx.PathParam("configType")),
NewTemplate: tplAdminHookNew,
}, nil
}
@@ -110,7 +110,7 @@ func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) {
}
func checkHookType(ctx *context.Context) string {
- hookType := strings.ToLower(ctx.PathParam(":type"))
+ hookType := strings.ToLower(ctx.PathParam("type"))
if !util.SliceContainsString(setting.Webhook.Types, hookType, true) {
ctx.NotFound("checkHookType", nil)
return ""
@@ -592,11 +592,11 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
var w *webhook.Webhook
if orCtx.RepoID > 0 {
- w, err = webhook.GetWebhookByRepoID(ctx, orCtx.RepoID, ctx.PathParamInt64(":id"))
+ w, err = webhook.GetWebhookByRepoID(ctx, orCtx.RepoID, ctx.PathParamInt64("id"))
} else if orCtx.OwnerID > 0 {
- w, err = webhook.GetWebhookByOwnerID(ctx, orCtx.OwnerID, ctx.PathParamInt64(":id"))
+ w, err = webhook.GetWebhookByOwnerID(ctx, orCtx.OwnerID, ctx.PathParamInt64("id"))
} else if orCtx.IsAdmin {
- w, err = webhook.GetSystemOrDefaultWebhook(ctx, ctx.PathParamInt64(":id"))
+ w, err = webhook.GetSystemOrDefaultWebhook(ctx, ctx.PathParamInt64("id"))
}
if err != nil || w == nil {
if webhook.IsErrWebhookNotExist(err) {
@@ -645,7 +645,7 @@ func WebHooksEdit(ctx *context.Context) {
// TestWebhook test if web hook is work fine
func TestWebhook(ctx *context.Context) {
- hookID := ctx.PathParamInt64(":id")
+ hookID := ctx.PathParamInt64("id")
w, err := webhook.GetWebhookByRepoID(ctx, ctx.Repo.Repository.ID, hookID)
if err != nil {
ctx.Flash.Error("GetWebhookByRepoID: " + err.Error())
@@ -706,7 +706,7 @@ func TestWebhook(ctx *context.Context) {
// ReplayWebhook replays a webhook
func ReplayWebhook(ctx *context.Context) {
- hookTaskUUID := ctx.PathParam(":uuid")
+ hookTaskUUID := ctx.PathParam("uuid")
orCtx, w := checkWebhook(ctx)
if ctx.Written() {
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index e43841acd3..14fc9038f3 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -35,6 +35,7 @@ import (
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
@@ -45,12 +46,12 @@ import (
)
const (
- tplRepoEMPTY base.TplName = "repo/empty"
- tplRepoHome base.TplName = "repo/home"
- tplRepoViewList base.TplName = "repo/view_list"
- tplWatchers base.TplName = "repo/watchers"
- tplForks base.TplName = "repo/forks"
- tplMigrating base.TplName = "repo/migrate/migrating"
+ tplRepoEMPTY templates.TplName = "repo/empty"
+ tplRepoHome templates.TplName = "repo/home"
+ tplRepoViewList templates.TplName = "repo/view_list"
+ tplWatchers templates.TplName = "repo/watchers"
+ tplForks templates.TplName = "repo/forks"
+ tplMigrating templates.TplName = "repo/migrate/migrating"
)
type fileInfo struct {
@@ -314,7 +315,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
}
// RenderUserCards render a page show users according the input template
-func RenderUserCards(ctx *context.Context, total int, getter func(opts db.ListOptions) ([]*user_model.User, error), tpl base.TplName) {
+func RenderUserCards(ctx *context.Context, total int, getter func(opts db.ListOptions) ([]*user_model.User, error), tpl templates.TplName) {
page := ctx.FormInt("page")
if page <= 0 {
page = 1
diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go
index b318c4a621..70ba07f9a8 100644
--- a/routers/web/repo/view_home.go
+++ b/routers/web/repo/view_home.go
@@ -276,7 +276,7 @@ func prepareToRenderDirOrFile(entry *git.TreeEntry) func(ctx *context.Context) {
func handleRepoHomeFeed(ctx *context.Context) bool {
if setting.Other.EnableFeed {
- isFeed, _, showFeedType := feed.GetFeedType(ctx.PathParam(":reponame"), ctx.Req)
+ isFeed, _, showFeedType := feed.GetFeedType(ctx.PathParam("reponame"), ctx.Req)
if isFeed {
switch {
case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType):
diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go
index fec72c9253..3fca7cebea 100644
--- a/routers/web/repo/wiki.go
+++ b/routers/web/repo/wiki.go
@@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
@@ -37,11 +38,11 @@ import (
)
const (
- tplWikiStart base.TplName = "repo/wiki/start"
- tplWikiView base.TplName = "repo/wiki/view"
- tplWikiRevision base.TplName = "repo/wiki/revision"
- tplWikiNew base.TplName = "repo/wiki/new"
- tplWikiPages base.TplName = "repo/wiki/pages"
+ tplWikiStart templates.TplName = "repo/wiki/start"
+ tplWikiView templates.TplName = "repo/wiki/view"
+ tplWikiRevision templates.TplName = "repo/wiki/revision"
+ tplWikiNew templates.TplName = "repo/wiki/new"
+ tplWikiPages templates.TplName = "repo/wiki/pages"
)
// MustEnableWiki check if wiki is enabled, if external then redirect
diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go
index f38933226b..6d77bdd2fa 100644
--- a/routers/web/shared/actions/runners.go
+++ b/routers/web/shared/actions/runners.go
@@ -136,9 +136,8 @@ func RunnerResetRegistrationToken(ctx *context.Context, ownerID, repoID int64, r
ctx.ServerError("ResetRunnerRegistrationToken", err)
return
}
-
ctx.Flash.Success(ctx.Tr("actions.runners.reset_registration_token_success"))
- ctx.Redirect(redirectTo)
+ ctx.JSONRedirect(redirectTo)
}
// RunnerDeletePost response for deleting a runner
diff --git a/routers/web/shared/actions/variables.go b/routers/web/shared/actions/variables.go
index 5c5768243a..f895475748 100644
--- a/routers/web/shared/actions/variables.go
+++ b/routers/web/shared/actions/variables.go
@@ -40,7 +40,7 @@ func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL str
}
func UpdateVariable(ctx *context.Context, redirectURL string) {
- id := ctx.PathParamInt64(":variable_id")
+ id := ctx.PathParamInt64("variable_id")
form := web.GetForm(ctx).(*forms.EditVariableForm)
if ok, err := actions_service.UpdateVariable(ctx, id, form.Name, form.Data); err != nil || !ok {
@@ -53,7 +53,7 @@ func UpdateVariable(ctx *context.Context, redirectURL string) {
}
func DeleteVariable(ctx *context.Context, redirectURL string) {
- id := ctx.PathParamInt64(":variable_id")
+ id := ctx.PathParamInt64("variable_id")
if err := actions_service.DeleteVariableByID(ctx, id); err != nil {
log.Error("Delete variable [%d] failed: %v", id, err)
diff --git a/routers/web/shared/packages/packages.go b/routers/web/shared/packages/packages.go
index 1d3cabf71b..a1bcb09850 100644
--- a/routers/web/shared/packages/packages.go
+++ b/routers/web/shared/packages/packages.go
@@ -11,9 +11,9 @@ import (
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
@@ -54,11 +54,11 @@ func setRuleEditContext(ctx *context.Context, pcr *packages_model.PackageCleanup
ctx.Data["AvailableTypes"] = packages_model.TypeList
}
-func PerformRuleAddPost(ctx *context.Context, owner *user_model.User, redirectURL string, template base.TplName) {
+func PerformRuleAddPost(ctx *context.Context, owner *user_model.User, redirectURL string, template templates.TplName) {
performRuleEditPost(ctx, owner, nil, redirectURL, template)
}
-func PerformRuleEditPost(ctx *context.Context, owner *user_model.User, redirectURL string, template base.TplName) {
+func PerformRuleEditPost(ctx *context.Context, owner *user_model.User, redirectURL string, template templates.TplName) {
pcr := getCleanupRuleByContext(ctx, owner)
if pcr == nil {
return
@@ -79,7 +79,7 @@ func PerformRuleEditPost(ctx *context.Context, owner *user_model.User, redirectU
}
}
-func performRuleEditPost(ctx *context.Context, owner *user_model.User, pcr *packages_model.PackageCleanupRule, redirectURL string, template base.TplName) {
+func performRuleEditPost(ctx *context.Context, owner *user_model.User, pcr *packages_model.PackageCleanupRule, redirectURL string, template templates.TplName) {
isEditRule := pcr != nil
if pcr == nil {
diff --git a/routers/web/shared/project/column.go b/routers/web/shared/project/column.go
index ba1f527bea..141c80716f 100644
--- a/routers/web/shared/project/column.go
+++ b/routers/web/shared/project/column.go
@@ -11,7 +11,7 @@ import (
// MoveColumns moves or keeps columns in a project and sorts them inside that project
func MoveColumns(ctx *context.Context) {
- project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id"))
if err != nil {
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
return
diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go
index 4cb0592b4b..d388d2b5d9 100644
--- a/routers/web/shared/user/header.go
+++ b/routers/web/shared/user/header.go
@@ -61,11 +61,20 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{
UserID: ctx.ContextUser.ID,
IncludePrivate: showPrivate,
+ ListOptions: db.ListOptions{
+ Page: 1,
+ // query one more results (without a separate counting) to see whether we need to add the "show more orgs" link
+ PageSize: setting.UI.User.OrgPagingNum + 1,
+ },
})
if err != nil {
ctx.ServerError("FindOrgs", err)
return
}
+ if len(orgs) > setting.UI.User.OrgPagingNum {
+ orgs = orgs[:setting.UI.User.OrgPagingNum]
+ ctx.Data["ShowMoreOrgs"] = true
+ }
ctx.Data["Orgs"] = orgs
ctx.Data["HasOrgsVisible"] = organization.HasOrgsVisible(ctx, orgs, ctx.Doer)
diff --git a/routers/web/user/avatar.go b/routers/web/user/avatar.go
index 7000e25778..f77bd602b3 100644
--- a/routers/web/user/avatar.go
+++ b/routers/web/user/avatar.go
@@ -23,8 +23,8 @@ func cacheableRedirect(ctx *context.Context, location string) {
// AvatarByUserName redirect browser to user avatar of requested size
func AvatarByUserName(ctx *context.Context) {
- userName := ctx.PathParam(":username")
- size := int(ctx.PathParamInt64(":size"))
+ userName := ctx.PathParam("username")
+ size := int(ctx.PathParamInt64("size"))
var user *user_model.User
if strings.ToLower(userName) != user_model.GhostUserLowerName {
@@ -46,7 +46,7 @@ func AvatarByUserName(ctx *context.Context) {
// AvatarByEmailHash redirects the browser to the email avatar link
func AvatarByEmailHash(ctx *context.Context) {
- hash := ctx.PathParam(":hash")
+ hash := ctx.PathParam("hash")
email, err := avatars.GetEmailForHash(ctx, hash)
if err != nil {
ctx.ServerError("invalid avatar hash: "+hash, err)
diff --git a/routers/web/user/code.go b/routers/web/user/code.go
index 785c37b124..f805f2b028 100644
--- a/routers/web/user/code.go
+++ b/routers/web/user/code.go
@@ -8,15 +8,15 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/base"
code_indexer "code.gitea.io/gitea/modules/indexer/code"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
)
const (
- tplUserCode base.TplName = "user/code"
+ tplUserCode templates.TplName = "user/code"
)
// CodeSearch render user/organization code search page
diff --git a/routers/web/user/home.go b/routers/web/user/home.go
index befa33b0c0..b5d3a7294b 100644
--- a/routers/web/user/home.go
+++ b/routers/web/user/home.go
@@ -31,6 +31,7 @@ import (
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/feed"
"code.gitea.io/gitea/routers/web/shared/issue"
@@ -46,16 +47,16 @@ import (
)
const (
- tplDashboard base.TplName = "user/dashboard/dashboard"
- tplIssues base.TplName = "user/dashboard/issues"
- tplMilestones base.TplName = "user/dashboard/milestones"
- tplProfile base.TplName = "user/profile"
+ tplDashboard templates.TplName = "user/dashboard/dashboard"
+ tplIssues templates.TplName = "user/dashboard/issues"
+ tplMilestones templates.TplName = "user/dashboard/milestones"
+ tplProfile templates.TplName = "user/profile"
)
// getDashboardContextUser finds out which context user dashboard is being viewed as .
func getDashboardContextUser(ctx *context.Context) *user_model.User {
ctxUser := ctx.Doer
- orgName := ctx.PathParam(":org")
+ orgName := ctx.PathParam("org")
if len(orgName) > 0 {
ctxUser = ctx.Org.Organization.AsUser()
ctx.Data["Teams"] = ctx.Org.Teams
diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go
index 414cb0be49..732fac2595 100644
--- a/routers/web/user/notification.go
+++ b/routers/web/user/notification.go
@@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
issue_service "code.gitea.io/gitea/services/issue"
@@ -29,9 +30,9 @@ import (
)
const (
- tplNotification base.TplName = "user/notification/notification"
- tplNotificationDiv base.TplName = "user/notification/notification_div"
- tplNotificationSubscriptions base.TplName = "user/notification/notification_subscriptions"
+ tplNotification templates.TplName = "user/notification/notification"
+ tplNotificationDiv templates.TplName = "user/notification/notification_div"
+ tplNotificationSubscriptions templates.TplName = "user/notification/notification_subscriptions"
)
// GetNotificationCount is the middleware that sets the notification count in the context
diff --git a/routers/web/user/package.go b/routers/web/user/package.go
index c6f85ac734..8d78da7c5a 100644
--- a/routers/web/user/package.go
+++ b/routers/web/user/package.go
@@ -14,7 +14,6 @@ import (
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
@@ -24,6 +23,7 @@ import (
debian_module "code.gitea.io/gitea/modules/packages/debian"
rpm_module "code.gitea.io/gitea/modules/packages/rpm"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
packages_helper "code.gitea.io/gitea/routers/api/packages/helper"
@@ -34,10 +34,10 @@ import (
)
const (
- tplPackagesList base.TplName = "user/overview/packages"
- tplPackagesView base.TplName = "package/view"
- tplPackageVersionList base.TplName = "user/overview/package_versions"
- tplPackagesSettings base.TplName = "package/settings"
+ tplPackagesList templates.TplName = "user/overview/packages"
+ tplPackagesView templates.TplName = "package/view"
+ tplPackageVersionList templates.TplName = "user/overview/package_versions"
+ tplPackagesSettings templates.TplName = "package/settings"
)
// ListPackages displays a list of all packages of the context user
@@ -502,7 +502,7 @@ func PackageSettingsPost(ctx *context.Context) {
// DownloadPackageFile serves the content of a package file
func DownloadPackageFile(ctx *context.Context) {
- pf, err := packages_model.GetFileForVersionByID(ctx, ctx.Package.Descriptor.Version.ID, ctx.PathParamInt64(":fileid"))
+ pf, err := packages_model.GetFileForVersionByID(ctx, ctx.Package.Descriptor.Version.ID, ctx.PathParamInt64("fileid"))
if err != nil {
if err == packages_model.ErrPackageFileNotExist {
ctx.NotFound("", err)
diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go
index c41030a5e2..9c014bffdb 100644
--- a/routers/web/user/profile.go
+++ b/routers/web/user/profile.go
@@ -12,15 +12,16 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/renderhelper"
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/log"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/feed"
"code.gitea.io/gitea/routers/web/org"
@@ -30,8 +31,8 @@ import (
)
const (
- tplProfileBigAvatar base.TplName = "shared/user/profile_big_avatar"
- tplFollowUnfollow base.TplName = "org/follow_unfollow"
+ tplProfileBigAvatar templates.TplName = "shared/user/profile_big_avatar"
+ tplFollowUnfollow templates.TplName = "org/follow_unfollow"
)
// OwnerProfile render profile page for a user or a organization (aka, repo owner)
@@ -256,6 +257,21 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
ctx.Data["ProfileReadme"] = profileContent
}
}
+ case "organizations":
+ orgs, count, err := db.FindAndCount[organization.Organization](ctx, organization.FindOrgOptions{
+ UserID: ctx.ContextUser.ID,
+ IncludePrivate: showPrivate,
+ ListOptions: db.ListOptions{
+ Page: page,
+ PageSize: pagingNum,
+ },
+ })
+ if err != nil {
+ ctx.ServerError("GetUserOrganizations", err)
+ return
+ }
+ ctx.Data["Cards"] = orgs
+ total = int(count)
default: // default to "repositories"
repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
ListOptions: db.ListOptions{
@@ -294,31 +310,7 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
}
pager := context.NewPagination(total, pagingNum, page, 5)
- pager.SetDefaultParams(ctx)
- pager.AddParamString("tab", tab)
- if tab != "followers" && tab != "following" && tab != "activity" && tab != "projects" {
- pager.AddParamString("language", language)
- }
- if tab == "activity" {
- if ctx.Data["Date"] != nil {
- pager.AddParamString("date", fmt.Sprint(ctx.Data["Date"]))
- }
- }
- if archived.Has() {
- pager.AddParamString("archived", fmt.Sprint(archived.Value()))
- }
- if fork.Has() {
- pager.AddParamString("fork", fmt.Sprint(fork.Value()))
- }
- if mirror.Has() {
- pager.AddParamString("mirror", fmt.Sprint(mirror.Value()))
- }
- if template.Has() {
- pager.AddParamString("template", fmt.Sprint(template.Value()))
- }
- if private.Has() {
- pager.AddParamString("private", fmt.Sprint(private.Value()))
- }
+ pager.AddParamFromRequest(ctx.Req)
ctx.Data["Page"] = pager
}
diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go
index 7f2dece416..3acc3c7a54 100644
--- a/routers/web/user/setting/account.go
+++ b/routers/web/user/setting/account.go
@@ -10,13 +10,15 @@ 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"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/auth"
@@ -29,7 +31,7 @@ import (
)
const (
- tplSettingsAccount base.TplName = "user/settings/account"
+ tplSettingsAccount templates.TplName = "user/settings/account"
)
// Account renders change user's password, user's email and user suicide page
@@ -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/user/setting/applications.go b/routers/web/user/setting/applications.go
index 356c2ea5de..cf71d01dc1 100644
--- a/routers/web/user/setting/applications.go
+++ b/routers/web/user/setting/applications.go
@@ -10,15 +10,15 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
const (
- tplSettingsApplications base.TplName = "user/settings/applications"
+ tplSettingsApplications templates.TplName = "user/settings/applications"
)
// Applications render manage access token page
diff --git a/routers/web/user/setting/block.go b/routers/web/user/setting/block.go
index d419fb321b..3756495fd2 100644
--- a/routers/web/user/setting/block.go
+++ b/routers/web/user/setting/block.go
@@ -7,14 +7,14 @@ import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
)
const (
- tplSettingsBlockedUsers base.TplName = "user/settings/blocked_users"
+ tplSettingsBlockedUsers templates.TplName = "user/settings/blocked_users"
)
func BlockedUsers(ctx *context.Context) {
diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go
index c492715fb5..127aed9845 100644
--- a/routers/web/user/setting/keys.go
+++ b/routers/web/user/setting/keys.go
@@ -11,8 +11,8 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/context"
@@ -20,7 +20,7 @@ import (
)
const (
- tplSettingsKeys base.TplName = "user/settings/keys"
+ tplSettingsKeys templates.TplName = "user/settings/keys"
)
// Keys render user's SSH/GPG public keys page
diff --git a/routers/web/user/setting/oauth2.go b/routers/web/user/setting/oauth2.go
index 1f485e06c8..d50728c24e 100644
--- a/routers/web/user/setting/oauth2.go
+++ b/routers/web/user/setting/oauth2.go
@@ -4,13 +4,13 @@
package setting
import (
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
)
const (
- tplSettingsOAuthApplicationEdit base.TplName = "user/settings/applications_oauth2_edit"
+ tplSettingsOAuthApplicationEdit templates.TplName = "user/settings/applications_oauth2_edit"
)
func newOAuth2CommonHandlers(userID int64) *OAuth2CommonHandlers {
diff --git a/routers/web/user/setting/oauth2_common.go b/routers/web/user/setting/oauth2_common.go
index e93e9e1954..783deca710 100644
--- a/routers/web/user/setting/oauth2_common.go
+++ b/routers/web/user/setting/oauth2_common.go
@@ -8,7 +8,7 @@ import (
"net/http"
"code.gitea.io/gitea/models/auth"
- "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
@@ -17,10 +17,10 @@ import (
)
type OAuth2CommonHandlers struct {
- OwnerID int64 // 0 for instance-wide, otherwise OrgID or UserID
- BasePathList string // the base URL for the application list page, eg: "/user/setting/applications"
- BasePathEditPrefix string // the base URL for the application edit page, will be appended with app id, eg: "/user/setting/applications/oauth2"
- TplAppEdit base.TplName // the template for the application edit page
+ OwnerID int64 // 0 for instance-wide, otherwise OrgID or UserID
+ BasePathList string // the base URL for the application list page, eg: "/user/setting/applications"
+ BasePathEditPrefix string // the base URL for the application edit page, will be appended with app id, eg: "/user/setting/applications/oauth2"
+ TplAppEdit templates.TplName // the template for the application edit page
}
func (oa *OAuth2CommonHandlers) renderEditPage(ctx *context.Context) {
diff --git a/routers/web/user/setting/packages.go b/routers/web/user/setting/packages.go
index 50521c11c0..9692cd9db0 100644
--- a/routers/web/user/setting/packages.go
+++ b/routers/web/user/setting/packages.go
@@ -8,18 +8,18 @@ import (
"strings"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
chef_module "code.gitea.io/gitea/modules/packages/chef"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
shared "code.gitea.io/gitea/routers/web/shared/packages"
"code.gitea.io/gitea/services/context"
)
const (
- tplSettingsPackages base.TplName = "user/settings/packages"
- tplSettingsPackagesRuleEdit base.TplName = "user/settings/packages_cleanup_rules_edit"
- tplSettingsPackagesRulePreview base.TplName = "user/settings/packages_cleanup_rules_preview"
+ tplSettingsPackages templates.TplName = "user/settings/packages"
+ tplSettingsPackagesRuleEdit templates.TplName = "user/settings/packages_cleanup_rules_edit"
+ tplSettingsPackagesRulePreview templates.TplName = "user/settings/packages_cleanup_rules_preview"
)
func Packages(ctx *context.Context) {
diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go
index 3b051c9b5f..4b3c214096 100644
--- a/routers/web/user/setting/profile.go
+++ b/routers/web/user/setting/profile.go
@@ -19,10 +19,10 @@ 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/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
@@ -35,10 +35,10 @@ import (
)
const (
- tplSettingsProfile base.TplName = "user/settings/profile"
- tplSettingsAppearance base.TplName = "user/settings/appearance"
- tplSettingsOrganization base.TplName = "user/settings/organization"
- tplSettingsRepositories base.TplName = "user/settings/repos"
+ tplSettingsProfile templates.TplName = "user/settings/profile"
+ tplSettingsAppearance templates.TplName = "user/settings/appearance"
+ tplSettingsOrganization templates.TplName = "user/settings/organization"
+ tplSettingsRepositories templates.TplName = "user/settings/repos"
)
// Profile render user's profile page
diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go
index b44cb4dd49..38d1910e2d 100644
--- a/routers/web/user/setting/security/security.go
+++ b/routers/web/user/setting/security/security.go
@@ -11,16 +11,16 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/context"
)
const (
- tplSettingsSecurity base.TplName = "user/settings/security/security"
- tplSettingsTwofaEnroll base.TplName = "user/settings/security/twofa_enroll"
+ tplSettingsSecurity templates.TplName = "user/settings/security/security"
+ tplSettingsTwofaEnroll templates.TplName = "user/settings/security/twofa_enroll"
)
// Security render change user's password page and 2FA
diff --git a/routers/web/user/setting/webhooks.go b/routers/web/user/setting/webhooks.go
index 3732ca27c0..ca7c7ec1ac 100644
--- a/routers/web/user/setting/webhooks.go
+++ b/routers/web/user/setting/webhooks.go
@@ -9,13 +9,13 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
)
const (
- tplSettingsHooks base.TplName = "user/settings/hooks"
+ tplSettingsHooks templates.TplName = "user/settings/hooks"
)
// Webhooks render webhook list page
diff --git a/routers/web/web.go b/routers/web/web.go
index 72ee47bb4c..5e0995545e 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -4,7 +4,6 @@
package web
import (
- gocontext "context"
"net/http"
"strings"
@@ -463,7 +462,7 @@ func registerRoutes(m *web.Router) {
m.Combo("/{runnerid}").Get(repo_setting.RunnersEdit).
Post(web.Bind(forms.EditRunnerForm{}), repo_setting.RunnersEditPost)
m.Post("/{runnerid}/delete", repo_setting.RunnerDeletePost)
- m.Get("/reset_registration_token", repo_setting.ResetRunnerRegistrationToken)
+ m.Post("/reset_registration_token", repo_setting.ResetRunnerRegistrationToken)
})
}
@@ -1020,14 +1019,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)
@@ -1166,7 +1166,7 @@ func registerRoutes(m *web.Router) {
Get(repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff).
Post(reqSignIn, context.RepoMustNotBeArchived(), reqRepoPullsReader, repo.MustAllowPulls, web.Bind(forms.CreateIssueForm{}), repo.SetWhitespaceBehavior, repo.CompareAndPullRequestPost)
}, optSignIn, context.RepoAssignment, reqRepoCodeReader)
- // end "/{username}/{reponame}": find, compare, list (code related)
+ // end "/{username}/{reponame}": repo code: find, compare, list
m.Group("/{username}/{reponame}", func() {
m.Get("/issues/posters", repo.IssuePosters) // it can't use {type:issues|pulls} because it would conflict with other routes like "/pulls/{index}"
@@ -1387,14 +1387,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)
@@ -1442,6 +1443,7 @@ func registerRoutes(m *web.Router) {
m.Combo("/*").
Get(repo.Wiki).
Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost)
+ m.Get("/blob_excerpt/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob)
m.Get("/commit/{sha:[a-f0-9]{7,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
m.Get("/commit/{sha:[a-f0-9]{7,64}}.{ext:patch|diff}", repo.RawDiff)
m.Get("/raw/*", repo.WikiRaw)
@@ -1517,28 +1519,6 @@ func registerRoutes(m *web.Router) {
m.Get("", repo.Branches)
}, repo.MustBeNotEmpty, context.RepoRef())
- m.Group("/blob_excerpt", func() {
- m.Get("/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob)
- }, func(ctx *context.Context) gocontext.CancelFunc {
- // FIXME: refactor this function, use separate routes for wiki/code
- if ctx.FormBool("wiki") {
- ctx.Data["PageIsWiki"] = true
- repo.MustEnableWiki(ctx)
- return nil
- }
-
- if ctx.Written() {
- return nil
- }
- cancel := context.RepoRef()(ctx)
- if ctx.Written() {
- return cancel
- }
-
- repo.MustBeNotEmpty(ctx)
- return cancel
- })
-
m.Group("/media", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownloadOrLFS)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownloadOrLFS)
@@ -1578,6 +1558,8 @@ func registerRoutes(m *web.Router) {
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefBlame)
}, repo.MustBeNotEmpty)
+ m.Get("/blob_excerpt/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob)
+
m.Group("", func() {
m.Get("/graph", repo.Graph)
m.Get("/commit/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
diff --git a/services/actions/init.go b/services/actions/init.go
index 0f49cb6297..7136da05ed 100644
--- a/services/actions/init.go
+++ b/services/actions/init.go
@@ -4,23 +4,68 @@
package actions
import (
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+
+ actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
)
-func Init() {
+func initGlobalRunnerToken(ctx context.Context) error {
+ // use the same env name as the runner, for consistency
+ token := os.Getenv("GITEA_RUNNER_REGISTRATION_TOKEN")
+ tokenFile := os.Getenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE")
+ if token != "" && tokenFile != "" {
+ return errors.New("both GITEA_RUNNER_REGISTRATION_TOKEN and GITEA_RUNNER_REGISTRATION_TOKEN_FILE are set, only one can be used")
+ }
+ if tokenFile != "" {
+ file, err := os.ReadFile(tokenFile)
+ if err != nil {
+ return fmt.Errorf("unable to read GITEA_RUNNER_REGISTRATION_TOKEN_FILE: %w", err)
+ }
+ token = strings.TrimSpace(string(file))
+ }
+ if token == "" {
+ return nil
+ }
+
+ if len(token) < 32 {
+ return errors.New("GITEA_RUNNER_REGISTRATION_TOKEN must be at least 32 random characters")
+ }
+
+ existing, err := actions_model.GetRunnerToken(ctx, token)
+ if err != nil && !errors.Is(err, util.ErrNotExist) {
+ return fmt.Errorf("unable to check existing token: %w", err)
+ }
+ if existing != nil {
+ if !existing.IsActive {
+ log.Warn("The token defined by GITEA_RUNNER_REGISTRATION_TOKEN is already invalidated, please use the latest one from web UI")
+ }
+ return nil
+ }
+ _, err = actions_model.NewRunnerTokenWithValue(ctx, 0, 0, token)
+ return err
+}
+
+func Init(ctx context.Context) error {
if !setting.Actions.Enabled {
- return
+ return nil
}
jobEmitterQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "actions_ready_job", jobEmitterQueueHandler)
if jobEmitterQueue == nil {
- log.Fatal("Unable to create actions_ready_job queue")
+ return errors.New("unable to create actions_ready_job queue")
}
go graceful.GetManager().RunWithCancel(jobEmitterQueue)
notify_service.RegisterNotifier(NewNotifier())
+ return initGlobalRunnerToken(ctx)
}
diff --git a/services/actions/init_test.go b/services/actions/init_test.go
new file mode 100644
index 0000000000..59c321ccd7
--- /dev/null
+++ b/services/actions/init_test.go
@@ -0,0 +1,80 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package actions
+
+import (
+ "os"
+ "testing"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/util"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestMain(m *testing.M) {
+ unittest.MainTest(m, &unittest.TestOptions{
+ FixtureFiles: []string{"action_runner_token.yml"},
+ })
+ os.Exit(m.Run())
+}
+
+func TestInitToken(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ t.Run("NoToken", func(t *testing.T) {
+ _, _ = db.Exec(db.DefaultContext, "DELETE FROM action_runner_token")
+ t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", "")
+ t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE", "")
+ err := initGlobalRunnerToken(db.DefaultContext)
+ require.NoError(t, err)
+ notEmpty, err := db.IsTableNotEmpty(&actions_model.ActionRunnerToken{})
+ require.NoError(t, err)
+ assert.False(t, notEmpty)
+ })
+
+ t.Run("EnvToken", func(t *testing.T) {
+ tokenValue, _ := util.CryptoRandomString(32)
+ t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", tokenValue)
+ t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE", "")
+ err := initGlobalRunnerToken(db.DefaultContext)
+ require.NoError(t, err)
+ token := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
+ assert.True(t, token.IsActive)
+
+ // init with the same token again, should not create a new token
+ err = initGlobalRunnerToken(db.DefaultContext)
+ require.NoError(t, err)
+ token2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
+ assert.Equal(t, token.ID, token2.ID)
+ assert.True(t, token.IsActive)
+ })
+
+ t.Run("EnvFileToken", func(t *testing.T) {
+ tokenValue, _ := util.CryptoRandomString(32)
+ f := t.TempDir() + "/token"
+ _ = os.WriteFile(f, []byte(tokenValue), 0o644)
+ t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", "")
+ t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE", f)
+ err := initGlobalRunnerToken(db.DefaultContext)
+ require.NoError(t, err)
+ token := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
+ assert.True(t, token.IsActive)
+
+ // if the env token is invalidated by another new token, then it shouldn't be active anymore
+ _, err = actions_model.NewRunnerToken(db.DefaultContext, 0, 0)
+ require.NoError(t, err)
+ token = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
+ assert.False(t, token.IsActive)
+ })
+
+ t.Run("InvalidToken", func(t *testing.T) {
+ t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", "abc")
+ err := initGlobalRunnerToken(db.DefaultContext)
+ assert.ErrorContains(t, err, "must be at least")
+ })
+}
diff --git a/services/asymkey/deploy_key.go b/services/asymkey/deploy_key.go
index 324688c534..9e5a6d6292 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 3505b26f69..1cdc39933d 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/interface.go b/services/auth/interface.go
index ece28af12d..275b4dd56c 100644
--- a/services/auth/interface.go
+++ b/services/auth/interface.go
@@ -8,12 +8,11 @@ import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/session"
- "code.gitea.io/gitea/modules/web/middleware"
)
-// DataStore represents a data store
-type DataStore middleware.ContextDataStore
+type DataStore = reqctx.ContextDataProvider
// SessionStore represents a session store
type SessionStore session.Store
diff --git a/services/auth/oauth2_test.go b/services/auth/oauth2_test.go
index b706847e8e..0d9e793cf3 100644
--- a/services/auth/oauth2_test.go
+++ b/services/auth/oauth2_test.go
@@ -9,7 +9,7 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/services/actions"
"github.com/stretchr/testify/assert"
@@ -23,7 +23,7 @@ func TestUserIDFromToken(t *testing.T) {
token, err := actions.CreateAuthorizationToken(RunningTaskID, 1, 2)
assert.NoError(t, err)
- ds := make(middleware.ContextData)
+ ds := make(reqctx.ContextData)
o := OAuth2{}
uid := o.userIDFromToken(context.Background(), token, ds)
diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go
index 020e5784dc..6a6c60cd40 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,7 +32,12 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
return nil, user_model.ErrUserNotExist{Name: loginName}
}
// Fallback.
+ // 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 sr.Mail == "" {
diff --git a/services/auth/sspi.go b/services/auth/sspi.go
index 7f8a03a4c6..8ac83dcb04 100644
--- a/services/auth/sspi.go
+++ b/services/auth/sspi.go
@@ -13,10 +13,10 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/auth/source/sspi"
gitea_context "code.gitea.io/gitea/services/context"
@@ -25,7 +25,7 @@ import (
)
const (
- tplSignIn base.TplName = "user/auth/signin"
+ tplSignIn templates.TplName = "user/auth/signin"
)
type SSPIAuth interface {
diff --git a/services/context/api.go b/services/context/api.go
index b45e80a329..7b604c5ea1 100644
--- a/services/context/api.go
+++ b/services/context/api.go
@@ -5,7 +5,6 @@
package context
import (
- "context"
"fmt"
"net/http"
"net/url"
@@ -212,17 +211,15 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
func APIContexter() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
- base, baseCleanUp := NewBaseContext(w, req)
+ base := NewBaseContext(w, req)
ctx := &APIContext{
Base: base,
Cache: cache.GetCache(),
Repo: &Repository{PullRequest: &PullRequest{}},
Org: &APIOrganization{},
}
- defer baseCleanUp()
- ctx.Base.AppendContextValue(apiContextKey, ctx)
- ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
+ ctx.SetContextValue(apiContextKey, ctx)
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
@@ -267,31 +264,22 @@ func (ctx *APIContext) NotFound(objs ...any) {
// ReferencesGitRepo injects the GitRepo into the Context
// you can optional skip the IsEmpty check
-func ReferencesGitRepo(allowEmpty ...bool) func(ctx *APIContext) (cancel context.CancelFunc) {
- return func(ctx *APIContext) (cancel context.CancelFunc) {
+func ReferencesGitRepo(allowEmpty ...bool) func(ctx *APIContext) {
+ return func(ctx *APIContext) {
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty && !(len(allowEmpty) != 0 && allowEmpty[0]) {
- return nil
+ return
}
// For API calls.
if ctx.Repo.GitRepo == nil {
- gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
+ var err error
+ ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, fmt.Sprintf("Open Repository %v failed", ctx.Repo.Repository.FullName()), err)
- return cancel
- }
- ctx.Repo.GitRepo = gitRepo
- // We opened it, we should close it
- return func() {
- // If it's been set to nil then assume someone else has closed it.
- if ctx.Repo.GitRepo != nil {
- _ = ctx.Repo.GitRepo.Close()
- }
+ return
}
}
-
- return cancel
}
}
diff --git a/services/context/base.go b/services/context/base.go
index d627095584..7a39353e09 100644
--- a/services/context/base.go
+++ b/services/context/base.go
@@ -9,81 +9,36 @@ import (
"html/template"
"io"
"net/http"
- "net/url"
- "strconv"
"strings"
- "time"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/web/middleware"
-
- "github.com/go-chi/chi/v5"
)
-type contextValuePair struct {
- key any
- valueFn func() any
-}
-
type BaseContextKeyType struct{}
var BaseContextKey BaseContextKeyType
type Base struct {
- originCtx context.Context
- contextValues []contextValuePair
+ context.Context
+ reqctx.RequestDataStore
Resp ResponseWriter
Req *http.Request
// Data is prepared by ContextDataStore middleware, this field only refers to the pre-created/prepared ContextData.
// Although it's mainly used for MVC templates, sometimes it's also used to pass data between middlewares/handler
- Data middleware.ContextData
+ Data reqctx.ContextData
// Locale is mainly for Web context, although the API context also uses it in some cases: message response, form validation
Locale translation.Locale
}
-func (b *Base) Deadline() (deadline time.Time, ok bool) {
- return b.originCtx.Deadline()
-}
-
-func (b *Base) Done() <-chan struct{} {
- return b.originCtx.Done()
-}
-
-func (b *Base) Err() error {
- return b.originCtx.Err()
-}
-
-func (b *Base) Value(key any) any {
- for _, pair := range b.contextValues {
- if pair.key == key {
- return pair.valueFn()
- }
- }
- return b.originCtx.Value(key)
-}
-
-func (b *Base) AppendContextValueFunc(key any, valueFn func() any) any {
- b.contextValues = append(b.contextValues, contextValuePair{key, valueFn})
- return b
-}
-
-func (b *Base) AppendContextValue(key, value any) any {
- b.contextValues = append(b.contextValues, contextValuePair{key, func() any { return value }})
- return b
-}
-
-func (b *Base) GetData() middleware.ContextData {
- return b.Data
-}
-
// AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header
func (b *Base) AppendAccessControlExposeHeaders(names ...string) {
val := b.RespHeader().Get("Access-Control-Expose-Headers")
@@ -147,93 +102,6 @@ func (b *Base) RemoteAddr() string {
return b.Req.RemoteAddr
}
-// PathParam returns the param in request path, eg: "/{var}" => "/a%2fb", then `var == "a/b"`
-func (b *Base) PathParam(name string) string {
- s, err := url.PathUnescape(b.PathParamRaw(name))
- if err != nil && !setting.IsProd {
- panic("Failed to unescape path param: " + err.Error() + ", there seems to be a double-unescaping bug")
- }
- return s
-}
-
-// PathParamRaw returns the raw param in request path, eg: "/{var}" => "/a%2fb", then `var == "a%2fb"`
-func (b *Base) PathParamRaw(name string) string {
- return chi.URLParam(b.Req, strings.TrimPrefix(name, ":"))
-}
-
-// PathParamInt64 returns the param in request path as int64
-func (b *Base) PathParamInt64(p string) int64 {
- v, _ := strconv.ParseInt(b.PathParam(p), 10, 64)
- return v
-}
-
-// SetPathParam set request path params into routes
-func (b *Base) SetPathParam(k, v string) {
- chiCtx := chi.RouteContext(b)
- chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v))
-}
-
-// FormString returns the first value matching the provided key in the form as a string
-func (b *Base) FormString(key string) string {
- return b.Req.FormValue(key)
-}
-
-// FormStrings returns a string slice for the provided key from the form
-func (b *Base) FormStrings(key string) []string {
- if b.Req.Form == nil {
- if err := b.Req.ParseMultipartForm(32 << 20); err != nil {
- return nil
- }
- }
- if v, ok := b.Req.Form[key]; ok {
- return v
- }
- return nil
-}
-
-// FormTrim returns the first value for the provided key in the form as a space trimmed string
-func (b *Base) FormTrim(key string) string {
- return strings.TrimSpace(b.Req.FormValue(key))
-}
-
-// FormInt returns the first value for the provided key in the form as an int
-func (b *Base) FormInt(key string) int {
- v, _ := strconv.Atoi(b.Req.FormValue(key))
- return v
-}
-
-// FormInt64 returns the first value for the provided key in the form as an int64
-func (b *Base) FormInt64(key string) int64 {
- v, _ := strconv.ParseInt(b.Req.FormValue(key), 10, 64)
- return v
-}
-
-// FormBool returns true if the value for the provided key in the form is "1", "true" or "on"
-func (b *Base) FormBool(key string) bool {
- s := b.Req.FormValue(key)
- v, _ := strconv.ParseBool(s)
- v = v || strings.EqualFold(s, "on")
- return v
-}
-
-// FormOptionalBool returns an optional.Some(true) or optional.Some(false) if the value
-// for the provided key exists in the form else it returns optional.None[bool]()
-func (b *Base) FormOptionalBool(key string) optional.Option[bool] {
- value := b.Req.FormValue(key)
- if len(value) == 0 {
- return optional.None[bool]()
- }
- s := b.Req.FormValue(key)
- v, _ := strconv.ParseBool(s)
- v = v || strings.EqualFold(s, "on")
- return optional.Some(v)
-}
-
-func (b *Base) SetFormString(key, value string) {
- _ = b.Req.FormValue(key) // force parse form
- b.Req.Form.Set(key, value)
-}
-
// PlainTextBytes renders bytes as plain text
func (b *Base) plainTextInternal(skip, status int, bs []byte) {
statusPrefix := status / 100
@@ -295,13 +163,6 @@ func (b *Base) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) {
http.ServeContent(b.Resp, b.Req, opts.Filename, opts.LastModified, r)
}
-// Close frees all resources hold by Context
-func (b *Base) cleanUp() {
- if b.Req != nil && b.Req.MultipartForm != nil {
- _ = b.Req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
- }
-}
-
func (b *Base) Tr(msg string, args ...any) template.HTML {
return b.Locale.Tr(msg, args...)
}
@@ -310,17 +171,28 @@ func (b *Base) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
return b.Locale.TrN(cnt, key1, keyN, args...)
}
-func NewBaseContext(resp http.ResponseWriter, req *http.Request) (b *Base, closeFunc func()) {
- b = &Base{
- originCtx: req.Context(),
- Req: req,
- Resp: WrapResponseWriter(resp),
- Locale: middleware.Locale(resp, req),
- Data: middleware.GetContextData(req.Context()),
+func NewBaseContext(resp http.ResponseWriter, req *http.Request) *Base {
+ ds := reqctx.GetRequestDataStore(req.Context())
+ b := &Base{
+ Context: req.Context(),
+ RequestDataStore: ds,
+ Req: req,
+ Resp: WrapResponseWriter(resp),
+ Locale: middleware.Locale(resp, req),
+ Data: ds.GetData(),
}
b.Req = b.Req.WithContext(b)
- b.AppendContextValue(BaseContextKey, b)
- b.AppendContextValue(translation.ContextKey, b.Locale)
- b.AppendContextValue(httplib.RequestContextKey, b.Req)
- return b, b.cleanUp
+ ds.SetContextValue(BaseContextKey, b)
+ ds.SetContextValue(translation.ContextKey, b.Locale)
+ ds.SetContextValue(httplib.RequestContextKey, b.Req)
+ return b
+}
+
+func NewBaseContextForTest(resp http.ResponseWriter, req *http.Request) *Base {
+ if !setting.IsInTesting {
+ panic("This function is only for testing")
+ }
+ ctx := reqctx.NewRequestContextForTest(req.Context())
+ *req = *req.WithContext(ctx)
+ return NewBaseContext(resp, req)
}
diff --git a/services/context/base_form.go b/services/context/base_form.go
new file mode 100644
index 0000000000..ddf9734f57
--- /dev/null
+++ b/services/context/base_form.go
@@ -0,0 +1,72 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package context
+
+import (
+ "strconv"
+ "strings"
+
+ "code.gitea.io/gitea/modules/optional"
+)
+
+// FormString returns the first value matching the provided key in the form as a string
+func (b *Base) FormString(key string) string {
+ return b.Req.FormValue(key)
+}
+
+// FormStrings returns a string slice for the provided key from the form
+func (b *Base) FormStrings(key string) []string {
+ if b.Req.Form == nil {
+ if err := b.Req.ParseMultipartForm(32 << 20); err != nil {
+ return nil
+ }
+ }
+ if v, ok := b.Req.Form[key]; ok {
+ return v
+ }
+ return nil
+}
+
+// FormTrim returns the first value for the provided key in the form as a space trimmed string
+func (b *Base) FormTrim(key string) string {
+ return strings.TrimSpace(b.Req.FormValue(key))
+}
+
+// FormInt returns the first value for the provided key in the form as an int
+func (b *Base) FormInt(key string) int {
+ v, _ := strconv.Atoi(b.Req.FormValue(key))
+ return v
+}
+
+// FormInt64 returns the first value for the provided key in the form as an int64
+func (b *Base) FormInt64(key string) int64 {
+ v, _ := strconv.ParseInt(b.Req.FormValue(key), 10, 64)
+ return v
+}
+
+// FormBool returns true if the value for the provided key in the form is "1", "true" or "on"
+func (b *Base) FormBool(key string) bool {
+ s := b.Req.FormValue(key)
+ v, _ := strconv.ParseBool(s)
+ v = v || strings.EqualFold(s, "on")
+ return v
+}
+
+// FormOptionalBool returns an optional.Some(true) or optional.Some(false) if the value
+// for the provided key exists in the form else it returns optional.None[bool]()
+func (b *Base) FormOptionalBool(key string) optional.Option[bool] {
+ value := b.Req.FormValue(key)
+ if len(value) == 0 {
+ return optional.None[bool]()
+ }
+ s := b.Req.FormValue(key)
+ v, _ := strconv.ParseBool(s)
+ v = v || strings.EqualFold(s, "on")
+ return optional.Some(v)
+}
+
+func (b *Base) SetFormString(key, value string) {
+ _ = b.Req.FormValue(key) // force parse form
+ b.Req.Form.Set(key, value)
+}
diff --git a/services/context/base_path.go b/services/context/base_path.go
new file mode 100644
index 0000000000..3678deaff9
--- /dev/null
+++ b/services/context/base_path.go
@@ -0,0 +1,47 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package context
+
+import (
+ "net/url"
+ "strconv"
+ "strings"
+
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/go-chi/chi/v5"
+)
+
+// PathParam returns the param in request path, eg: "/{var}" => "/a%2fb", then `var == "a/b"`
+func (b *Base) PathParam(name string) string {
+ s, err := url.PathUnescape(b.PathParamRaw(name))
+ if err != nil && !setting.IsProd {
+ panic("Failed to unescape path param: " + err.Error() + ", there seems to be a double-unescaping bug")
+ }
+ return s
+}
+
+// PathParamRaw returns the raw param in request path, eg: "/{var}" => "/a%2fb", then `var == "a%2fb"`
+func (b *Base) PathParamRaw(name string) string {
+ if strings.HasPrefix(name, ":") {
+ setting.PanicInDevOrTesting("path param should not start with ':'")
+ name = name[1:]
+ }
+ return chi.URLParam(b.Req, name)
+}
+
+// PathParamInt64 returns the param in request path as int64
+func (b *Base) PathParamInt64(p string) int64 {
+ v, _ := strconv.ParseInt(b.PathParam(p), 10, 64)
+ return v
+}
+
+// SetPathParam set request path params into routes
+func (b *Base) SetPathParam(name, value string) {
+ if strings.HasPrefix(name, ":") {
+ setting.PanicInDevOrTesting("path param should not start with ':'")
+ name = name[1:]
+ }
+ chi.RouteContext(b).URLParams.Add(name, url.PathEscape(value))
+}
diff --git a/services/context/base_test.go b/services/context/base_test.go
index 823f20e00b..b936b76f58 100644
--- a/services/context/base_test.go
+++ b/services/context/base_test.go
@@ -14,6 +14,7 @@ import (
)
func TestRedirect(t *testing.T) {
+ setting.IsInTesting = true
req, _ := http.NewRequest("GET", "/", nil)
cases := []struct {
@@ -28,10 +29,9 @@ func TestRedirect(t *testing.T) {
}
for _, c := range cases {
resp := httptest.NewRecorder()
- b, cleanup := NewBaseContext(resp, req)
+ b := NewBaseContextForTest(resp, req)
resp.Header().Add("Set-Cookie", (&http.Cookie{Name: setting.SessionConfig.CookieName, Value: "dummy"}).String())
b.Redirect(c.url)
- cleanup()
has := resp.Header().Get("Set-Cookie") == "i_like_gitea=dummy"
assert.Equal(t, c.keep, has, "url = %q", c.url)
}
@@ -39,9 +39,8 @@ func TestRedirect(t *testing.T) {
req, _ = http.NewRequest("GET", "/", nil)
resp := httptest.NewRecorder()
req.Header.Add("HX-Request", "true")
- b, cleanup := NewBaseContext(resp, req)
+ b := NewBaseContextForTest(resp, req)
b.Redirect("/other")
- cleanup()
assert.Equal(t, "/other", resp.Header().Get("HX-Redirect"))
assert.Equal(t, http.StatusNoContent, resp.Code)
}
diff --git a/services/context/captcha.go b/services/context/captcha.go
index 41afe0e7d2..9272e7a65a 100644
--- a/services/context/captcha.go
+++ b/services/context/captcha.go
@@ -7,13 +7,13 @@ import (
"fmt"
"sync"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/hcaptcha"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/mcaptcha"
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/turnstile"
"gitea.com/go-chi/captcha"
@@ -60,7 +60,7 @@ const (
// VerifyCaptcha verifies Captcha data
// No-op if captchas are not enabled
-func VerifyCaptcha(ctx *Context, tpl base.TplName, form any) {
+func VerifyCaptcha(ctx *Context, tpl templates.TplName, form any) {
if !setting.Service.EnableCaptcha {
return
}
diff --git a/services/context/context.go b/services/context/context.go
index 812a8c27ee..6715c5663d 100644
--- a/services/context/context.go
+++ b/services/context/context.go
@@ -18,7 +18,6 @@ import (
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/cache"
- "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
@@ -32,7 +31,7 @@ import (
// Render represents a template render
type Render interface {
TemplateLookup(tmpl string, templateCtx context.Context) (templates.TemplateExecutor, error)
- HTML(w io.Writer, status int, name string, data any, templateCtx context.Context) error
+ HTML(w io.Writer, status int, name templates.TplName, data any, templateCtx context.Context) error
}
// Context represents context of a request.
@@ -153,14 +152,9 @@ func Contexter() func(next http.Handler) http.Handler {
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
- base, baseCleanUp := NewBaseContext(resp, req)
- defer baseCleanUp()
+ base := NewBaseContext(resp, req)
ctx := NewWebContext(base, rnd, session.GetContextSession(req))
-
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
- if setting.IsProd && !setting.IsInTesting {
- ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
- }
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
ctx.Data["Link"] = ctx.Link
@@ -168,9 +162,7 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.PageData = map[string]any{}
ctx.Data["PageData"] = ctx.PageData
- ctx.Base.AppendContextValue(WebContextKey, ctx)
- ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
-
+ ctx.Base.SetContextValue(WebContextKey, ctx)
ctx.Csrf = NewCSRFProtector(csrfOpts)
// Get the last flash message from cookie
diff --git a/services/context/context_response.go b/services/context/context_response.go
index c43a649b49..c7044791eb 100644
--- a/services/context/context_response.go
+++ b/services/context/context_response.go
@@ -17,7 +17,6 @@ import (
"time"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -63,10 +62,10 @@ func (ctx *Context) RedirectToCurrentSite(location ...string) {
ctx.Redirect(setting.AppSubURL + "/")
}
-const tplStatus500 base.TplName = "status/500"
+const tplStatus500 templates.TplName = "status/500"
// HTML calls Context.HTML and renders the template to HTTP response
-func (ctx *Context) HTML(status int, name base.TplName) {
+func (ctx *Context) HTML(status int, name templates.TplName) {
log.Debug("Template: %s", name)
tmplStartTime := time.Now()
@@ -77,7 +76,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms"
}
- err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data, ctx.TemplateContext)
+ err := ctx.Render.HTML(ctx.Resp, status, name, ctx.Data, ctx.TemplateContext)
if err == nil || errors.Is(err, syscall.EPIPE) {
return
}
@@ -94,7 +93,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
// JSONTemplate renders the template as JSON response
// keep in mind that the template is processed in HTML context, so JSON-things should be handled carefully, eg: by JSEscape
-func (ctx *Context) JSONTemplate(tmpl base.TplName) {
+func (ctx *Context) JSONTemplate(tmpl templates.TplName) {
t, err := ctx.Render.TemplateLookup(string(tmpl), nil)
if err != nil {
ctx.ServerError("unable to find template", err)
@@ -107,14 +106,14 @@ func (ctx *Context) JSONTemplate(tmpl base.TplName) {
}
// RenderToHTML renders the template content to a HTML string
-func (ctx *Context) RenderToHTML(name base.TplName, data map[string]any) (template.HTML, error) {
+func (ctx *Context) RenderToHTML(name templates.TplName, data any) (template.HTML, error) {
var buf strings.Builder
- err := ctx.Render.HTML(&buf, 0, string(name), data, ctx.TemplateContext)
+ err := ctx.Render.HTML(&buf, 0, name, data, ctx.TemplateContext)
return template.HTML(buf.String()), err
}
// RenderWithErr used for page has form validation but need to prompt error to users.
-func (ctx *Context) RenderWithErr(msg any, tpl base.TplName, form any) {
+func (ctx *Context) RenderWithErr(msg any, tpl templates.TplName, form any) {
if form != nil {
middleware.AssignForm(form, ctx.Data)
}
@@ -151,7 +150,7 @@ func (ctx *Context) notFoundInternal(logMsg string, logErr error) {
ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
ctx.Data["Title"] = "Page Not Found"
- ctx.HTML(http.StatusNotFound, base.TplName("status/404"))
+ ctx.HTML(http.StatusNotFound, templates.TplName("status/404"))
}
// ServerError displays a 500 (Internal Server Error) page and prints the given error, if any.
diff --git a/services/context/context_test.go b/services/context/context_test.go
index 984593398d..54044644f0 100644
--- a/services/context/context_test.go
+++ b/services/context/context_test.go
@@ -26,6 +26,7 @@ func TestRemoveSessionCookieHeader(t *testing.T) {
}
func TestRedirectToCurrentSite(t *testing.T) {
+ setting.IsInTesting = true
defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")()
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
cases := []struct {
@@ -40,8 +41,7 @@ func TestRedirectToCurrentSite(t *testing.T) {
t.Run(c.location, func(t *testing.T) {
req := &http.Request{URL: &url.URL{Path: "/"}}
resp := httptest.NewRecorder()
- base, baseCleanUp := NewBaseContext(resp, req)
- defer baseCleanUp()
+ base := NewBaseContextForTest(resp, req)
ctx := NewWebContext(base, nil, nil)
ctx.RedirectToCurrentSite(c.location)
redirect := test.RedirectURL(resp)
diff --git a/services/context/org.go b/services/context/org.go
index bf482fa754..be87cef7a3 100644
--- a/services/context/org.go
+++ b/services/context/org.go
@@ -40,7 +40,7 @@ func (org *Organization) CanReadUnit(ctx *Context, unitType unit.Type) bool {
}
func GetOrganizationByParams(ctx *Context) {
- orgName := ctx.PathParam(":org")
+ orgName := ctx.PathParam("org")
var err error
@@ -220,7 +220,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
ctx.Data["NumTeams"] = len(ctx.Org.Teams)
}
- teamName := ctx.PathParam(":team")
+ teamName := ctx.PathParam("team")
if len(teamName) > 0 {
teamExists := false
for _, team := range ctx.Org.Teams {
diff --git a/services/context/package.go b/services/context/package.go
index 271b61e99c..e98e01acbb 100644
--- a/services/context/package.go
+++ b/services/context/package.go
@@ -153,12 +153,10 @@ func PackageContexter() func(next http.Handler) http.Handler {
renderer := templates.HTMLRenderer()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
- base, baseCleanUp := NewBaseContext(resp, req)
- defer baseCleanUp()
-
+ base := NewBaseContext(resp, req)
// it is still needed when rendering 500 page in a package handler
ctx := NewWebContext(base, renderer, nil)
- ctx.Base.AppendContextValue(WebContextKey, ctx)
+ ctx.SetContextValue(WebContextKey, ctx)
next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
diff --git a/services/context/private.go b/services/context/private.go
index 8b41949f60..51857da8fe 100644
--- a/services/context/private.go
+++ b/services/context/private.go
@@ -64,11 +64,9 @@ func GetPrivateContext(req *http.Request) *PrivateContext {
func PrivateContexter() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
- base, baseCleanUp := NewBaseContext(w, req)
+ base := NewBaseContext(w, req)
ctx := &PrivateContext{Base: base}
- defer baseCleanUp()
- ctx.Base.AppendContextValue(privateContextKey, ctx)
-
+ ctx.SetContextValue(privateContextKey, ctx)
next.ServeHTTP(ctx.Resp, ctx.Req)
})
}
@@ -78,8 +76,15 @@ func PrivateContexter() func(http.Handler) http.Handler {
// This function should be used when there is a need for work to continue even if the request has been cancelled.
// Primarily this affects hook/post-receive and hook/proc-receive both of which need to continue working even if
// the underlying request has timed out from the ssh/http push
-func OverrideContext(ctx *PrivateContext) (cancel context.CancelFunc) {
- // We now need to override the request context as the base for our work because even if the request is cancelled we have to continue this work
- ctx.Override, _, cancel = process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI), process.RequestProcessType, true)
- return cancel
+func OverrideContext() func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ // We now need to override the request context as the base for our work because even if the request is cancelled we have to continue this work
+ ctx := GetPrivateContext(req)
+ var finished func()
+ ctx.Override, _, finished = process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI), process.RequestProcessType, true)
+ defer finished()
+ next.ServeHTTP(ctx.Resp, ctx.Req)
+ })
+ }
}
diff --git a/services/context/repo.go b/services/context/repo.go
index 9b54439110..b537a05036 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"
@@ -317,8 +316,8 @@ func ComposeGoGetImport(ctx context.Context, owner, repo string) string {
// This is particular a workaround for "go get" command which does not respect
// .netrc file.
func EarlyResponseForGoGetMeta(ctx *Context) {
- username := ctx.PathParam(":username")
- reponame := strings.TrimSuffix(ctx.PathParam(":reponame"), ".git")
+ username := ctx.PathParam("username")
+ reponame := strings.TrimSuffix(ctx.PathParam("reponame"), ".git")
if username == "" || reponame == "" {
ctx.PlainText(http.StatusBadRequest, "invalid repository path")
return
@@ -337,8 +336,8 @@ func EarlyResponseForGoGetMeta(ctx *Context) {
// RedirectToRepo redirect to a differently-named repository
func RedirectToRepo(ctx *Base, redirectRepoID int64) {
- ownerName := ctx.PathParam(":username")
- previousRepoName := ctx.PathParam(":reponame")
+ ownerName := ctx.PathParam("username")
+ previousRepoName := ctx.PathParam("reponame")
repo, err := repo_model.GetRepositoryByID(ctx, redirectRepoID)
if err != nil {
@@ -398,11 +397,13 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
}
// RepoAssignment returns a middleware to handle repository assignment
-func RepoAssignment(ctx *Context) context.CancelFunc {
+func RepoAssignment(ctx *Context) {
if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce {
// FIXME: it should panic in dev/test modes to have a clear behavior
- log.Trace("RepoAssignment was exec already, skipping second call ...")
- return nil
+ if !setting.IsProd || setting.IsInTesting {
+ panic("RepoAssignment should not be executed twice")
+ }
+ return
}
ctx.Data["repoAssignmentExecuted"] = true
@@ -411,8 +412,8 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
err error
)
- userName := ctx.PathParam(":username")
- repoName := ctx.PathParam(":reponame")
+ userName := ctx.PathParam("username")
+ repoName := ctx.PathParam("reponame")
repoName = strings.TrimSuffix(repoName, ".git")
if setting.Other.EnableFeed {
repoName = strings.TrimSuffix(repoName, ".rss")
@@ -430,7 +431,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
// https://github.com/golang/go/issues/19760
if ctx.FormString("go-get") == "1" {
EarlyResponseForGoGetMeta(ctx)
- return nil
+ return
}
if redirectUserID, err := user_model.LookupUserRedirect(ctx, userName); err == nil {
@@ -443,7 +444,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
} else {
ctx.ServerError("GetUserByName", err)
}
- return nil
+ return
}
}
ctx.Repo.Owner = owner
@@ -455,7 +456,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
if strings.HasSuffix(repoName, ".wiki") {
// ctx.Req.URL.Path does not have the preceding appSubURL - any redirect must have this added
// Now we happen to know that all of our paths are: /:username/:reponame/whatever_else
- originalRepoName := ctx.PathParam(":reponame")
+ originalRepoName := ctx.PathParam("reponame")
redirectRepoName := strings.TrimSuffix(repoName, ".wiki")
redirectRepoName += originalRepoName[len(redirectRepoName)+5:]
redirectPath := strings.Replace(
@@ -468,7 +469,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
redirectPath += "?" + ctx.Req.URL.RawQuery
}
ctx.Redirect(path.Join(setting.AppSubURL, redirectPath))
- return nil
+ return
}
// Get repository.
@@ -481,7 +482,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
} else if repo_model.IsErrRedirectNotExist(err) {
if ctx.FormString("go-get") == "1" {
EarlyResponseForGoGetMeta(ctx)
- return nil
+ return
}
ctx.NotFound("GetRepositoryByName", nil)
} else {
@@ -490,13 +491,13 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
} else {
ctx.ServerError("GetRepositoryByName", err)
}
- return nil
+ return
}
repo.Owner = owner
repoAssignment(ctx, repo)
if ctx.Written() {
- return nil
+ return
}
ctx.Repo.RepoLink = repo.Link()
@@ -521,7 +522,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
})
if err != nil {
ctx.ServerError("GetReleaseCountByRepoID", err)
- return nil
+ return
}
ctx.Data["NumReleases"], err = db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{
// only show draft releases for users who can write, read-only users shouldn't see draft releases.
@@ -530,7 +531,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
})
if err != nil {
ctx.ServerError("GetReleaseCountByRepoID", err)
- return nil
+ return
}
ctx.Data["Title"] = owner.Name + "/" + repo.Name
@@ -547,14 +548,14 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
canSignedUserFork, err := repo_module.CanUserForkRepo(ctx, ctx.Doer, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("CanUserForkRepo", err)
- return nil
+ return
}
ctx.Data["CanSignedUserFork"] = canSignedUserFork
userAndOrgForks, err := repo_model.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("GetForksByUserAndOrgs", err)
- return nil
+ return
}
ctx.Data["UserAndOrgForks"] = userAndOrgForks
@@ -588,14 +589,14 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
if repo.IsFork {
RetrieveBaseRepo(ctx, repo)
if ctx.Written() {
- return nil
+ return
}
}
if repo.IsGenerated() {
RetrieveTemplateRepo(ctx, repo)
if ctx.Written() {
- return nil
+ return
}
}
@@ -610,10 +611,18 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
if !isHomeOrSettings {
ctx.Redirect(ctx.Repo.RepoLink)
}
- return nil
+ return
}
- gitRepo, err := gitrepo.OpenRepository(ctx, repo)
+ if ctx.Repo.GitRepo != nil {
+ if !setting.IsProd || setting.IsInTesting {
+ panic("RepoAssignment: GitRepo should be nil")
+ }
+ _ = ctx.Repo.GitRepo.Close()
+ ctx.Repo.GitRepo = nil
+ }
+
+ ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, repo)
if err != nil {
if strings.Contains(err.Error(), "repository does not exist") || strings.Contains(err.Error(), "no such file or directory") {
log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err)
@@ -623,28 +632,16 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
if !isHomeOrSettings {
ctx.Redirect(ctx.Repo.RepoLink)
}
- return nil
+ return
}
ctx.ServerError("RepoAssignment Invalid repo "+repo.FullName(), err)
- return nil
- }
- if ctx.Repo.GitRepo != nil {
- ctx.Repo.GitRepo.Close()
- }
- ctx.Repo.GitRepo = gitRepo
-
- // We opened it, we should close it
- cancel := func() {
- // If it's been set to nil then assume someone else has closed it.
- if ctx.Repo.GitRepo != nil {
- ctx.Repo.GitRepo.Close()
- }
+ return
}
// Stop at this point when the repo is empty.
if ctx.Repo.Repository.IsEmpty {
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
- return cancel
+ return
}
branchOpts := git_model.FindBranchOptions{
@@ -655,7 +652,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
branchesTotal, err := db.Count[git_model.Branch](ctx, branchOpts)
if err != nil {
ctx.ServerError("CountBranches", err)
- return cancel
+ return
}
// non-empty repo should have at least 1 branch, so this repository's branches haven't been synced yet
@@ -663,7 +660,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
branchesTotal, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
if err != nil {
ctx.ServerError("SyncRepoBranches", err)
- return cancel
+ return
}
}
@@ -671,7 +668,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
// If no branch is set in the request URL, try to guess a default one.
if len(ctx.Repo.BranchName) == 0 {
- if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
+ if len(ctx.Repo.Repository.DefaultBranch) > 0 && ctx.Repo.GitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
} else {
ctx.Repo.BranchName, _ = gitrepo.GetDefaultBranch(ctx, ctx.Repo.Repository)
@@ -709,15 +706,15 @@ 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
+ return
}
if err := repoTransfer.LoadAttributes(ctx); err != nil {
ctx.ServerError("LoadRecipient", err)
- return cancel
+ return
}
ctx.Data["RepoTransfer"] = repoTransfer
@@ -732,7 +729,6 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
ctx.Data["GoDocDirectory"] = fullURLPrefix + "{/dir}"
ctx.Data["GoDocFile"] = fullURLPrefix + "{/dir}/{file}#L{line}"
}
- return cancel
}
// RepoRefType type of repo reference
@@ -751,7 +747,7 @@ const headRefName = "HEAD"
// RepoRef handles repository reference names when the ref name is not
// explicitly given
-func RepoRef() func(*Context) context.CancelFunc {
+func RepoRef() func(*Context) {
// since no ref name is explicitly specified, ok to just use branch
return RepoRefByType(RepoRefBranch)
}
@@ -866,9 +862,9 @@ type RepoRefByTypeOptions struct {
// RepoRefByType handles repository reference name for a specific type
// of repository reference
-func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func(*Context) context.CancelFunc {
+func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func(*Context) {
opt := util.OptionalArg(opts)
- return func(ctx *Context) (cancel context.CancelFunc) {
+ return func(ctx *Context) {
refType := detectRefType
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty {
@@ -876,7 +872,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
ctx.Repo.IsViewBranch = true
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
ctx.Data["TreePath"] = ""
- return nil
+ return
}
var (
@@ -885,17 +881,10 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
)
if ctx.Repo.GitRepo == nil {
- ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
+ ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
if err != nil {
ctx.ServerError(fmt.Sprintf("Open Repository %v failed", ctx.Repo.Repository.FullName()), err)
- return nil
- }
- // We opened it, we should close it
- cancel = func() {
- // If it's been set to nil then assume someone else has closed it.
- if ctx.Repo.GitRepo != nil {
- ctx.Repo.GitRepo.Close()
- }
+ return
}
}
@@ -925,7 +914,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
ctx.Repo.Repository.MarkAsBrokenEmpty()
} else {
ctx.ServerError("GetBranchCommit", err)
- return cancel
+ return
}
ctx.Repo.IsViewBranch = true
} else {
@@ -942,7 +931,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
ctx.Flash.Info(ctx.Tr("repo.branch.renamed", refName, renamedBranchName))
link := setting.AppSubURL + strings.Replace(ctx.Req.URL.EscapedPath(), util.PathEscapeSegments(refName), util.PathEscapeSegments(renamedBranchName), 1)
ctx.Redirect(link)
- return cancel
+ return
}
if refType == RepoRefBranch && ctx.Repo.GitRepo.IsBranchExist(refName) {
@@ -952,7 +941,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
if err != nil {
ctx.ServerError("GetBranchCommit", err)
- return cancel
+ return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if refType == RepoRefTag && ctx.Repo.GitRepo.IsTagExist(refName) {
@@ -963,10 +952,10 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
if err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound("GetTagCommit", err)
- return cancel
+ return
}
ctx.ServerError("GetTagCommit", err)
- return cancel
+ return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if git.IsStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refName, 7) {
@@ -976,7 +965,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
if err != nil {
ctx.NotFound("GetCommit", err)
- return cancel
+ return
}
// If short commit ID add canonical link header
if len(refName) < ctx.Repo.GetObjectFormat().FullLength() {
@@ -985,10 +974,10 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
}
} else {
if opt.IgnoreNotExistErr {
- return cancel
+ return
}
ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
- return cancel
+ return
}
if guessLegacyPath {
@@ -1000,7 +989,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
ctx.Repo.BranchNameSubURL(),
util.PathEscapeSegments(ctx.Repo.TreePath))
ctx.Redirect(redirect)
- return cancel
+ return
}
}
@@ -1018,12 +1007,10 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
if err != nil {
ctx.ServerError("GetCommitsCount", err)
- return cancel
+ return
}
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache())
-
- return cancel
}
}
diff --git a/services/context/upload/upload.go b/services/context/upload/upload.go
index cefd13ebb6..da4370a433 100644
--- a/services/context/upload/upload.go
+++ b/services/context/upload/upload.go
@@ -97,8 +97,8 @@ func AddUploadContext(ctx *context.Context, uploadType string) {
} else if uploadType == "comment" {
ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/issues/attachments"
ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/issues/attachments/remove"
- if len(ctx.PathParam(":index")) > 0 {
- ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/" + url.PathEscape(ctx.PathParam(":index")) + "/attachments"
+ if len(ctx.PathParam("index")) > 0 {
+ ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/" + url.PathEscape(ctx.PathParam("index")) + "/attachments"
} else {
ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/attachments"
}
diff --git a/services/context/user.go b/services/context/user.go
index b0e855e923..dbc35e198d 100644
--- a/services/context/user.go
+++ b/services/context/user.go
@@ -33,7 +33,7 @@ func UserAssignmentWeb() func(ctx *Context) {
// UserIDAssignmentAPI returns a middleware to handle context-user assignment for api routes
func UserIDAssignmentAPI() func(ctx *APIContext) {
return func(ctx *APIContext) {
- userID := ctx.PathParamInt64(":user-id")
+ userID := ctx.PathParamInt64("user-id")
if ctx.IsSigned && ctx.Doer.ID == userID {
ctx.ContextUser = ctx.Doer
@@ -59,7 +59,7 @@ func UserAssignmentAPI() func(ctx *APIContext) {
}
func userAssignment(ctx *Base, doer *user_model.User, errCb func(int, string, any)) (contextUser *user_model.User) {
- username := ctx.PathParam(":username")
+ username := ctx.PathParam("username")
if doer != nil && doer.LowerName == strings.ToLower(username) {
contextUser = doer
diff --git a/services/contexttest/context_tests.go b/services/contexttest/context_tests.go
index 3c3fa76e3c..b0f71cad20 100644
--- a/services/contexttest/context_tests.go
+++ b/services/contexttest/context_tests.go
@@ -21,6 +21,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
@@ -40,7 +41,7 @@ func mockRequest(t *testing.T, reqPath string) *http.Request {
requestURL, err := url.Parse(path)
assert.NoError(t, err)
req := &http.Request{Method: method, Host: requestURL.Host, URL: requestURL, Form: maps.Clone(requestURL.Query()), Header: http.Header{}}
- req = req.WithContext(middleware.WithContextData(req.Context()))
+ req = req.WithContext(reqctx.NewRequestContextForTest(req.Context()))
return req
}
@@ -60,17 +61,16 @@ func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*cont
}
resp := httptest.NewRecorder()
req := mockRequest(t, reqPath)
- base, baseCleanUp := context.NewBaseContext(resp, req)
- _ = baseCleanUp // during test, it doesn't need to do clean up. TODO: this can be improved later
+ base := context.NewBaseContext(resp, req)
base.Data = middleware.GetContextData(req.Context())
base.Locale = &translation.MockLocale{}
chiCtx := chi.NewRouteContext()
ctx := context.NewWebContext(base, opt.Render, nil)
- ctx.AppendContextValue(context.WebContextKey, ctx)
- ctx.AppendContextValue(chi.RouteCtxKey, chiCtx)
+ ctx.SetContextValue(context.WebContextKey, ctx)
+ ctx.SetContextValue(chi.RouteCtxKey, chiCtx)
if opt.SessionStore != nil {
- ctx.AppendContextValue(session.MockStoreContextKey, opt.SessionStore)
+ ctx.SetContextValue(session.MockStoreContextKey, opt.SessionStore)
ctx.Session = opt.SessionStore
}
ctx.Cache = cache.GetCache()
@@ -83,27 +83,24 @@ func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*cont
func MockAPIContext(t *testing.T, reqPath string) (*context.APIContext, *httptest.ResponseRecorder) {
resp := httptest.NewRecorder()
req := mockRequest(t, reqPath)
- base, baseCleanUp := context.NewBaseContext(resp, req)
+ base := context.NewBaseContext(resp, req)
base.Data = middleware.GetContextData(req.Context())
base.Locale = &translation.MockLocale{}
ctx := &context.APIContext{Base: base}
- _ = baseCleanUp // during test, it doesn't need to do clean up. TODO: this can be improved later
-
chiCtx := chi.NewRouteContext()
- ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx)
+ ctx.SetContextValue(chi.RouteCtxKey, chiCtx)
return ctx, resp
}
func MockPrivateContext(t *testing.T, reqPath string) (*context.PrivateContext, *httptest.ResponseRecorder) {
resp := httptest.NewRecorder()
req := mockRequest(t, reqPath)
- base, baseCleanUp := context.NewBaseContext(resp, req)
+ base := context.NewBaseContext(resp, req)
base.Data = middleware.GetContextData(req.Context())
base.Locale = &translation.MockLocale{}
ctx := &context.PrivateContext{Base: base}
- _ = baseCleanUp // during test, it doesn't need to do clean up. TODO: this can be improved later
chiCtx := chi.NewRouteContext()
- ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx)
+ ctx.SetContextValue(chi.RouteCtxKey, chiCtx)
return ctx, resp
}
@@ -183,7 +180,7 @@ func (tr *MockRender) TemplateLookup(tmpl string, _ gocontext.Context) (template
return nil, nil
}
-func (tr *MockRender) HTML(w io.Writer, status int, _ string, _ any, _ gocontext.Context) error {
+func (tr *MockRender) HTML(w io.Writer, status int, _ templates.TplName, _ any, _ gocontext.Context) error {
if resp, ok := w.(http.ResponseWriter); ok {
resp.WriteHeader(status)
}
diff --git a/services/convert/pull.go b/services/convert/pull.go
index ddaaa300a4..a1ab7eeb8e 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/repository.go b/services/convert/repository.go
index e026d0f440..88ccd88fcf 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/feed/notifier.go b/services/feed/notifier.go
index d941027c35..702eb5ad53 100644
--- a/services/feed/notifier.go
+++ b/services/feed/notifier.go
@@ -109,7 +109,7 @@ func (a *actionNotifier) CreateIssueComment(ctx context.Context, doer *user_mode
IsPrivate: issue.Repo.IsPrivate,
}
- truncatedContent, truncatedRight := util.SplitStringAtByteN(comment.Content, 200)
+ truncatedContent, truncatedRight := util.EllipsisDisplayStringX(comment.Content, 200)
if truncatedRight != "" {
// in case the content is in a Latin family language, we remove the last broken word.
lastSpaceIdx := strings.LastIndex(truncatedContent, " ")
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index 7647c74e46..b14171787e 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 bb1722039e..0b5a855d42 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/issue/commit.go b/services/issue/commit.go
index 0579e0f5c5..963d0359fd 100644
--- a/services/issue/commit.go
+++ b/services/issue/commit.go
@@ -188,15 +188,19 @@ func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_m
continue
}
}
- isClosed := ref.Action == references.XRefActionCloses
- if isClosed && len(ref.TimeLog) > 0 {
- if err := issueAddTime(ctx, refIssue, doer, c.Timestamp, ref.TimeLog); err != nil {
+
+ refIssue.Repo = refRepo
+ if ref.Action == references.XRefActionCloses && !refIssue.IsClosed {
+ if len(ref.TimeLog) > 0 {
+ if err := issueAddTime(ctx, refIssue, doer, c.Timestamp, ref.TimeLog); err != nil {
+ return err
+ }
+ }
+ if err := CloseIssue(ctx, refIssue, doer, c.Sha1); err != nil {
return err
}
- }
- if isClosed != refIssue.IsClosed {
- refIssue.Repo = refRepo
- if err := ChangeStatus(ctx, refIssue, doer, c.Sha1, isClosed); err != nil {
+ } else if ref.Action == references.XRefActionReopens && refIssue.IsClosed {
+ if err := ReopenIssue(ctx, refIssue, doer, c.Sha1); err != nil {
return err
}
}
diff --git a/services/issue/milestone.go b/services/issue/milestone.go
index ff645744a7..beb6f131a9 100644
--- a/services/issue/milestone.go
+++ b/services/issue/milestone.go
@@ -59,6 +59,10 @@ func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *is
}
}
+ if issue.MilestoneID == 0 {
+ issue.Milestone = nil
+ }
+
return nil
}
diff --git a/services/issue/milestone_test.go b/services/issue/milestone_test.go
index 42b910166f..bf5abc85b7 100644
--- a/services/issue/milestone_test.go
+++ b/services/issue/milestone_test.go
@@ -23,6 +23,7 @@ func TestChangeMilestoneAssign(t *testing.T) {
oldMilestoneID := issue.MilestoneID
issue.MilestoneID = 2
+ assert.NoError(t, issue.LoadMilestone(db.DefaultContext))
assert.NoError(t, ChangeMilestoneAssign(db.DefaultContext, issue, doer, oldMilestoneID))
unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{
IssueID: issue.ID,
@@ -31,4 +32,11 @@ func TestChangeMilestoneAssign(t *testing.T) {
OldMilestoneID: oldMilestoneID,
})
unittest.CheckConsistencyFor(t, &issues_model.Milestone{}, &issues_model.Issue{})
+ assert.NotNil(t, issue.Milestone)
+
+ oldMilestoneID = issue.MilestoneID
+ issue.MilestoneID = 0
+ assert.NoError(t, ChangeMilestoneAssign(db.DefaultContext, issue, doer, oldMilestoneID))
+ assert.EqualValues(t, 0, issue.MilestoneID)
+ assert.Nil(t, issue.Milestone)
}
diff --git a/services/issue/status.go b/services/issue/status.go
index 967c29bd22..e18b891175 100644
--- a/services/issue/status.go
+++ b/services/issue/status.go
@@ -6,34 +6,54 @@ package issue
import (
"context"
+ "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
notify_service "code.gitea.io/gitea/services/notify"
)
-// ChangeStatus changes issue status to open or closed.
-// closed means the target status
-// Fix me: you should check whether the current issue status is same to the target status before call this function
-// as in function changeIssueStatus we will return WasClosedError, even the issue status and target status are both open
-func ChangeStatus(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, commitID string, closed bool) error {
- comment, err := issues_model.ChangeIssueStatus(ctx, issue, doer, closed)
+// CloseIssue close an issue.
+func CloseIssue(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, commitID string) error {
+ dbCtx, committer, err := db.TxContext(ctx)
if err != nil {
- if issues_model.IsErrDependenciesLeft(err) && closed {
- if err := issues_model.FinishIssueStopwatchIfPossible(ctx, doer, issue); err != nil {
+ return err
+ }
+ defer committer.Close()
+
+ comment, err := issues_model.CloseIssue(dbCtx, issue, doer)
+ if err != nil {
+ if issues_model.IsErrDependenciesLeft(err) {
+ if err := issues_model.FinishIssueStopwatchIfPossible(dbCtx, doer, issue); err != nil {
log.Error("Unable to stop stopwatch for issue[%d]#%d: %v", issue.ID, issue.Index, err)
}
}
return err
}
- if closed {
- if err := issues_model.FinishIssueStopwatchIfPossible(ctx, doer, issue); err != nil {
- return err
- }
+ if err := issues_model.FinishIssueStopwatchIfPossible(dbCtx, doer, issue); err != nil {
+ return err
}
- notify_service.IssueChangeStatus(ctx, doer, commitID, issue, comment, closed)
+ if err := committer.Commit(); err != nil {
+ return err
+ }
+ committer.Close()
+
+ notify_service.IssueChangeStatus(ctx, doer, commitID, issue, comment, true)
+
+ return nil
+}
+
+// ReopenIssue reopen an issue.
+// FIXME: If some issues dependent this one are closed, should we also reopen them?
+func ReopenIssue(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, commitID string) error {
+ comment, err := issues_model.ReopenIssue(ctx, issue, doer)
+ if err != nil {
+ return err
+ }
+
+ notify_service.IssueChangeStatus(ctx, doer, commitID, issue, comment, false)
return nil
}
diff --git a/services/mailer/mail.go b/services/mailer/mail.go
index ee2c8c0963..52e19bde6f 100644
--- a/services/mailer/mail.go
+++ b/services/mailer/mail.go
@@ -21,11 +21,11 @@ import (
"code.gitea.io/gitea/models/renderhelper"
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/emoji"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"
incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload"
@@ -34,14 +34,14 @@ import (
)
const (
- mailAuthActivate base.TplName = "auth/activate"
- mailAuthActivateEmail base.TplName = "auth/activate_email"
- mailAuthResetPassword base.TplName = "auth/reset_passwd"
- mailAuthRegisterNotify base.TplName = "auth/register_notify"
+ mailAuthActivate templates.TplName = "auth/activate"
+ mailAuthActivateEmail templates.TplName = "auth/activate_email"
+ mailAuthResetPassword templates.TplName = "auth/reset_passwd"
+ mailAuthRegisterNotify templates.TplName = "auth/register_notify"
- mailNotifyCollaborator base.TplName = "notify/collaborator"
+ mailNotifyCollaborator templates.TplName = "notify/collaborator"
- mailRepoTransferNotify base.TplName = "notify/repo_transfer"
+ mailRepoTransferNotify templates.TplName = "notify/repo_transfer"
// There's no actual limit for subject in RFC 5322
mailMaxSubjectRunes = 256
@@ -63,7 +63,7 @@ func SendTestMail(email string) error {
}
// sendUserMail sends a mail to the user
-func sendUserMail(language string, u *user_model.User, tpl base.TplName, code, subject, info string) {
+func sendUserMail(language string, u *user_model.User, tpl templates.TplName, code, subject, info string) {
locale := translation.NewLocale(language)
data := map[string]any{
"locale": locale,
@@ -93,7 +93,8 @@ func SendActivateAccountMail(locale translation.Locale, u *user_model.User) {
// No mail service configured
return
}
- sendUserMail(locale.Language(), u, mailAuthActivate, u.GenerateEmailActivateCode(u.Email), locale.TrString("mail.activate_account"), "activate account")
+ opts := &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeActivateAccount}
+ sendUserMail(locale.Language(), u, mailAuthActivate, user_model.GenerateUserTimeLimitCode(opts, u), locale.TrString("mail.activate_account"), "activate account")
}
// SendResetPasswordMail sends a password reset mail to the user
@@ -103,7 +104,8 @@ func SendResetPasswordMail(u *user_model.User) {
return
}
locale := translation.NewLocale(u.Language)
- sendUserMail(u.Language, u, mailAuthResetPassword, u.GenerateEmailActivateCode(u.Email), locale.TrString("mail.reset_password"), "recover account")
+ opts := &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeResetPassword}
+ sendUserMail(u.Language, u, mailAuthResetPassword, user_model.GenerateUserTimeLimitCode(opts, u), locale.TrString("mail.reset_password"), "recover account")
}
// SendActivateEmailMail sends confirmation email to confirm new email address
@@ -113,11 +115,12 @@ func SendActivateEmailMail(u *user_model.User, email string) {
return
}
locale := translation.NewLocale(u.Language)
+ opts := &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeActivateEmail, NewEmail: email}
data := map[string]any{
"locale": locale,
"DisplayName": u.DisplayName(),
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale),
- "Code": u.GenerateEmailActivateCode(email),
+ "Code": user_model.GenerateUserTimeLimitCode(opts, u),
"Email": email,
"Language": locale.Language(),
}
diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go
index 1d73d77612..796d63d27a 100644
--- a/services/mailer/mail_release.go
+++ b/services/mailer/mail_release.go
@@ -10,16 +10,16 @@ import (
"code.gitea.io/gitea/models/renderhelper"
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/log"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
sender_service "code.gitea.io/gitea/services/mailer/sender"
)
const (
- tplNewReleaseMail base.TplName = "release"
+ tplNewReleaseMail templates.TplName = "release"
)
// MailNewRelease send new release notify to all repo watchers.
diff --git a/services/mailer/mail_team_invite.go b/services/mailer/mail_team_invite.go
index 4f2d5e4ca7..5ca44442f3 100644
--- a/services/mailer/mail_team_invite.go
+++ b/services/mailer/mail_team_invite.go
@@ -11,15 +11,15 @@ import (
org_model "code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
sender_service "code.gitea.io/gitea/services/mailer/sender"
)
const (
- tplTeamInviteMail base.TplName = "team_invite"
+ tplTeamInviteMail templates.TplName = "team_invite"
)
// MailTeamInvite sends team invites
diff --git a/services/mailer/sender/message.go b/services/mailer/sender/message.go
index db20675572..55f03e4f7e 100644
--- a/services/mailer/sender/message.go
+++ b/services/mailer/sender/message.go
@@ -10,9 +10,9 @@ import (
"strings"
"time"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
"github.com/jaytaylor/html2text"
gomail "github.com/wneessen/go-mail"
@@ -54,7 +54,7 @@ func (m *Message) ToMessage() *gomail.Msg {
plainBody, err := html2text.FromString(m.Body)
if err != nil || setting.MailService.SendAsPlainText {
- if strings.Contains(base.TruncateString(m.Body, 100), "") {
+ if strings.Contains(util.TruncateRunes(m.Body, 100), "") {
log.Warn("Mail contains HTML but configured to send as plain text.")
}
msg.SetBodyString("text/plain", plainBody)
diff --git a/services/markup/main_test.go b/services/markup/main_test.go
index 5553ebc058..d04a18bfa1 100644
--- a/services/markup/main_test.go
+++ b/services/markup/main_test.go
@@ -11,6 +11,6 @@ import (
func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{
- FixtureFiles: []string{"user.yml", "repository.yml", "access.yml", "repo_unit.yml"},
+ FixtureFiles: []string{"user.yml", "repository.yml", "access.yml", "repo_unit.yml", "issue.yml"},
})
}
diff --git a/services/markup/processorhelper.go b/services/markup/renderhelper.go
similarity index 88%
rename from services/markup/processorhelper.go
rename to services/markup/renderhelper.go
index 1f1abf496a..4b9852b48b 100644
--- a/services/markup/processorhelper.go
+++ b/services/markup/renderhelper.go
@@ -11,9 +11,10 @@ import (
gitea_context "code.gitea.io/gitea/services/context"
)
-func ProcessorHelper() *markup.RenderHelperFuncs {
+func FormalRenderHelperFuncs() *markup.RenderHelperFuncs {
return &markup.RenderHelperFuncs{
RenderRepoFileCodePreview: renderRepoFileCodePreview,
+ RenderRepoIssueIconTitle: renderRepoIssueIconTitle,
IsUsernameMentionable: func(ctx context.Context, username string) bool {
mentionedUser, err := user.GetUserByName(ctx, username)
if err != nil {
diff --git a/services/markup/processorhelper_codepreview.go b/services/markup/renderhelper_codepreview.go
similarity index 97%
rename from services/markup/processorhelper_codepreview.go
rename to services/markup/renderhelper_codepreview.go
index 0500e57e46..170c70c409 100644
--- a/services/markup/processorhelper_codepreview.go
+++ b/services/markup/renderhelper_codepreview.go
@@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/indexer/code"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
gitea_context "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/repository/files"
)
@@ -46,7 +47,7 @@ func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePrevie
return "", err
}
if !perms.CanRead(unit.TypeCode) {
- return "", fmt.Errorf("no permission")
+ return "", util.ErrPermissionDenied
}
gitRepo, err := gitrepo.OpenRepository(ctx, dbRepo)
diff --git a/services/markup/processorhelper_codepreview_test.go b/services/markup/renderhelper_codepreview_test.go
similarity index 95%
rename from services/markup/processorhelper_codepreview_test.go
rename to services/markup/renderhelper_codepreview_test.go
index 154e4e8e44..ea945584b4 100644
--- a/services/markup/processorhelper_codepreview_test.go
+++ b/services/markup/renderhelper_codepreview_test.go
@@ -9,12 +9,13 @@ import (
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
-func TestProcessorHelperCodePreview(t *testing.T) {
+func TestRenderHelperCodePreview(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
@@ -79,5 +80,5 @@ func TestProcessorHelperCodePreview(t *testing.T) {
LineStart: 1,
LineStop: 10,
})
- assert.ErrorContains(t, err, "no permission")
+ assert.ErrorIs(t, err, util.ErrPermissionDenied)
}
diff --git a/services/markup/renderhelper_issueicontitle.go b/services/markup/renderhelper_issueicontitle.go
new file mode 100644
index 0000000000..53a508e908
--- /dev/null
+++ b/services/markup/renderhelper_issueicontitle.go
@@ -0,0 +1,66 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package markup
+
+import (
+ "context"
+ "fmt"
+ "html/template"
+
+ "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/models/perm/access"
+ "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/modules/htmlutil"
+ "code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/util"
+ gitea_context "code.gitea.io/gitea/services/context"
+)
+
+func renderRepoIssueIconTitle(ctx context.Context, opts markup.RenderIssueIconTitleOptions) (_ template.HTML, err error) {
+ webCtx, ok := ctx.Value(gitea_context.WebContextKey).(*gitea_context.Context)
+ if !ok {
+ return "", fmt.Errorf("context is not a web context")
+ }
+
+ textIssueIndex := fmt.Sprintf("(#%d)", opts.IssueIndex)
+ dbRepo := webCtx.Repo.Repository
+ if opts.OwnerName != "" {
+ dbRepo, err = repo.GetRepositoryByOwnerAndName(ctx, opts.OwnerName, opts.RepoName)
+ if err != nil {
+ return "", err
+ }
+ textIssueIndex = fmt.Sprintf("(%s/%s#%d)", dbRepo.OwnerName, dbRepo.Name, opts.IssueIndex)
+ }
+ if dbRepo == nil {
+ return "", nil
+ }
+
+ issue, err := issues.GetIssueByIndex(ctx, dbRepo.ID, opts.IssueIndex)
+ if err != nil {
+ return "", err
+ }
+
+ if webCtx.Repo.Repository == nil || dbRepo.ID != webCtx.Repo.Repository.ID {
+ perms, err := access.GetUserRepoPermission(ctx, dbRepo, webCtx.Doer)
+ if err != nil {
+ return "", err
+ }
+ if !perms.CanReadIssuesOrPulls(issue.IsPull) {
+ return "", util.ErrPermissionDenied
+ }
+ }
+
+ if issue.IsPull {
+ if err = issue.LoadPullRequest(ctx); err != nil {
+ return "", err
+ }
+ }
+
+ htmlIcon, err := webCtx.RenderToHTML("shared/issueicon", issue)
+ if err != nil {
+ return "", err
+ }
+
+ return htmlutil.HTMLFormat(`%s %s %s`, opts.LinkHref, htmlIcon, issue.Title, textIssueIndex), nil
+}
diff --git a/services/markup/renderhelper_issueicontitle_test.go b/services/markup/renderhelper_issueicontitle_test.go
new file mode 100644
index 0000000000..adce8401e0
--- /dev/null
+++ b/services/markup/renderhelper_issueicontitle_test.go
@@ -0,0 +1,49 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package markup
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/contexttest"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRenderHelperIssueIconTitle(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
+ ctx.Repo.Repository = unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
+ htm, err := renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{
+ LinkHref: "/link",
+ IssueIndex: 1,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, `octicon-issue-opened(16/text green) issue1 (#1)`, string(htm))
+
+ ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
+ htm, err = renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{
+ OwnerName: "user2",
+ RepoName: "repo1",
+ LinkHref: "/link",
+ IssueIndex: 1,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, `octicon-issue-opened(16/text green) issue1 (user2/repo1#1)`, string(htm))
+
+ ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
+ _, err = renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{
+ OwnerName: "user2",
+ RepoName: "repo2",
+ LinkHref: "/link",
+ IssueIndex: 2,
+ })
+ assert.ErrorIs(t, err, util.ErrPermissionDenied)
+}
diff --git a/services/markup/processorhelper_test.go b/services/markup/renderhelper_mention_test.go
similarity index 57%
rename from services/markup/processorhelper_test.go
rename to services/markup/renderhelper_mention_test.go
index 170edae0e0..c244fa3d21 100644
--- a/services/markup/processorhelper_test.go
+++ b/services/markup/renderhelper_mention_test.go
@@ -18,7 +18,7 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestProcessorHelper(t *testing.T) {
+func TestRenderHelperMention(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
userPublic := "user1"
@@ -32,23 +32,22 @@ func TestProcessorHelper(t *testing.T) {
unittest.AssertCount(t, &user.User{Name: userNoSuch}, 0)
// when using general context, use user's visibility to check
- assert.True(t, ProcessorHelper().IsUsernameMentionable(context.Background(), userPublic))
- assert.False(t, ProcessorHelper().IsUsernameMentionable(context.Background(), userLimited))
- assert.False(t, ProcessorHelper().IsUsernameMentionable(context.Background(), userPrivate))
- assert.False(t, ProcessorHelper().IsUsernameMentionable(context.Background(), userNoSuch))
+ assert.True(t, FormalRenderHelperFuncs().IsUsernameMentionable(context.Background(), userPublic))
+ assert.False(t, FormalRenderHelperFuncs().IsUsernameMentionable(context.Background(), userLimited))
+ assert.False(t, FormalRenderHelperFuncs().IsUsernameMentionable(context.Background(), userPrivate))
+ assert.False(t, FormalRenderHelperFuncs().IsUsernameMentionable(context.Background(), userNoSuch))
// when using web context, use user.IsUserVisibleToViewer to check
req, err := http.NewRequest("GET", "/", nil)
assert.NoError(t, err)
- base, baseCleanUp := gitea_context.NewBaseContext(httptest.NewRecorder(), req)
- defer baseCleanUp()
+ base := gitea_context.NewBaseContextForTest(httptest.NewRecorder(), req)
giteaCtx := gitea_context.NewWebContext(base, &contexttest.MockRender{}, nil)
- assert.True(t, ProcessorHelper().IsUsernameMentionable(giteaCtx, userPublic))
- assert.False(t, ProcessorHelper().IsUsernameMentionable(giteaCtx, userPrivate))
+ assert.True(t, FormalRenderHelperFuncs().IsUsernameMentionable(giteaCtx, userPublic))
+ assert.False(t, FormalRenderHelperFuncs().IsUsernameMentionable(giteaCtx, userPrivate))
giteaCtx.Doer, err = user.GetUserByName(db.DefaultContext, userPrivate)
assert.NoError(t, err)
- assert.True(t, ProcessorHelper().IsUsernameMentionable(giteaCtx, userPublic))
- assert.True(t, ProcessorHelper().IsUsernameMentionable(giteaCtx, userPrivate))
+ assert.True(t, FormalRenderHelperFuncs().IsUsernameMentionable(giteaCtx, userPublic))
+ assert.True(t, FormalRenderHelperFuncs().IsUsernameMentionable(giteaCtx, userPrivate))
}
diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go
index eb21b6534b..9e06b77b66 100644
--- a/services/migrations/gitea_uploader.go
+++ b/services/migrations/gitea_uploader.go
@@ -19,7 +19,6 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
- base_module "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/label"
@@ -409,7 +408,7 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
RepoID: g.repo.ID,
Repo: g.repo,
Index: issue.Number,
- Title: base_module.TruncateString(issue.Title, 255),
+ Title: util.TruncateRunes(issue.Title, 255),
Content: issue.Content,
Ref: issue.Ref,
IsClosed: issue.State == "closed",
diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go
index d0ad6d0139..51b22d6111 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 a81fe6e9bd..22d380e8e6 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 21ba0afeff..02ff97b1f0 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 c19572a123..3d30ae21a3 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 e7d2a18ea9..791404c5c8 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 3688e68433..ee3bd898ea 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 98addac8f8..3791776e46 100644
--- a/services/org/team_test.go
+++ b/services/org/team_test.go
@@ -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 0627860fe7..0e74d006bb 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 ab1b85ae95..6731d9a1ac 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 736be4611b..bffca394a8 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 a3fbe4f627..75011a697c 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))
@@ -242,14 +263,17 @@ func handleCloseCrossReferences(ctx context.Context, pr *issues_model.PullReques
if err = ref.Issue.LoadRepo(ctx); err != nil {
return err
}
- isClosed := ref.RefAction == references.XRefActionCloses
- if isClosed != ref.Issue.IsClosed {
- if err = issue_service.ChangeStatus(ctx, ref.Issue, doer, pr.MergedCommitID, isClosed); err != nil {
+ if ref.RefAction == references.XRefActionCloses && !ref.Issue.IsClosed {
+ if err = issue_service.CloseIssue(ctx, ref.Issue, doer, pr.MergedCommitID); err != nil {
// Allow ErrDependenciesLeft
if !issues_model.IsErrDependenciesLeft(err) {
return err
}
}
+ } else if ref.RefAction == references.XRefActionReopens && ref.Issue.IsClosed {
+ if err = issue_service.ReopenIssue(ctx, ref.Issue, doer, pr.MergedCommitID); err != nil {
+ return err
+ }
}
}
return nil
@@ -283,7 +307,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 +398,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 +465,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 +473,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 +508,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 +546,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 +578,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 +607,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 88f6c037eb..2e1cc8cf85 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/merge_squash.go b/services/pull/merge_squash.go
index 197d8102dd..8f8a5d82e7 100644
--- a/services/pull/merge_squash.go
+++ b/services/pull/merge_squash.go
@@ -10,7 +10,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
@@ -25,12 +24,12 @@ func getAuthorSignatureSquash(ctx *mergeContext) (*git.Signature, error) {
// Try to get an signature from the same user in one of the commits, as the
// poster email might be private or commits might have a different signature
// than the primary email address of the poster.
- gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpenPath(ctx, ctx.tmpBasePath)
+ gitRepo, err := git.OpenRepository(ctx, ctx.tmpBasePath)
if err != nil {
log.Error("%-v Unable to open base repository: %v", ctx.pr, err)
return nil, err
}
- defer closer.Close()
+ defer gitRepo.Close()
commits, err := gitRepo.CommitsBetweenIDs(trackingBranch, "HEAD")
if err != nil {
diff --git a/services/pull/patch.go b/services/pull/patch.go
index 0934a86c89..13623d73c6 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"
@@ -42,9 +41,19 @@ func DownloadDiffOrPatch(ctx context.Context, pr *issues_model.PullRequest, w io
}
defer closer.Close()
- if err := gitRepo.GetDiffOrPatch(pr.MergeBase, pr.GetGitRefName(), w, patch, binary); err != nil {
- log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
- return fmt.Errorf("Unable to get patch file from %s to %s in %s Error: %w", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
+ compareArg := pr.MergeBase + "..." + pr.GetGitRefName()
+ switch {
+ case patch:
+ err = gitRepo.GetPatch(compareArg, w)
+ case binary:
+ err = gitRepo.GetDiffBinary(compareArg, w)
+ default:
+ err = gitRepo.GetDiff(compareArg, w)
+ }
+
+ if err != nil {
+ log.Error("unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
+ return fmt.Errorf("unable to get patch file from %s to %s in %s Error: %w", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
}
return nil
}
@@ -355,7 +364,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo *
_ = util.Remove(tmpPatchFile.Name())
}()
- if err := gitRepo.GetDiffBinary(pr.MergeBase, "tracking", tmpPatchFile); err != nil {
+ if err := gitRepo.GetDiffBinary(pr.MergeBase+"...tracking", tmpPatchFile); err != nil {
tmpPatchFile.Close()
log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
return false, fmt.Errorf("unable to get patch file from %s to %s in %s Error: %w", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
@@ -502,6 +511,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 +557,7 @@ func CheckFileProtection(repo *git.Repository, branchName, oldCommitID, newCommi
}
}
if len(changedProtectedFiles) > 0 {
- err = models.ErrFilePathProtected{
+ err = ErrFilePathProtected{
Path: changedProtectedFiles[0],
}
}
@@ -575,7 +607,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 3362cb97ff..85c36bb16a 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"
@@ -65,7 +64,8 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
}
// user should be a collaborator or a member of the organization for base repo
- if !issue.Poster.IsAdmin {
+ canCreate := issue.Poster.IsAdmin || pr.Flow == issues_model.PullRequestFlowAGit
+ if !canCreate {
canCreate, err := repo_model.IsOwnerMemberCollaborator(ctx, repo, issue.Poster.ID)
if err != nil {
return err
@@ -224,6 +224,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 +269,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 +676,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)
}
@@ -685,7 +707,7 @@ func CloseBranchPulls(ctx context.Context, doer *user_model.User, repoID int64,
var errs errlist
for _, pr := range prs {
- if err = issue_service.ChangeStatus(ctx, pr.Issue, doer, "", true); err != nil && !issues_model.IsErrPullWasClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
+ if err = issue_service.CloseIssue(ctx, pr.Issue, doer, ""); err != nil && !issues_model.IsErrPullWasClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
errs = append(errs, err)
}
}
@@ -719,7 +741,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re
if pr.BaseRepoID == repo.ID {
continue
}
- if err = issue_service.ChangeStatus(ctx, pr.Issue, doer, "", true); err != nil && !issues_model.IsErrPullWasClosed(err) {
+ if err = issue_service.CloseIssue(ctx, pr.Issue, doer, ""); err != nil && !issues_model.IsErrPullWasClosed(err) {
errs = append(errs, err)
}
}
diff --git a/services/release/release.go b/services/release/release.go
index 980a5e98e7..835a5943b1 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,
}
}
@@ -142,7 +179,7 @@ func CreateRelease(gitRepo *git.Repository, rel *repo_model.Release, attachmentU
return err
}
- rel.Title, _ = util.SplitStringAtByteN(rel.Title, 255)
+ rel.Title = util.EllipsisDisplayString(rel.Title, 255)
rel.LowerTagName = strings.ToLower(rel.TagName)
if err = db.Insert(gitRepo.Ctx, rel); err != nil {
return err
@@ -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/repository/adopt.go b/services/repository/adopt.go
index 615f4d482c..e37909e7ab 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/avatar.go b/services/repository/avatar.go
index 38c2621bc4..15e51d4a25 100644
--- a/services/repository/avatar.go
+++ b/services/repository/avatar.go
@@ -8,7 +8,6 @@ import (
"fmt"
"io"
"strconv"
- "strings"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
@@ -107,7 +106,8 @@ func RemoveRandomAvatars(ctx context.Context) error {
// generateAvatar generates the avatar from a template repository
func generateAvatar(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error {
- generateRepo.Avatar = strings.Replace(templateRepo.Avatar, strconv.FormatInt(templateRepo.ID, 10), strconv.FormatInt(generateRepo.ID, 10), 1)
+ // generate a new different hash, whatever the "hash data" is, it doesn't matter
+ generateRepo.Avatar = avatar.HashAvatar(generateRepo.ID, []byte("new-avatar"))
if _, err := storage.Copy(storage.RepoAvatars, generateRepo.CustomAvatarRelativePath(), storage.RepoAvatars, templateRepo.CustomAvatarRelativePath()); err != nil {
return err
}
diff --git a/services/repository/avatar_test.go b/services/repository/avatar_test.go
index 4a0ba61853..bea820e85f 100644
--- a/services/repository/avatar_test.go
+++ b/services/repository/avatar_test.go
@@ -61,3 +61,11 @@ func TestDeleteAvatar(t *testing.T) {
assert.Equal(t, "", repo.Avatar)
}
+
+func TestGenerateAvatar(t *testing.T) {
+ templateRepo := &repo_model.Repository{ID: 10, Avatar: "a"}
+ generateRepo := &repo_model.Repository{ID: 11}
+ _ = generateAvatar(db.DefaultContext, templateRepo, generateRepo)
+ assert.NotEmpty(t, generateRepo.Avatar)
+ assert.NotEqual(t, templateRepo.Avatar, generateRepo.Avatar)
+}
diff --git a/services/repository/branch.go b/services/repository/branch.go
index 3a95aab264..fc476298ca 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 5cdcc14679..acca15daf2 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 14e625d962..a3199f2a40 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 f33bae7790..2166b4dd5c 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
@@ -324,7 +317,8 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID
if len(repo.Avatar) > 0 {
if err := storage.RepoAvatars.Delete(repo.CustomAvatarRelativePath()); err != nil {
- return fmt.Errorf("Failed to remove %s: %w", repo.Avatar, err)
+ log.Error("remove avatar file %q: %v", repo.CustomAvatarRelativePath(), err)
+ // go on
}
}
diff --git a/services/repository/files/cherry_pick.go b/services/repository/files/cherry_pick.go
index 451a182155..10545e9e03 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 95e7c7087c..0ab7422ce2 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/content_test.go b/services/repository/files/content_test.go
index a899be70e3..7cb46c0bb6 100644
--- a/services/repository/files/content_test.go
+++ b/services/repository/files/content_test.go
@@ -53,7 +53,7 @@ func getExpectedReadmeContentsResponse() *api.ContentsResponse {
func TestGetContents(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam(":id", "1")
+ ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
@@ -81,7 +81,7 @@ func TestGetContents(t *testing.T) {
func TestGetContentsOrListForDir(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam(":id", "1")
+ ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
@@ -116,7 +116,7 @@ func TestGetContentsOrListForDir(t *testing.T) {
func TestGetContentsOrListForFile(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam(":id", "1")
+ ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
@@ -144,7 +144,7 @@ func TestGetContentsOrListForFile(t *testing.T) {
func TestGetContentsErrors(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam(":id", "1")
+ ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
@@ -175,7 +175,7 @@ func TestGetContentsErrors(t *testing.T) {
func TestGetContentsOrListErrors(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam(":id", "1")
+ ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
@@ -206,7 +206,7 @@ func TestGetContentsOrListErrors(t *testing.T) {
func TestGetContentsOrListOfEmptyRepos(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user30/empty")
- ctx.SetPathParam(":id", "52")
+ ctx.SetPathParam("id", "52")
contexttest.LoadRepo(t, ctx, 52)
contexttest.LoadUser(t, ctx, 30)
contexttest.LoadGitRepo(t, ctx)
@@ -231,15 +231,15 @@ func TestGetBlobBySHA(t *testing.T) {
defer ctx.Repo.GitRepo.Close()
sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d"
- ctx.SetPathParam(":id", "1")
- ctx.SetPathParam(":sha", sha)
+ ctx.SetPathParam("id", "1")
+ ctx.SetPathParam("sha", sha)
gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
if err != nil {
t.Fail()
}
- gbr, err := GetBlobBySHA(ctx, ctx.Repo.Repository, gitRepo, ctx.PathParam(":sha"))
+ gbr, err := GetBlobBySHA(ctx, ctx.Repo.Repository, gitRepo, ctx.PathParam("sha"))
expectedGBR := &api.GitBlobResponse{
Content: "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK",
Encoding: "base64",
diff --git a/services/repository/files/diff_test.go b/services/repository/files/diff_test.go
index ea6ffe60c3..b7bdcd8ecf 100644
--- a/services/repository/files/diff_test.go
+++ b/services/repository/files/diff_test.go
@@ -18,7 +18,7 @@ import (
func TestGetDiffPreview(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam(":id", "1")
+ ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
@@ -140,7 +140,7 @@ func TestGetDiffPreview(t *testing.T) {
func TestGetDiffPreviewErrors(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam(":id", "1")
+ ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
diff --git a/services/repository/files/file.go b/services/repository/files/file.go
index 16783f5b5f..d7ca8e79e5 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/file_test.go b/services/repository/files/file_test.go
index b2f51e3c82..52c0574883 100644
--- a/services/repository/files/file_test.go
+++ b/services/repository/files/file_test.go
@@ -99,7 +99,7 @@ func getExpectedFileResponse() *api.FileResponse {
func TestGetFileResponseFromCommit(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
- ctx.SetPathParam(":id", "1")
+ ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go
index ab0e7ffd36..38c17b4073 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 30ab22db1e..138af991f9 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 e3a7f3b8b0..6775186afd 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/tree_test.go b/services/repository/files/tree_test.go
index 786bc15857..0c60fddf7b 100644
--- a/services/repository/files/tree_test.go
+++ b/services/repository/files/tree_test.go
@@ -25,10 +25,10 @@ func TestGetTreeBySHA(t *testing.T) {
sha := ctx.Repo.Repository.DefaultBranch
page := 1
perPage := 10
- ctx.SetPathParam(":id", "1")
- ctx.SetPathParam(":sha", sha)
+ ctx.SetPathParam("id", "1")
+ ctx.SetPathParam("sha", sha)
- tree, err := GetTreeBySHA(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam(":sha"), page, perPage, true)
+ tree, err := GetTreeBySHA(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.PathParam("sha"), page, perPage, true)
assert.NoError(t, err)
expectedTree := &api.GitTreeResponse{
SHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
diff --git a/services/repository/files/update.go b/services/repository/files/update.go
index b1b64bacd9..a2763105b0 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 ea0690da26..53fe036628 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 f2280de8b2..24cf9d1b9b 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 817fa4abd7..c719e11786 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/migrate.go b/services/repository/migrate.go
index c627b46fab..7fe63eb5ca 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 29c67893b2..672ee49fea 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 9a643469d9..9ef28ddeb9 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 0401701ba5..91722fb8ae 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,23 +85,23 @@ 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)
+ 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)
@@ -110,12 +109,12 @@ func TestRepositoryTransfer(t *testing.T) {
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 0b3b618aae..c24ce5273c 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 cbaf90053a..4a39f4f783 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 7bde642412..1aeebff142 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 c668b005c5..162a735cd4 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 cc263947e9..a3d5cb34b1 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/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl
index 951ee590d1..ed7a8d6f24 100644
--- a/templates/base/head_navbar.tmpl
+++ b/templates/base/head_navbar.tmpl
@@ -11,9 +11,9 @@
-